admin管理员组

文章数量:1328037

I'm facing a problem with the search. It is a front-end search rather than a remote search, I'm using react.js because it is a requirement in the problem and created a ponent named App. My task is to display and highlight the matching parts according to the type value.

I will appreciate it. If you provide me a good solution for this.

Let me tell you the whole scenario. I'm dividing this problem into 3 parts.

Part 1: What is the shape of the data?

The shape of the data is this:

src/data.js:

export default [
    {
        id: 1,
        name: 'Wordpress',
        list: [
            {
                id: 1,
                name: 'Best Mobile App Builder',
                slug: '/'
            },
            {
                id: 2,
                name: 'Best Wordpress Themes',
                slug: '/'
            },
            {
                id: 3,
                name: 'Best Website Creator',
                slug: '/'
            },
            {
                id: 4,
                name: 'Best Wordpress Builder',
                slug: '/'
            }
        ]
    },
    {
        id: 2,
        name: 'SaaS',
        list: [
            {
                id: 1,
                name: 'Appointment Scheduling Software',
                slug: '/'
            },
            {
                id: 2,
                name: 'Design Services',
                slug: '/'
            },
            {
                id: 3,
                name: 'Online Cloud Storage',
                slug: '/'
            },
            {
                id: 4,
                name: 'Remote PC Access',
                slug: '/'
            }
        ]
    },
];

Note:

Basically this is my filter function.

src/filter.js:

import _ from 'lodash';
import match from 'autosuggest-highlight/match';
import parse from 'autosuggest-highlight/parse';

/**
 * Returns the new filtered array with highlighted parts.
 * @param data {Array<Object>} - The collection to iterate over.
 * @param inputValue {string} - The input value.
 * @return {Array} - Returns the new filtered array.
 */
export const filterByNames = (data, inputValue) => {
    // Create a dynamic regex expression object with ignore case sensitivity
    const re = new RegExp(_.escapeRegExp(inputValue), 'i');
    const results = data.filter((object) => {
        if (re.test(object.name)) {
            return true;
        } else {
            return object.list.some((item) => {
                if (re.test(item.name)) {
                    // Calculates the characters to highlight in text based on query
                    const matches = match(item.name, inputValue);
                    // Breaks the given text to parts based on matches.
                    // After that create a new property named `parts` and assign an array to it.
                    item['parts'] = parse(item.name, matches);
                    return true;
                } else {
                    return false;
                }
            });
        }
    });
    return results;
};

The search is working fine but facing 2 major issues.

  1. When the above match of the name property occurs, then it stops and does not go much deeper. The same thing is happening with the nested list name property.

  2. When the filtration happens behind the scenes we're mutating the original data by adding a new property named parts which contains highlighted parts and it is an array of objects. But I don't want to mutate the original data instead wants to return the new filtered array which contains parts property.

See this.

WORKING DEMO :

Part 2: Which third-party libraries I'm using for filter and highlighting?

  • lodash string function escapeRegExp for escapes the RegExp special characters.

  • autosuggest-highlight match function to calculates the characters to highlight in text based on the query.

    After that, from the same library parse function help us to break the given text to parts based on matches. In the end, it will return an array of objects with the match string and highlight boolean flag. So it's easy for us to bold the highlighted parts on the UI.

Part 3: App ponent

import React, { useState } from 'react';
import { filterByNames } from './filter';
import data from './data';


/**
 * Return the JSX for the List
 * @param data {Array<Object>} - The collection to iterate over.
 * @return {null|*} - Returns the JSX or null.
 */
const renderList = (data) => {
  if (Array.isArray(data) && data.length > 0) {
    return data.map((object) => {
      return (
          <div key={object.id}>
            <h1>{object.name}</h1>
            <ul className="list">
              {object.list.map((item) => {
                return (
                    <li key={item.id}>
                      {item.parts ? (
                          <a href={item.slug}>
                            {item.parts.map((part, index) => (
                                <span
                                    key={index}
                                    style={{ fontWeight: part.highlight ? 700 : 400 }}
                                >
                          {part.text}
                        </span>
                            ))}
                          </a>
                      ) : (
                          <a href={item.slug}>{item.name}</a>
                      )}
                    </li>
                )
              })}
            </ul>
          </div>
      )
    })
  } else {
    return null
  }
};

