Sharon
Sharon

Reputation: 3909

In React Context, how can I use state variables in state functions?

I have a React Context which looks like this:

import React, { Component } from 'react'

const AlertsContext = React.createContext({
  categoryList: [],
  setCategoryList: () => {}
})

export class AlertsProvider extends Component {

  state = {
    categoryList: [],
    setCategoryList: categoryString => (
      this.categoryList.includes(categoryString)
        ? this.setState({ categoryList: this.categoryList.filter(value => value !== categoryString) })
        : this.setState({ categoryList: this.categoryList.concat([categoryString]) })
    )
  }

  render() {
    const { children } = this.props
    const {categoryList, setCategoryList } = this.state

    return (
      <AlertsContext.Provider value={{categoryList, setCategoryList}}>
        {children}
      </AlertsContext.Provider>
    )
  }
}

export const AlertsConsumer = AlertsContext.Consumer

So, categoryList is an array of strings, each representing a category. setCategoryList should take a string; if that string is already in the array, it removes it, and if it's not in the array it adds it.

In one of my components the user can select categories from a list of checkboxes. When a checkbox is clicked, the AlertsContext setCategoryList should be called with the value of the clicked box:

import React, { Component } from 'react'
import { AlertsConsumer } from '../../../context/alerts-context'


    class AlertFilters extends Component {
    
      constructor(props) {
        super(props)
        this.state = {
          categories: props.categories
        }
      }
    
      render() {
        const { categories } = this.state
    
        return (
          <AlertsConsumer>
            {({ categoryList, setCategoryList }) => (
              <>
                {
                categories.map(category => (
                  return (
                    <div key={category.id}>
                      <Checkbox id={category.id} value={category.value} onChange={e => setCategoryList(e.target.value)} checked={categoryList.includes(category.value)} />
                      <label htmlFor={category.id}>{category.value}</label>
                    </div>
                  )
                ))
                }
              </>
            )}
          </AlertsConsumer>
        )
      }
    }
    
    export default AlertFilters

This compiles ok, but when I run it and click a checkbox I get the following error:

alerts-context.jsx:77 Uncaught TypeError: Cannot read property 'includes' of undefined

This is in the line:

  this.categoryList.includes(categoryString)

in the Context Provider, suggesting that "this.categoryList" is undefined at this point.

I tried changing it to

this.state.categoryList.includes(categoryString)

but it said I had to use state destructuring, so I changed to:

setCategoryList: (categoryString) => {
      const { categoryList } = this.state
      categoryList.includes(categoryString)
        ? this.setState({ categoryList: categoryList.filter(value => value !== categoryString) })
        : this.setState({ categoryList: categoryList.concat([categoryString]) })
    }

which highlighted the ternary operator and gave the following lint error:

Expected an assignment or function call and instead saw an expression.

What am I doing wrong?

Upvotes: 0

Views: 420

Answers (1)

Murli Prajapati
Murli Prajapati

Reputation: 9713

Use if/else syntax to update the state.

setCategoryList: categoryString => {
  const { categoryList } = this.state;
  if (categoryList.includes(categoryString)) {
    this.setState({
      categoryList: categoryList.filter(value => value !== categoryString)
    });
  } else {
    this.setState({ categoryList: categoryList.concat([categoryString]) });
  }
};

Upvotes: 1

Related Questions