javascriptreactjs

Using array as `this.state` I'm getting map() is not a function errors


I don't know why but I have an "is not a function" error in my React class component. I want to increase the the count of a product in React with context.

I created context and used my context in main.jsx and gave the main component state and some functions. I can use states but I can't use functions in the context.

Main file - here's the state array:

state = [
    {prodoctName : "food" , count : 0, id:0},
    {prodoctName : "water" , count : 0, id:1},
    {prodoctName : "store" , count : 0, id:2},
    {prodoctName : "game" , count : 0, id:3},
    {prodoctName : "how" , count : 0, id:4}
];

Here's where I use the state to create the context:

increase = (id = 0)=>{
    let newState = [...this.state];
    newState[id].count += 1;
    this.setState(newState);
}

render() {
    return (
        <>
        <MyContext.Provider
            value={{
                increase : this.increase,
                reduce : this.reduce,
                sumCount : this.sumCount,
                state : this.state,
            }}>
        <Products></Products> 
        </MyContext.Provider>
        </>
    );
}

Products file (this is where I have the error):

class Products extends Component {
    static contextType = MyContext;
    render() {
        return (
            <>
                {this.context.state.map((p)=>{
                    return <Product key={p.id} ProductId={p.id}></Product>
                })}
            </>
        );
    }
}

Product file:

class Product extends Component{
    static contextType = MyContext;
    render() {
        return (
             <div>
                <span>{this.context.state[this.props.ProductId].prodoctName}</span>
                <span> {this.context.state[this.props.ProductId].count} </span>
                <button onClick={()=>{this.context.increase(this.props.ProductId)}}>+</button>
             </div>
        );
    }
}

Why am I getting that error?


Solution

  • In class-based components, your state object can't be an array object, it has to be a simple object. That's because when you call setState, React merges the previous state and the state object you give it together into a plain object on the state property. So although your code would work initially, the first time you call setState your state property becomes a non-Array object.

    Instead, make your state an object with a property that contains the array you want:

    state = {
        items: [
            // ...
        ]
    };
    

    ...and adjust the code to use this.state.items instead of this.state to access the array. For instance, in the main file code you've shown:

    increase = (id = 0) => {
        this.setState(({items}) => {            // ***
            const newItems = [...items];        // ***
            newItems[id].count += 1;            // ***
            return { items };                   // ***
        });                                     // ***
    };
    
    render() {
        return (
            <>
            <MyContext.Provider
                value={{
                    increase : this.increase,
                    reduce : this.reduce,
                    sumCount : this.sumCount,
                    state : this.state.items,   // ***
                }}>
            <Products></Products> 
            </MyContext.Provider>
            </>
        );
    }
    

    Aside from the change to keep the array in this.state.items instead of directly in this.state, note that in increase I updated the code to use the callback form of setState. You should do that whenever you're updating state based on existing state, because state updates can be asynchronous and queued up, so when you call setState the this.state value may be stale.

    (You might also consider changing the state property in your context to be items or something else that more clearly expresses what the contents of the array are.)


    Side note: class components are now a legacy API. I doubt it's going away any time soon, but you might consider switching to function components with hooks when you can. (Maybe you already know that, most of us have legacy code we have to maintain and can only evolve over time, but just in case...)