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?
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...)