skip to Main Content

I’m trying to build a searchable menu of teams in Javascript/TypeScript – not sure how to do it.

Basically, I have a set of data laid out like this.

const data = [
    {
        'name': 'Alex A',
        'agentId': '1225',
     },
    {
        'name': 'Tammy',
        'agentId': '125',
        'team': [
            {
                'name': 'Alex B',
                'agentId': 249
            },
            {
                'name': 'Alexander C',
                'agentId': 148
            },
            {
                'name': 'Ed',
                'agentId': 382
            }
        ]
    },
    {
        'name': 'Edward',
        'agentId': '594',
        'team': [
            {
                'name': 'Daniel',
                'agentId': 152
            },
            {
                'name': 'Alexis D',
                'agentId': 1900
            }
        ]
    }
];

When I search (i.e. Alex) so my new data set would be:

const data = [
    {
        'name': 'Alex A',
        'agentId': '1225',
     },
    {
        'name': 'Tammy',
        'agentId': '125',
        'team': [
            {
                'name': 'Alex B',
                'agentId': 249
            },
            {
                'name': 'Alexander C',
                'agentId': 148
            }
        ]
    },
    {
        'name': 'Edward',
        'agentId': '594',
        'team': [
            {
                'name': 'Alexis D',
                'agentId': 1900
            }
        ]
    }
];

I am more familiar with PHP so I would do something along the lines of:

$x = 0;
foreach($data as $row){
       if (strpos($row.name, 'alex')) {
            $newArray[$x] = $row;
           }

       foreach($row['team'] as $person) {
        if (strpos($person.name, 'alex')) {
           if(!isset($newArray[$x])) {
             $newArray[$x] = $row;
          }
             $newArray[$x]['team'][] = $person;
            }
        $x++;
    }

}

Been working with Typescript within the last few months, so this is the rabbit hole i’m going down, but I know it’s not right.

const searchName = "Alex";
const newArray = [];
if (searchName.length >= 3) {
  const filtered = data.forEach((user) => {
    const name = user.name.toLowerCase();
    if (name.indexOf(searchName.toLowerCase())) {
      newArray.push(user);
    }
    if (user.team) {
      const teamFiltered = user.team.forEach((member) => {
        if (member.name.indexOf(searchName.toLowerCase())) {
          newArray.push({
            name: user.name,
            agentId: user.agentId,
            team: member,
          });
        }
      });
    }
  });
}

3

