Technical question here. I created a context provider to handle the state of some variables and to create functions to update these variables from other components. It is of my understanding that StricMode calls setStates twice, however, how are we supposed to handle pushing/popping elements from data arrays within states without duplicating these actions.
This is my context.jsx:
export const BrollProvider = ({ children }) => {
//These are the states in question
const [bRollItems, setbRollItems] = useState([]);
const [timelineData, setTimelineData] = useState([
{
id: "0",
actions: []
},
]);
//This is the function that upon being called updates the states
const addBrollItemAndAction = (event, currentFrame) => {
//Some irrelevant variables
event.preventDefault();
const bRollItemId = event.dataTransfer.getData('bRollItemId');
const newAction = {
id: bRollItemId,
start: currentFrame / 30,
end: (currentFrame / 30) + 3,
isSelected: true,
effectId: ''
};
//Next we have the setStates, regardless of the logic within them, they always get called twice
//First setState, doesn't matter if it's called twice since it works on the same object everytime (id === id)
setbRollItems((prevItems) =>
prevItems.map((item) =>
item.id === bRollItemId ?
{ ...item,
addedToTimeline: true,
startFrame: currentFrame, durationInFrames: 90,
width:'85%',
transform: 'translate(32px, 37px)'
} : item
)
);
//This setState adds data to the existing timelineData array, so everytime we call it, 2 identical objects are .pushed to the data array.
setTimelineData((prevTimelineData) => {
const updatedData = [...prevTimelineData];
const lastRow = updatedData[updatedData.length - 1];
if (lastRow && lastRow.actions.length === 0) {
lastRow.actions.push(newAction);
} else {
const newRow = {
id: (parseInt(lastRow.id) + 1).toString(),
actions: [newAction]
};
updatedData.push(newRow);
}
return updatedData;
});
};
return (
<BrollContext.Provider value={{ bRollItems, setbRollItems, timelineData, setTimelineData, addBrollItemAndAction }}>
{children}
</BrollContext.Provider>
);
};
Now the addBrollItemAndAction function is called from another component (simplified for clarity):
const MyComponent = ({ propss }) => {
const { bRollItems, setbRollItems, timelineData, setTimelineData, addBrollItemAndAction } = useMyCustomContextHook();
const currentFrame = someIntValue
const handleBrollDrop = (event) => {
addBrollItemAndAction(event, currentFrame)
}
return(
<div onDrag={handleBrollDrop}>
<MyOtherComponent/>
</div>
)
}
When I disable StrictMode the issue is pretty much fixed, but not the underlying cause. With that being said, what is the best practice to modify arrays of data in developer mode? Specifically with .push() if you don’t want the same variable being pushed twice to the same state. And how come updating an array with .push() may cause bugs in the future? Seems pretty straightforward to me. Are we supposed to do workarounds or is this a measure taken by strictMode so we don’t use useState for what I’m intending to use it for?
2
Answers
What ended up fixing the issue was creating a deep copy of the state object array so instead of:
This still holds a reference to the original state which causes direct mutations which is a no-no in React
The right way to clone the state is:
If I understand correctly, this creates a whole different copy of the state and doesnt affect it directly.
First, It’s really not a good idea to put
setState
in the root of a component, even if it’s a provider. What should be done is to put theuseState
inThis will ensure that the
setStatae
will run once on the componentmount
phase (initialization).for better information you need to check React Lifecycle Methods where it explains how the component is mounted and unmounted. However, this article explains the states of the component on a
class component
but you can easily search for the equivalente methods for functional components.If you could not find these methods, just let me know.