5

In my React app I am trying to catch some event when checkbox is clicked in order to proceed some state filtering, and show only items are needed. event is coming from child's checkbox with some name. There is 3 checkboxes, so I need to know the name which one is clicked.

some of them

<input 
  type="checkbox" 
  name="name1"
  onClick={filterHandler} 
/>Something</div>

state is something like

state = {
  items = [
   { 
    name: "name1",
    useful: true
   },{
    name: "name2",
    useful: false
   }],

   filteredItems: []  
} 

here is the handler

 filterHandler = (evt) => {

    let checked = evt.target.checked;
    let name = evt.target.name;

    let filteredItems = this.state.items.filter(item => {
     return item[name] === checked; // filtered [{...},{...}...]
    });

    // when filtered - push to state and show all from here in <ItemsList />
    this.setState({ filteredItems })
 }

ItemsList component for "showing" is like so:

<ItemsList 
  items={this.state.filteredItems.length === 0 ? this.state.items : this.state.filteredItems}
          /> 

When checkbox is one and only - its working fine. But I have three of those boxes -- complications appears:

1) when checking next box I operate with original un-filtered items array - so for this purpose I need already filtered array. 2) I cant use my filteredItems array reviously filtered, because when unchecking box that array gets empty.To have 3rd "temporary" array seems a little weird.

I tried this way, pretty similar also