Answers


  1. First iterate over each array item and create a new team property (if it exists in the first place) with only Alexes in it.

    const searchStr = 'Alex'.toLowerCase();
    const filteredTeams = data.map(
      obj => !obj.team ? obj : ({
        // clone to avoid mutation
        ...obj,
        team: objteam.filter(({ name }) => name.toLowerCase().includes(searchStr))
      })
    );
    

    Then apply another filter so that the top-level items only contain those with names that match, or whose team property is not empty.

    const data=[{name:"Alex A",agentId:"1225"},{name:"Tammy",agentId:"125",team:[{name:"Alex B",agentId:249},{name:"Alexander C",agentId:148},{name:"Ed",agentId:382}]},{name:"Edward",agentId:"594",team:[{name:"Daniel",agentId:152},{name:"Alexis D",agentId:1900}]}];
    const searchStr = 'Alex'.toLowerCase();
    
    const filteredTeams = data.map(
      obj => !obj.team ? obj : ({
        // clone to avoid mutation
        ...obj,
        team: obj.team.filter(({ name }) => name.toLowerCase().includes(searchStr))
      })
    );
    const output = filteredTeams.filter(({ name, team }) => (
      name.toLowerCase().includes(searchStr) ||
      team.length && team.length > 0
    ));
    console.log(output);
    Login or Signup to reply.
  2. Here is the function

    const filterFn = (data, search) => {
      let str = search.toLowerCase();
      return data.map((element) => {
        // map through the array items and treat the object of every itteration as element
        let newTeam;
        if (element.team) {
          // if the property team exists in element
          newTeam = element.team.filter(
            // loop through element.team
            (member) => member.name.toLowerCase().includes(str) 
              // return each object with the property 'name' contains 'str' to 'newTeam'
          );
        }
        if (element.team && newTeam.length > 0) {
          // if 'newTeam' it is not an empty array so it is full of objects that contains 'str' in their property 'name'
          return { ...element, team: newTeam }; 
           // so we return a copy of element updating the property 'team' with the value of 'newTeam'
        } else if (element.name.toLowerCase().includes(str)) {
          // else ('newTeam' is empty) if (the property 'name' of element contains 'str')
          return element; 
          // we return element
        }
      });
    };
    

    And call it this way passing it the array you want to filter and the lookup value:

    const filtredArray = filterFn(data,"alex")
    
    Login or Signup to reply.
  3. We can start by defining the type for your data. We will make sure our solution works for data nested to any depth

    type Agent = {
      name: string,
      agentId: number,
      team?: Array<Agent>
    }
    
    const data: Array<Agent> = [
        { name: 'Alex A', agentId: 1225 },
        { name: 'Tammy', agentId: 125, team: [
            { name: 'Alex B', agentId: 249 },
            { name: 'Alexander C', agentId: 148, team: [
                { name: "Alice D", agentId: 999 }
            ]},
            { name: 'Ed', agentId: 382 }
        ]},
        { name: 'Edward', agentId: 594, team: [
            { name: 'Daniel', agentId: 152 },
            { name: 'Alexis D', agentId: 1900 }
        ]}
    ]
    

    Next we write filter as a mutually recursive function. This technique is excellent for processing tree-like data. We have two functions –

    1. many that processes a list of elements
    2. one that processes a single element

    Using the tandem pair we can easily filter the data with minimal complexity –

    function filter(arr: Array<Agent>, query: string): Array<Agent> {
      function many(ts: Array<Agent>): Array<Agent> {
        return ts.flatMap(one)
      }
      function one(t: Agent): Array<Agent> {
        const team = many(t.team ?? [])
        // include the node if the query matches
        // or if the node has matching children
        return t.name.toLowerCase().indexOf(query) >= 0 || team.length > 0
            ? [ { ...t, team } ]
            : []
      }
      return many(arr)
    }
    
    console.log(filter(data, "alex"))
    

    Run the demo below to verify the result in your own browser or on typescript playground

    const data = [
        { name: 'Alex A', agentId: 1225 },
        { name: 'Tammy', agentId: 125, team: [
            { name: 'Alex B', agentId: 249 },
            { name: 'Alexander C', agentId: 148, team: [
                { name: "Alice D", agentId: 999 }
            ]},
            { name: 'Ed', agentId: 382 }
        ]},
        { name: 'Edward', agentId: 594, team: [
            { name: 'Daniel', agentId: 152 },
            { name: 'Alexis D', agentId: 1900 }
        ]}
    ]
    
    function filter(arr, query) {
        function many(ts) {
            return ts.flatMap(one)
        }
        function one(t) {
            const team = many(t.team ?? [])
            return t.name.toLowerCase().indexOf(query) >= 0 || team.length > 0
                ? [{ ...t, team }]
                : []
        }
        return many(arr)
    }
    
    console.log(JSON.stringify(filter(data, "alex"), null, 2))
    .as-console-wrapper { min-height: 100%; top: 0; }
    [
      {
        "name": "Alex A",
        "agentId": 1225,
        "team": []
      },
      {
        "name": "Tammy",
        "agentId": 125,
        "team": [
          {
            "name": "Alex B",
            "agentId": 249,
            "team": []
          },
          {
            "name": "Alexander C",
            "agentId": 148,
            "team": []
          }
        ]
      },
      {
        "name": "Edward",
        "agentId": 594,
        "team": [
          {
            "name": "Alexis D",
            "agentId": 1900,
            "team": []
          }
        ]
      }
    ]
    

    An obvious improvement would be to make filter a higher-order funciton, much like Array.prototype.filter. Instead of a fixed query: string parameter, we can use a func: (agent: Agent) => boolean) parameter, allowing the caller to filter with much greater flexibility –

    function filter(
      arr: Array<Agent>,
      func: (agent: Agent) => boolean // ✅ filtering function
    ): Array<Agent> {
      function many(ts: Array<Agent>): Array<Agent> {
        return ts.flatMap(one)
      }
      function one(t: Agent): Array<Agent> {
        const team = many(t.team ?? [])
        return func(t) || team.length > 0 // ✅ simplified
            ? [ { ...t, team } ]
            : []
      }
      return many(arr)
    }
    

    The caller can specify exactly how the data is filtered now. This will produce the same result as before –

    filter(data, agent =>
      agent.name.toLowerCase().indexOf("alex") >= 0
    )
    

    But maybe the caller wishes to filter using regex –

    filter(data, agent => /ed/i.test(agent.name))
    
    [
      {
        "name": "Tammy",
        "agentId": 125,
        "team": [
          {
            "name": "Ed",
            "agentId": 382,
            "team": []
          }
        ]
      },
      {
        "name": "Edward",
        "agentId": 594,
        "team": []
      }
    ] 
    

    Or perhaps you want to filter by agentId

    filter(data, agent => agent.agentId == 1900)
    
    [
      {
        "name": "Edward",
        "agentId": 594,
        "team": [
          {
            "name": "Alexis D",
            "agentId": 1900,
            "team": []
          }
        ]
      }
    ]
    

    You can verify these examples on typescript playground.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search