You should replace

  getSubMenuItem(subMenuItems[i].items, id);

with

  var found = getSubMenuItem(subMenuItems[i].items, id);
  if (found) return found;

in order to return the element when it is found.

And be careful with the name of the properties, javascript is case sensitive, so you must also replace

  if (subMenuItems[i].Id == id) {

with

  if (subMenuItems[i].id == id) {

Demonstration


Final (cleaned) code :

var getSubMenuItem = function (subMenuItems, id) {
    if (subMenuItems) {
        for (var i = 0; i < subMenuItems.length; i++) {
            if (subMenuItems[i].id == id) {
                return subMenuItems[i];
            }
            var found = getSubMenuItem(subMenuItems[i].items, id);
            if (found) return found;
        }
    }
};
Answer from Denys Séguret on Stack Overflow
🌐
GitHub
gist.github.com › c5124906d353ab144aa6fdee032d1db9
Typescript: Recursive find in array of objects · GitHub
Typescript: Recursive find in array of objects. GitHub Gist: instantly share code, notes, and snippets.
Top answer
1 of 2
38

You should replace

  getSubMenuItem(subMenuItems[i].items, id);

with

  var found = getSubMenuItem(subMenuItems[i].items, id);
  if (found) return found;

in order to return the element when it is found.

And be careful with the name of the properties, javascript is case sensitive, so you must also replace

  if (subMenuItems[i].Id == id) {

with

  if (subMenuItems[i].id == id) {

Demonstration


Final (cleaned) code :

var getSubMenuItem = function (subMenuItems, id) {
    if (subMenuItems) {
        for (var i = 0; i < subMenuItems.length; i++) {
            if (subMenuItems[i].id == id) {
                return subMenuItems[i];
            }
            var found = getSubMenuItem(subMenuItems[i].items, id);
            if (found) return found;
        }
    }
};
2 of 2
3

I know its late but here is a more generic approach

Array.prototype.findRecursive = function(predicate, childrenPropertyName){
    if(!childrenPropertyName){
        throw "findRecursive requires parameter `childrenPropertyName`";
    }
    let array = [];
    array = this;
    let initialFind =  array.find(predicate);
    let elementsWithChildren  = array.filter(x=>x[childrenPropertyName]);
    if(initialFind){
        return initialFind;
    }else if(elementsWithChildren.length){
        let childElements = [];
        elementsWithChildren.forEach(x=>{
            childElements.push(...x[childrenPropertyName]);
        });
        return childElements.findRecursive(predicate, childrenPropertyName);
    }else{
        return undefined;
    }
}

to use it:

var array = [<lets say an array of students who has their own students>];
var joe = array.findRecursive(x=>x.Name=="Joe", "students");

and if you want filter instead of find

Array.prototype.filterRecursive = function(predicate, childProperty){
    let filterResults = [];
    let filterAndPushResults = (arrayToFilter)=>{
        let elementsWithChildren  = arrayToFilter.filter(x=>x[childProperty]);
        let filtered = arrayToFilter.filter(predicate);
        filterResults.push(...filtered);
        if(elementsWithChildren.length){
            let childElements = [];
            elementsWithChildren.forEach(x=>{
                childElements.push(...x[childProperty]);
            });
            filterAndPushResults(childElements);
        }
    };
    filterAndPushResults(this);
    return filterResults;
}
Discussions

Recursively find TypeScript files imported/exported from an entry point
I'd like to know what's the best practice as functional programming in JavaScript to work with arrays and passing them as an argument to a recursive function that may run itself within its branch. ... More on codereview.stackexchange.com
🌐 codereview.stackexchange.com
August 22, 2017
reactjs - Typescript recursive search - Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most. Learn more about Collectives ... Connect and share knowledge within a single location that is structured and easy to search. Learn more about Teams · Get early access and see previews of new features. Learn more about Labs ... I'm trying to do a recursive search in Typescript ... More on stackoverflow.com
🌐 stackoverflow.com
How do I recursively use Array.prototype.find() while returning a single object?
Return the searches result variable ... is your find, otherwise just remember not to come back down by this branch); Keep iteration if node wasn't found (if banana wasn't found keep testing other branches); Return false if after all iterations the desired node wasn't found (assert that ripe bananas doesn't grow on this tree). Keep learning recursion it seems not ... More on stackoverflow.com
🌐 stackoverflow.com
Recursive Function Problem
So your problem is that your only return statement is conditional, so typescript will always evaluate this as either number[] or undefined/void Flip your logic so that your return statement only contains a number[] or modify your return type to include undefined and add an else return statement. More on reddit.com
🌐 r/typescript
7
8
October 18, 2021
🌐
DEV Community
dev.to › a_b_102931 › searching-through-a-nested-object-using-recursion-regular-expressions-and-sets-bm7
Searching Through a Nested Object Using Recursion, Regular Expressions, and Sets - DEV Community
March 11, 2020 - The tricky thing with nested objects is that some values--but not all--are objects themselves. That means we need to use recursion to iterate through those values, and do so until we the value is a string. Here, we can use typeof to check if the value at each key is an object.
🌐
Cookielab
cookielab.io › blog › tackling-recursion-not-just-in-typescript
Tackling recursion (not just) in TypeScript | Cookielab
It is a union type describing either final result (<inline-code>Value<inline-code>), or a yet-to-called function to get the final result (<inline-code>Recurse<inline-code>). If you are wondering what the purpose of _tag property is, check TypeScript: Documentation - Narrowing for more details.
Top answer
1 of 1
3

Update

As the OP is been updated and changed I want to change my answer, I'll live the original at the end.

I see you use Object Oriented other than functional, so I'll comment some I saw that is not ok for me.

Encapsulation broken

Looking at your code I see the broken of encapsulation. This means that from a piece of code you access or worst you try to change some internal data of an object.

You should not do that. Rather you should improve the object to provide a propper interface.

Here is an example from your code in handleFile():

...
const destFilePath = file.path.replace(baseDir, destDir);
copyFile(file, destFilePath);
...

The issue here is you have to know to that baseDir should match with the file path attribute.

A better way would be just:

copyFile(file, destDir);

in this case you conain inside the copyFile() function the details about using the path.replace... staff. So you avoid to copy a file in another part of your source code and forgot this part.

Another reason to put the logic inside the copyFile() function is that you could actually remove that line:

function copyFile(file: VinylFile, destFilePath: string): void {
    const destDir = path.dirname(destFilePath);
    ensureMakeDir(destDir);
    writeFile(destFilePath, file.contents.toString());
}

I think you just want to place the file name at the end of the new path. The first line of the function here, you just do a rollback of this part.

function copyFile(file: VinylFile, destDir: string): void {
    ensureMakeDir(destDir);
    const destFileName = path.join(destDir, path.basename(file.path));
    writeFile(destFileName, file.contents.toString());
}

This is a little bit better because you contain the VinyFile details in a single function, and you could change it without worring that something will broke in your code.

But is not the best! A better solution will be if the file know how to copy to another path, in such a way you should not deal with the object internal details.

You should be able to do:

file.copyTo(destDir);

If you try to write such a method you will notice that the code needed is simplest and much clear.

And you could maintain all the object boundary safe from the code base.

Another small thing here. I saw you use the path package before, so please avoid some trick like:

file.path.replace(baseDir, destDir);

It is difficult to understand and easy to broke.

Also the getVinyl() could be removed, the VinylFile should know how to load his content properly.

Here on getChildren(): while ((match = statementRE.exec(file.contents.toString())) && !isNil(match))

Correction

I checked the Javascript RegEx documentation of exec and in the examples they show a case where you use exec() with the option g. In this case you could have a loop as the regex object save the lastIndex and consider just a substring.

Anyway, if you remove g option exec() will behave differently, and that is dangerous.

For that reason I found out that the following sentence is not accurate.

Not sure why you use a loop as exec should return the matching. As the file content is not changing you will loop forever.

And this part I'm sure is not working:

    debugger;
    const indexBarrel = path.resolve(resolvedPath, 'index.ts');

There is still the debugger... :) I think you run this code on server side so that line have no effects, but please remove it, just in case.