this.setState({
  filteredItems: this.state.items.filter(item => {
    if (item[name] === checked) {
      console.log('catch');
      return Object.assign({}, item)
    } else {
      console.log('no hits') 
    }
 })

and this is almost good, but when uncheck filteredItems are filled with opposite values ((

I feel there is a better approach, please suggest.

3 Answers 3

7

You can do it by storing the checked state of the filters.

For example, your state can look something like:

state = {
    items: [
    {
        name: "name1",
        useful: true
    },
    {
        name: "name2",
        useful: false
    }
    ],
    filters: { 'name1': false, 'name2': false}, // key value pair name:checked
    filteredItems: []
};

Then your click/change handler would update both the filtered list and the actual filters state (what's checked).

Here's an example of that:
(Update: Heavily commented as per request in comments)

  // Using syntax: someFunc = (params) => { ... }
  // To avoid having to bind(this) in constructor
  onChange = evt => {
    // const is like var and let but doesn't change
    // We need to capture anything dependent on
    //  evt.target in synchronous code, and
    //  and setState below is asynchronous
    const name = evt.target.name;
    const checked = evt.target.checked;

    // Passing function instead of object to setState
    // This is the recommended way if
    //    new state depends on existing state
    this.setState(prevState => {
      // We create a new object for filters
      const filters = {
        //  We add all existing filters
        //  This adds them with their existing values
        ...prevState.filters,
        // This is like:
        //    filters[name] = checked
        // which just overrides the value of
        //    the prop that has the name of checkbox
        [name]: checked
      };

      // Object.keys() will return ["name1", "name2"]
      // But make sure that "filters" in
      //    our initial state has all values
      const activeFilterNames = Object.keys(filters).filter(
        // We then filter this list to only the ones that
        //    have their value set to true
        //    (meaning: checked)
        // We set this in the `const filter =` part above
        filterName => filters[filterName]
      );

      // We get the full list of items
      // (Make sure it's set in initial state)
      // Then we filter it to match only checked
      const filteredItems = prevState.items.filter(item =>
        // For each item, we loop over
        //     all checked filters
        // some() means: return true if any of the
        //    array elements in `activeFilterNames`
        //    matches the condition
        activeFilterNames.some(
          // The condition is simply the filter name is
          //    the same as the item name
          activeFilterName => activeFilterName === item.name
        )
      );

      // The object returned from setState function
      //    is what we want to change in the state
      return {
        // this is the same as
        // { filter: filters,
        //    filteredItems: filteredItems }
        // Just taking advantage of how const names
        //    are the same as prop names
        filters,
        filteredItems
      };
    });
  };

I'm using latest features of JS / Babel in here but hopefully the code is clear. I also had to use evt.target before entering setState()

Here's a full component example:

import * as React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class App extends React.Component {
  state = {
    items: [
      {
        name: "name1",
        useful: true
      },
      {
        name: "name2",
        useful: false
      }
    ],
    filters: { name1: false, name2: false },
    filteredItems: []
  };

  // Using syntax: someFunc = (params) => { ... }
  // To avoid having to bind(this) in constructor
  onChange = evt => {
    // const is like var and let but doesn't change
    // We need to capture anything dependent on
    //  evt.target in synchronous code, and
    //  and setState below is asynchronous
    const name = evt.target.name;
    const checked = evt.target.checked;

    // Passing function instead of object to setState
    // This is the recommended way if
    //    new state depends on existing state
    this.setState(prevState => {
      // We create a new object for filters
      const filters = {
        //  We add all existing filters
        //  This adds them with their existing values
        ...prevState.filters,
        // This is like:
        //    filters[name] = checked
        // which just overrides the value of
        //    the prop that has the name of checkbox
        [name]: checked
      };

      // Object.keys() will return ["name1", "name2"]
      // But make sure that "filters" in
      //    our initial state has all values
      const activeFilterNames = Object.keys(filters).filter(
        // We then filter this list to only the ones that
        //    have their value set to true
        //    (meaning: checked)
        // We set this in the `const filter =` part above
        filterName => filters[filterName]
      );

      // We get the full list of items
      // (Make sure it's set in initial state)
      // Then we filter it to match only checked
      const filteredItems = prevState.items.filter(item =>
        // For each item, we loop over
        //     all checked filters
        // some() means: return true if any of the
        //    array elements in `activeFilterNames`
        //    matches the condition
        activeFilterNames.some(
          // The condition is simply the filter name is
          //    the same as the item name
          activeFilterName => activeFilterName === item.name
        )
      );

      // The object returned from setState function
      //    is what we want to change in the state
      return {
        // this is the same as
        // { filter: filters,
        //    filteredItems: filteredItems }
        // Just taking advantage of how const names
        //    are the same as prop names
        filters,
        filteredItems
      };
    });
  };

  renderCheckboxes() {
    return Object.keys(this.state.filters).map((name, index) => {
      return (
        <label key={index}>
          <input
            onChange={this.onChange}
            type="checkbox"
            checked={this.state.filters[name]}
            name={name}
          />
          {name}
        </label>
      );
    });
  }

  render() {
    const items = this.state.filteredItems.length
      ? this.state.filteredItems
      : this.state.items;
    return (
      <div>
        <div>{this.renderCheckboxes()}</div>
        <ul>
          {items.map(item => (
            <li key={item.name}>
              {item.name}
              {item.useful && " (useful)"}
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

You can try it live from here:
https://codesandbox.io/embed/6z8754nq1n

You can of course create different variations of this as you wish. For example you might choose to move the filtering to the render function instead of the change event, or store how you store the selected filters, etc, or just use it as is. Whatever suits you best :)

Sign up to request clarification or add additional context in comments.

4 Comments

I guess you make template for all checkboxes in renderCheckboxes() and define them in state.filters list. If so, its very useful, no need for many same code pieces (as I did for now). Hopefully, this approach will also support some styling.
Yes. I do, and yes it supports styling for sure. What I did with rendering checkboxes optional though as this would still work with declaring them statically as long as you se the onClick/onChange handler. The implementation of the handler is the most important part I think.
would you kindly comment this pieces in onChange(): activeFilterNames and filteredItems , maybe by commenting them a little. I see them working in live example, but my filteredItems are empty when click, with state.filters full of checked names. I guess its a deep night already, sorry.
No problem. I added heavy comments as requested. Please also note that the implementation assumes you initialised the state as shown in the example (so, your filters are set already). If this is different from your case (for example in your case your filters are extracted from the date itself), you'll also need to change the parts that use state.filters.
3

Try to work with the name & append the toggled one to state.

  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }
      handleInputChange(event) {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;

        this.setState({
          [name]: value
        });
      }

              <input
                name="isGoing"
                type="checkbox"
                checked={this.state.isGoing}
                onChange={this.handleInputChange} />

Comments

1

You could introduce an additional piece of state checked in which you store your checked values, and use those for your filtering instead.

Example (CodeSandbox)

class App extends React.Component {
  state = {
    items: [
      {
        name: "name1",
        useful: true
      },
      {
        name: "name2",
        useful: false
      }
    ],
    checked: {}
  };

  filterHandler = event => {
    const { name } = event.target;
    this.setState(previousState => {
      const checked = { ...previousState.checked };
      checked[name] = !checked[name];
      return { checked };
    });
  };

  render() {
    const { items, checked } = this.state;

    let filteredItems = items.filter(item => checked[item.name]);
    filteredItems = filteredItems.length === 0 ? items : filteredItems;

    return (
      <div>
        <div>
          <input
            type="checkbox"
            name="name1"
            value={checked["name1"]}
            onClick={this.filterHandler}
          />Name 1
          <input
            type="checkbox"
            name="name2"
            value={checked["name2"]}
            onClick={this.filterHandler}
          />Name 2
        </div>
        <div>
          {filteredItems.map(item => <div key={item.name}> {item.name} </div>)}
        </div>
      </div>
    );
  }
}

4 Comments

Thanks a lot. One little thing, when I check|uncheck, then warning fires in console: Warning: A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). For now I can't figure out how to prevent this.
@d2048 Ah, you're right. I updated the example to use value instead of checked on the inputs, and now the warning is gone.
could you , please, explain purpose of using value={checked["name1"]} on inputs. I realize, that they are needed to pointing to box clicked, but when I commented it out (when studying code) , nothing happened, all is working fine. Thanks.
@d2048 In this particular case it would work fine to comment that out and let the inputs control their own values, but since the values are stored in state, we might just as well control the values of the inputs as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.