// Main App Component
const App = () => {

  const [value, setValue] = useState('');

  const onChangeHandler = (event) => {
    const { target } = event;
    const val = target.value;
    setValue(val);
  };

  const results = !value ? data : filterByNames(data, value);
  
    return (
        <div className="demo">
          <input type="text" value={value} onChange={onChangeHandler}/>
          <div className="demo-result">
            { renderList(results) }
          </div>
        </div>
    );
    
};

export default App;

I'm facing a problem with the search. It is a front-end search rather than a remote search, I'm using react.js because it is a requirement in the problem and created a ponent named App. My task is to display and highlight the matching parts according to the type value.

I will appreciate it. If you provide me a good solution for this.

Let me tell you the whole scenario. I'm dividing this problem into 3 parts.

Part 1: What is the shape of the data?

The shape of the data is this:

src/data.js:

export default [
    {
        id: 1,
        name: 'Wordpress',
        list: [
            {
                id: 1,
                name: 'Best Mobile App Builder',
                slug: '/'
            },
            {
                id: 2,
                name: 'Best Wordpress Themes',
                slug: '/'
            },
            {
                id: 3,
                name: 'Best Website Creator',
                slug: '/'
            },
            {
                id: 4,
                name: 'Best Wordpress Builder',
                slug: '/'
            }
        ]
    },
    {
        id: 2,
        name: 'SaaS',
        list: [
            {
                id: 1,
                name: 'Appointment Scheduling Software',
                slug: '/'
            },
            {
                id: 2,
                name: 'Design Services',
                slug: '/'
            },
            {
                id: 3,
                name: 'Online Cloud Storage',
                slug: '/'
            },
            {
                id: 4,
                name: 'Remote PC Access',
                slug: '/'
            }
        ]
    },
];

Note:

Basically this is my filter function.

src/filter.js:

import _ from 'lodash';
import match from 'autosuggest-highlight/match';
import parse from 'autosuggest-highlight/parse';

/**
 * Returns the new filtered array with highlighted parts.
 * @param data {Array<Object>} - The collection to iterate over.
 * @param inputValue {string} - The input value.
 * @return {Array} - Returns the new filtered array.
 */
export const filterByNames = (data, inputValue) => {
    // Create a dynamic regex expression object with ignore case sensitivity
    const re = new RegExp(_.escapeRegExp(inputValue), 'i');
    const results = data.filter((object) => {
        if (re.test(object.name)) {
            return true;
        } else {
            return object.list.some((item) => {
                if (re.test(item.name)) {
                    // Calculates the characters to highlight in text based on query
                    const matches = match(item.name, inputValue);
                    // Breaks the given text to parts based on matches.
                    // After that create a new property named `parts` and assign an array to it.
                    item['parts'] = parse(item.name, matches);
                    return true;
                } else {
                    return false;
                }
            });
        }
    });
    return results;
};

The search is working fine but facing 2 major issues.

  1. When the above match of the name property occurs, then it stops and does not go much deeper. The same thing is happening with the nested list name property.

  2. When the filtration happens behind the scenes we're mutating the original data by adding a new property named parts which contains highlighted parts and it is an array of objects. But I don't want to mutate the original data instead wants to return the new filtered array which contains parts property.

See this.

WORKING DEMO :

Part 2: Which third-party libraries I'm using for filter and highlighting?

  • lodash string function escapeRegExp for escapes the RegExp special characters.

  • autosuggest-highlight match function to calculates the characters to highlight in text based on the query.

    After that, from the same library parse function help us to break the given text to parts based on matches. In the end, it will return an array of objects with the match string and highlight boolean flag. So it's easy for us to bold the highlighted parts on the UI.

Part 3: App ponent

import React, { useState } from 'react';
import { filterByNames } from './filter';
import data from './data';


/**
 * Return the JSX for the List
 * @param data {Array<Object>} - The collection to iterate over.
 * @return {null|*} - Returns the JSX or null.
 */