The getChildren() function should be moved as a method of VinylFile. Here I use only es6 syntax as I don't know Typescript, but I think it will be easy to move on the proper syntax.

// should be a VinylFile attribute or a private const
const statementRE = /(import|export)\s*((\{[^}]*\}|\*)(\s*as\s+\w+)?(\s+from)?\s*)?([`'"])(.*)\6/ig;

hasChildren() {

   if (subFilesMatch != null) {

   }
   return false;
}

getChildren(): VinylFile[] {
    const children: VinylFile[] = [];

    let subFilesMatch = statementRE.exec(this.contents.toString()));
    if (!isNil(subFilesMatch)) {
        const filePath = subFilesMatch[FILE_PATH_MATCH_INDEX];

        // Again don't use triks rather use the packages features!
        if (path.extname(filePath) !== '.ts') {
            filePath += '.ts';
        }
        const childFileName = path.join(this.base, filePath);
        if (isFile(childFileName)) {
            // Here I just rearrange your code, but you have an issue here!
            // I don't know how many children you have on each file instance
            // but *getChildren()* is synchronous function, so the file 
            // content load is blocking!
            const contents = loadContents(childFileName);
            const opts = {base: this.base, path: childFileName, contents}
            children.push(new VinylFile(opts));
        } else if(isDirectory(childFileName)) {
            ...
        }
    }

    return children;
 }

As prompted in the comment above, your method here, even the original function, is blocking code, as it load the file content of the children sinchronously.

You should convert this too in an async function. I don't know the syntax in Typescript.

Last but not last handleFile() It is an async function and should return a promise and it doesn't.

Maybe Typescript is honoring the function signature and returning a promise in any case, and, I guess, in a working like mode, so you think the code is fine.

Please check this part too.

As I said before, you should create an array of promises and return it from getChildren(), and then return a Promise.all() from handleFile().

I'll do this in javascript.

Original answer

I'm not a big expert of functional programming, but your example did not fit.

I guess so.

The first point is that you create a list of random numbers. This end up in a different list each time.

In functional programming, a function should take input and produce output, and given the input it should return always the same output.

So, you just set up a trivial example, but in my above definition you just could notice other issues.

The parameter name resultList, is not the "resultList", is just the start list that is going to be fit of 10 random numbers.

function SampleRecursiveFunction(resultList) {
  // copy our list
  const result = [...resultList];

Instead of resultList you should have a different name like: inputList.

Another bad name is result.

You should use some much helpfull like: randomNumberList

  function SampleRecursiveFunction(inputList, maxLength = 10) {
      if (inputList.length >= maxLength)
          return [...inputList];

      let randomNumber = Math.random()*100;
      while(inputList.length > 0 && 
            inputList.indexOf(randomNumber) !== -1)
          randomNumber = Math.random()*100;

      return SampleRecursiveFunction([...inputList, randomNumber], maxLength);
  }

This is a small evolution of your function with recursion.

The function accept a new parameter that set the returned list max length.

An even better version could be like:

function GetRandomNumber(seed) {
    return function () {
        return Math.random() * seed;
    }
}

This function return a function that provide the proper next value.

In real case you could have a generation function instead, depending on what you're doing.

function SampleRecursiveFunction(inputList, getNewNumber, maxLength = 10) {
      if (inputList.length >= maxLength)
          return [...inputList];

      let randomNumber = getNewNumber();
      while(inputList.length > 0 && 
            inputList.indexOf(randomNumber) !== -1)
          randomNumber = getNewNumber();

      return SampleRecursiveFunction([...inputList, randomNumber], maxLength);
  }

And you could compose the function like this:

  console.log(SampleRecursiveFunction([], GetRandomNumber(100)));

The good of this is you could split your logic into small tasks and put them in functions. At the end you compose those functions and building your whole logic.

As we deal with random numbers, even my example is not pure functional, I just try to give you a review of your code example.

I think you should find a more detailed example on what you're tring to do to have a much usefull help.

🌐
Joshtronic
joshtronic.com › 2020 › 04 › 20 › recursive-functions-in-typescript
Recursive functions in TypeScript - Joshtronic
April 20, 2020 - An easy example of a recursive function would be something that takes a nested array of objects like I mentioned above, and perhaps tallies up some values to get a grand total.
🌐
Stack Overflow
stackoverflow.com › questions › 70686449 › typescript-recursive-search
reactjs - Typescript recursive search - Stack Overflow
function findDirectory( directoryId: Key, directory: Directory, ): Directory | undefined { if (!directory) return; if (directory.id === directoryId) return directory; for (const child of directory.children) { const res = findDirectory(directoryId, child); if (res) return res; } }
🌐
GoLinuxCloud
golinuxcloud.com › home › javascript › recursive search in array of objects javascript? [solved]
Recursive search in array of objects JavaScript? [SOLVED] | GoLinuxCloud
January 17, 2023 - This function will check all elements of the array and recursively check all children arrays until it finds the first object that matches the specified value.
Find elsewhere
Top answer
1 of 13
35

The problem what you have, is the bubbling of the find. If the id is found inside the nested structure, the callback tries to returns the element, which is interpreted as true, the value for the find.

The find method executes the callback function once for each element present in the array until it finds one where callback returns a true value. [MDN]

Instead of find, I would suggest to use a recursive style for the search with a short circuit if found.

var data = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4, children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }] }, { id: 5 }];

function findById(data, id) {
    function iter(a) {
        if (a.id === id) {
            result = a;
            return true;
        }
        return Array.isArray(a.children) && a.children.some(iter);
    }

    var result;
    data.some(iter);
    return result
}

console.log(findById(data, 8));

2 of 13
10

Let's consider the implementation based on recursive calls:

function findById(tree, nodeId) {
  for (let node of tree) {
    if (node.id === nodeId) return node

    if (node.children) {
      let desiredNode = findById(node.children, nodeId)
      if (desiredNode) return desiredNode
    }
  }
  return false
}

Usage

var data = [
  { id: 1 }, { id: 2 }, { id: 3 },
  { id: 4, children: [
      { id: 6 },
      { id: 7,
        children: [
          { id: 8 }, 
          { id: 9 }
        ]}]},
  { id: 5 }
]

findById(data,  7 ) // {id: 7, children: [{id: 8}, {id: 9}]}
findById(data,  5 ) // {id: 5}
findById(data,  9 ) // {id: 9}
findById(data,  11) // false

To simplify the picture, imagine that:

  • you are the monkey sitting on the top of a palm tree;
  • and searching for a ripe banana, going down the tree
  • you are in the end and searches aren't satisfied you;
  • come back to the top of the tree and start again from the next branch;
  • if you tried all bananas on the tree and no one is satisfied you, you just assert that ripe bananas don't grow on this this palm;
  • but if the banana was found you come back to the top and get pleasure of eating it.

Now let's try apply it to our recursive algorithm:

  1. Start iteration from the top nodes (from the top of the tree);
  2. Return the node if it was found in the iteration (if a banana is ripe);
  3. Go deep until item is found or there will be nothing to deep. Hold the result of searches to the variable (hold the result of searches whether it is banana or just nothing and come back to the top);
  4. Return the searches result variable if it contains the desired node (eat the banana if it is your find, otherwise just remember not to come back down by this branch);
  5. Keep iteration if node wasn't found (if banana wasn't found keep testing other branches);
  6. Return false if after all iterations the desired node wasn't found (assert that ripe bananas doesn't grow on this tree).

Keep learning recursion it seems not easy at the first time, but this technique allows you to solve daily issues in elegant way.

🌐
Execute Program
executeprogram.com › courses › advanced-typescript › lessons › recursive-types
Advanced TypeScript: Recursive Types
Learn programming languages like TypeScript, Python, JavaScript, SQL, and regular expressions. Interactive with real code examples.
🌐
GitHub
gist.github.com › shakhal › 3cf5402fc61484d58c8d
Find values in JSON by key, recursively · GitHub
findValuesHelper(obj, key) { let list = [ ]; if (!obj) return list; if (obj instanceof Array) { for (var i in obj) { list = list.concat(this.findValuesHelper(obj[i], key)); } return list; } if (obj[key]) list.push(obj[key]); if ((typeof obj == "object") && (obj !== null)) { let children = Object.keys(obj); if (children.length > 0) { for (let i = 0; i < children.length; i++) { list = list.concat(this.findValuesHelper(obj[children[i]], key)); } } } return list; }
🌐
C# Corner
c-sharpcorner.com › UploadFile › 5089e0 › how-to-use-recursive-function-in-typescript
How To Use Recursive Function in TypeScript
October 14, 2019 - In this article, I will use direct recursion. The following example tells you, how to use recursive a function in TypeScript. In this example, we will find the factorial of a number we enter.
🌐
TypeScript
typescriptlang.org › play › 3-7 › types-and-code-flow › recursive-type-references.ts.html
TypeScript: Playground Example - Recursive Type References
type Json = string | number | boolean | null | Json[] | { [key: string]: Json }; const exampleStatusJSON: Json = { available: true, username: "Jean-loup", room: { name: "Highcrest", // Cannot add functions into the Json type // update: () => {} }, }; // There's more to learn from the 3.7 beta release notes and its PR: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/ https://github.com/microsoft/TypeScript/pull/33050
🌐
Medium
medium.com › @yazeedb › implement-array-filter-with-recursion-5b60a3e58398
Implement Array.filter with Recursion | by Yazeed Bzadough | Medium
June 5, 2018 - Implement Array.filter with Recursion My last post covered a recursive Array.map. While it’s a good learning resource, I don’t recommend anyone actually use it in their apps. Same goes for …
🌐
JavaScript Tutorial
javascripttutorial.net › home › javascript tutorial › javascript recursive function
JavaScript Recursive Function
November 15, 2024 - This tutorial shows you how to use the recursion technique to develop a JavaScript recursive function, which is a function that calls itself.
Top answer
1 of 2
38

You should replace

  getSubMenuItem(subMenuItems[i].items, id);

with

  var found = getSubMenuItem(subMenuItems[i].items, id);
  if (found) return found;

in order to return the element when it is found.

And be careful with the name of the properties, javascript is case sensitive, so you must also replace

  if (subMenuItems[i].Id == id) {

with

  if (subMenuItems[i].id == id) {

Demonstration


Final (cleaned) code :

var getSubMenuItem = function (subMenuItems, id) {
    if (subMenuItems) {
        for (var i = 0; i < subMenuItems.length; i++) {
            if (subMenuItems[i].id == id) {
                return subMenuItems[i];
            }
            var found = getSubMenuItem(subMenuItems[i].items, id);
            if (found) return found;
        }
    }
};
2 of 2
3

I know its late but here is a more generic approach

Array.prototype.findRecursive = function(predicate, childrenPropertyName){
    if(!childrenPropertyName){
        throw "findRecursive requires parameter `childrenPropertyName`";
    }
    let array = [];
    array = this;
    let initialFind =  array.find(predicate);
    let elementsWithChildren  = array.filter(x=>x[childrenPropertyName]);
    if(initialFind){
        return initialFind;
    }else if(elementsWithChildren.length){
        let childElements = [];
        elementsWithChildren.forEach(x=>{
            childElements.push(...x[childrenPropertyName]);
        });
        return childElements.findRecursive(predicate, childrenPropertyName);
    }else{
        return undefined;
    }
}

to use it:

var array = [<lets say an array of students who has their own students>];
var joe = array.findRecursive(x=>x.Name=="Joe", "students");

and if you want filter instead of find

Array.prototype.filterRecursive = function(predicate, childProperty){
    let filterResults = [];
    let filterAndPushResults = (arrayToFilter)=>{
        let elementsWithChildren  = arrayToFilter.filter(x=>x[childProperty]);
        let filtered = arrayToFilter.filter(predicate);
        filterResults.push(...filtered);
        if(elementsWithChildren.length){
            let childElements = [];
            elementsWithChildren.forEach(x=>{
                childElements.push(...x[childProperty]);
            });
            filterAndPushResults(childElements);
        }
    };
    filterAndPushResults(this);
    return filterResults;
}
🌐
DEV Community
dev.to › smeijer › three-ways-to-handle-recursion-5g2l
Three ways to handle recursion - DEV Community
May 29, 2023 - Also, mind that while the output has the same structure, the resulting nodes are in a different order. The recursive function eagerly moves to the child nodes and handles children before siblings. While the loop handles siblings before children.
🌐
Reddit
reddit.com › r/typescript › typed function to recursively access object properties?
r/typescript on Reddit: Typed function to recursively access object properties?
April 12, 2020 -

Is there a good way in typescript to type a function that will recursively access object properties? Here's a hand-coded example of going two levels deep:

function deepProp<T, K extends keyof T>(obj: T, prop1: K, prop2: keyof T[K]) {
    return obj[prop1][prop2];
}

// Example use
const obj = {
    user: {
        pets: [{
            toys: [{
                name: "Toughy",
                price: 1999
            }]
        }]
    }
} as const
deepProp(obj, "user", "pets");

But I'm looking for a good way to take any number of props in the deepProp function to dive down as deep as necessary. I imagine that function's signature would be something like function deepProp(obj, ...props) { }. Is there a good way to do this? Thanks!