Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to filter table in React

I have an array of objects stored in redux. I want to be able to filter that array based on user input. Should I create a state object that receives the array through props and modify that array, or is it bad practice to mix state and props? If it is alright to mix the two, should I set the state in componentWillReceiveProps?

like image 212
Juliuszc Avatar asked Feb 23 '16 16:02

Juliuszc


People also ask

How do you filter an array of objects in react?

To filter an array of objects in React:Call the filter() method on the array. On each iteration, check if a certain condition is met. The Array. filter methods returns an array with all elements that satisfy the condition.


1 Answers

Building state based on props can be somewhat complicated, which is acceptable, but you should consider all of your options.

The simplest to implement is to filter the props in your render method. If you have sufficiently small components which don't update for too many reasons, and especially if the number of elements in the list is low, this might be the preferred method:

class FilterList extends React.Component {
  render () {
    const { elements } = this.props;
    const { filterStr } = this.state;

    const filteredElements = elements
      .filter(e => e.includes(filterStr))
      .map(e => <li>{ e }</li>)

    return (
      <div>
        <input
          type="text"
          value={ filterStr }
          onChange={ e => this.setState({ filterStr: e.target.value }) } />
        <ul>
          { filteredElements }
        </ul>
      </div>
    );
  }
}

The next option is to do what you're describing, and derive a computed state based of the component's filter state and props passed to it. This is good when you have a complicated component which recieves many props and is rendered often. Here, you're caching the viewable elements and only filtering the list when it needs to be filtered.

class FilterList extends React.Component {
  constructor (props) {
    this.state = {
      viewableEls: props.elements
    }
  }

  componentWillReceiveProps (nextProps) {
    const { elements } = this.props;
    const { filterStr } = this.state;

    if (elements !== nextProps.elements) {
      this.setState({
        viewableEls: this.getViewableEls(nextProps.elements, filterStr)
      })
    }
  }

  getViewableEls (elements, filterStr) {
    return elements.filter(el => el.includes(filterStr))
  }

  handleFilterChange = e => {
    const { elements } = this.props;

    this.setState({
      filterStr: e.target.value,
      viewableEls: this.getViewableEls(elements, filterStr)
    })
  }
  render () {
    const { viewableEls } = this.state;

    return (
      <div>
        <input
          type="text"
          value={ filterStr }
          onChange={ this.handleFilterChange } />
        <ul>
          { viewableEls.map(e => <li key={ e }>{ e }</li>) }
        </ul>
      </div>
    );
  }
}

And finally, the redux 'way', which requires you to pass the action creator and filterStr as props to the component, probably passed in via connect somewhere else. The implementation below is using a stateless component since we're not keeping the fitlerStr in the component state at all.

const FilterTable = ({ elements, filterStr, changeFilterStr }) => {
  return (
    <div>
      <input
        type="text"
        value={ filterStr }
        onChange={ e => changeFilterStr(e.target.value) } />
      <ul>
        {
          elements
            .filter(e => e.includes(filterStr))
            .map(e => <li key={ e }>{ e }</li>)
        }
      </ul>
    </div>
  )
}
like image 53
Nathan Hagen Avatar answered Sep 23 '22 13:09

Nathan Hagen