const renderList = (data) => {
  if (Array.isArray(data) && data.length > 0) {
    return data.map((object) => {
      return (
          <div key={object.id}>
            <h1>{object.name}</h1>
            <ul className="list">
              {object.list.map((item) => {
                return (
                    <li key={item.id}>
                      {item.parts ? (
                          <a href={item.slug}>
                            {item.parts.map((part, index) => (
                                <span
                                    key={index}
                                    style={{ fontWeight: part.highlight ? 700 : 400 }}
                                >
                          {part.text}
                        </span>
                            ))}
                          </a>
                      ) : (
                          <a href={item.slug}>{item.name}</a>
                      )}
                    </li>
                )
              })}
            </ul>
          </div>
      )
    })
  } else {
    return null
  }
};

// Main App Component
const App = () => {

  const [value, setValue] = useState('');

  const onChangeHandler = (event) => {
    const { target } = event;
    const val = target.value;
    setValue(val);
  };

  const results = !value ? data : filterByNames(data, value);
  
    return (
        <div className="demo">
          <input type="text" value={value} onChange={onChangeHandler}/>
          <div className="demo-result">
            { renderList(results) }
          </div>
        </div>
    );
    
};

export default App;
Share Improve this question edited Aug 18, 2020 at 18:33 Ven Nilson asked Aug 16, 2020 at 16:47 Ven NilsonVen Nilson 1,0195 gold badges17 silver badges46 bronze badges 10
  • Easily could be handled with vanilla javascript, and perhaps ajax with jquery Meaningful ment because react isnt needed for this. – GetSet Commented Aug 16, 2020 at 16:50
  • @GetSet Don't need ajax it is not remote search but client-side search. – Ven Nilson Commented Aug 16, 2020 at 16:52
  • @GetSet Yes, don't need ajax the whole frontend of the application is in react.js. But the filter function is the main thing. – Ven Nilson Commented Aug 16, 2020 at 17:02
  • But react like most frameworks allows your own solutions where you want to fit them. Looks like your react skills are moderate, as such you could put in a solution that doesn't use react. Ijs – GetSet Commented Aug 16, 2020 at 17:04
  • 1 @FujiRoyale Can we do this with any lodash function e.g. map to return the new modified array. – Ven Nilson Commented Aug 18, 2020 at 17:44
 |  Show 5 more ments

2 Answers 2

Reset to default 2 +150

Here is the revised code.

export const filterByNames = (data, inputValue) => {
  // Create a dynamic regex expression object with ignore case sensitivity
  const re = new RegExp(_.escapeRegExp(inputValue), "i");
  const clonedData = _.cloneDeep(data);
  const results = clonedData.filter((object) => {
    return object.list.filter((item) => {
      if (re.test(item.name)) {
        // Calculates the characters to highlight in text based on query
        const matches = match(item.name, inputValue);
        // Breaks the given text to parts based on matches.
        // After that create a new property named `parts` and assign an array to it.
        item["parts"] = parse(item.name, matches);
        return true;
      } else {
        return false;
      }
    }).length > 0 || re.test(object.name);
  });
  return results;
};

Forked link here. https://codesandbox.io/s/search-frontend-forked-e3z55

Here is the code having solved both

export const filterByNames = (data, inputValue) => {
  // Create a dynamic regex expression object with ignore case sensitivity
  const re = new RegExp(_.escapeRegExp(inputValue), "i");
  // since we cannot directly mutate the data object, why not copy it here ? (or if the data is bigger and copying is also not an option then consider using two arrays of data, one for the mutation and one default maybe)
  let data_ = JSON.parse(JSON.stringify(data));
  // filter and return the newer copy of the object.
  const results = data_.filter((object) => {
    // since we need the highlighting in both cases, on top level, or even in nested level so create separate function for that.
    let highLightEm = (list) => {
      return object.list.some((item) => {
        if (re.test(item.name)) {
          // Calculates the characters to highlight in text based on query
          const matches = match(item.name, inputValue);
          // Breaks the given text to parts based on matches.
          // After that create a new property named `parts` and assign an array to it.
          item["parts"] = parse(item.name, matches);
          return true;
        } else {
          return false;
        }
      });
    };

    if (re.test(object.name)) {
      // check for the highlighting in the inner name
      highLightEm(object);
      return true;
    } else {
      return highLightEm(object);
    }
  });
  return results;
};

https://codesandbox.io/s/search-frontend-forked-kxui9?file=/src/filter.js

本文标签: javascriptReactjs search filtration on the array of objectsStack Overflow