Updating local and parent state causes unmounted component error - javascript

I have a table with buttons. I want to track the number of times each individual button is clicked along with the total number of times any button has been clicked. I can only get one-or-the-other to work.
I attempt to accomplish this by having a "global" state hook that is passed into each row of the table used for tracking total clicks. I also have a "local" state hook that is part of each component that makes up a row in the table for tracking individual clicks. I pass the "global" state from the parent to the children rows as a pair of callbacks (one for the getter, one for the setter).
Error Message:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in ButtonCell (created by clickableButtonCell)
in clickableButtonCell (created by Table)
Problematic Code:
export default function ButtonCell({
totalButtonClicks,
setTotalButtonClicks
}) {
// Having this local state variable causes the unmounted component error.
// Removing this state and its references will cause the error to go away.
// Or, removing the state variable passed into this component will cause
// the error to go away and the individualClicks state will work as intended.
const [individualClicks, setIndividualClicks] = useState(0);
const onClick = async () => {
await axios.get("www.github.com").then(() => {
setTotalButtonClicks(totalButtonClicks + 1);
setIndividualClicks(individualClicks + 1);
return null;
});
};
return (
<button type="button" onClick={onClick}>
Click Me! {individualClicks}
</button>
);
}
Code Sandbox: https://codesandbox.io/s/cranky-sun-wflot?file=/src/ButtonCell.js

Edit: https://codesandbox.io/s/hopeful-wing-7cwio?file=/src/ButtonCell.js
useMemo in the MyTable.js was causing rerender because of the props change (caused by setting state).

Related

React/Next-js : Getting TypeError: Cannot read properties of undefined (reading 'id'), but the object is clearly not empty?

I am having an issue with a Next-js React checkbox list snippet after extracting it into the sandbox.
whenever I clicked the checkbox, I get the error:
TypeError: Cannot read properties of undefined (reading 'id')
which originated from line 264:
setCheckedThread(prev => new Set(prev.add(pageData.currentThreads[index].id)));
but at the top of the index.js I have defined the static JSON
and in useEffect() I update the pageData state with:
setPageData({
currentPage: threadsDataJSON.threads.current_page,
currentThreads: threadsDataJSON.threads.data,
totalPages: totalPages,
totalThreads: threadsDataJSON.threads.total,
});
so why when I clicked the checkbox it throws the error?
my sandbox link: https://codesandbox.io/s/infallible-goldberg-vfu0ve?file=/pages/index.js
It looks like your useEffect on line 280 only triggers once you've checked a box (for some reason), so until you trigger that useEffect, pageData.currentThreads remains empty, which is where the error you're running into comes from.
I'd suggest moving all the state initialization from the useEffect into the useState call itself. E.g.
// Bad
const [something, setSomething] = useState(/* fake initial state */);
useEffect(() => {
setSomething(/* real initial state */)
}, []);
// Good
const [something, setSomething] = useState(/* real initial state */);
Here's a fork of your sandbox with this fix.
This is occurring because in Home you've created the handleOnChange function which is passed to the List component that is then passed to the memoized Item component. The Item component is kept the same across renders (and not rerendered) if the below function that you've written returns true:
function itemPropsAreEqual(prevItem, nextItem) {
return (
prevItem.index === nextItem.index &&
prevItem.thread === nextItem.thread &&
prevItem.checked === nextItem.checked
);
}
This means that the Item component holds the first initial version of handleOnChange function that was created when Home first rendered. This version of hanldeOnChange only knows about the initial state of pageData as it has a closure over the initial pageData state, which is not the most up-to-date state value. You can either not memoize your Item component, or you can change your itemPropsAreEqual so that Item is rerendered when your props.handleOnChange changes:
function itemPropsAreEqual(prevItem, nextItem) {
return (
prevItem.index === nextItem.index &&
prevItem.thread === nextItem.thread &&
prevItem.checked === nextItem.checked &&
prevItem.handleOnChange === nextItem.handleOnChange // also rerender if `handleOnChange` changes.
);
}
At this point you're checking every prop passed to Item in the comparison function, so you don't need it anymore and can just use React.memo(Item). However, either changing itemPropsAreEqual alone or removing itemPropsAreEqual from the React.memo() call now defeats the purpose of memoizing your Item component as handleOnChange gets recreated every time Home rerenders (ie: gets called). This means the above check with the new comparison function will always return false, causing Item to rerender each time the parent Home component rerenders. To manage that, you can memoize handleOnChange in the Home component by wrapping it in a useCallback() hook, and passing through the dependencies that it uses:
const handleOnChange = useCallback(
(iindex, id) => {
... your code in handleOnChange function ...
}
, [checkedState, pageData]); // dependencies for when a "new" version of `handleOnChange` should be created
This way, a new handleOnChange reference is only created when needed, causing your Item component to rerender to use the new up-to-date handleOnChange function. There is also the useEvent() hook which is an experimental API feature that you could look at using instead of useCallback() (that way Item doesn't need to rerender to deal with handleOnChange), but that isn't available yet as of writing this (you could use it as a custom hook for the time being though by creating a shim or using alternative solutions).
See working example here.

Purpose of React state

This might be a really stupid question but I am writing my first project in React and am struggling to understand the purpose of setState hooks.
As far as I understand, the setState hook is used to set current values used in a component that is scoped to that component only, and does not persist if a page is reloaded for example, the value is simply held in memory until it is destroyed.
My question is, what is the difference between using setState() to store values and just simply declaring a let variable and updating it the regular way? Both methods just seem to be holding a non-persisting value scoped to that component. What is the difference?
changes in the state automatically cause your app to re-render (in most cases), so typically you store data in a state that is being displayed and possibly changed throughout the app (a menu whose options can change based on previous selections, for example).
TL;DR, even though the answer's not very long:
setState or useState is the key for React to subscribe to your component's state and update your components when necessary. Using let variables for storing app state means React will never get to know about state change and won't rerender and update your components.
A short overview of React
The core principle of React is that your components, and consequentially your UI, are a function of your app's state. When your app's state changes, components "react" to this state change and get updated. Here's a simple example:
const CounterButton = () => {
// Create a state variable for counting number of clicks
const [count, setCount] = React.useState(0);
// Decide what the component looks like, as a function of this state
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
};
ReactDOM.render(<CounterButton />, document.querySelector('#root'));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
This is a component that just creates a button that shows how many times it has been clicked. Now, the component needs to store information about how many times it has been clicked - this means that the clicks count is a part of this component's "state". That's what we get from React.useState(0) - a state variable whose initial value is 0, and a function that allows us to change the value. Whenever you call setCount with some value, React gets to know that the CounterButton component's state has changed and thus the CounterButton component needs a rerender.
So in other words, React allows you to neatly and concisely define what internal state a component requires and what the component looks like as a function of this internal state (and external props). React does rest of the work - it manages the app's state, and whenever a piece of state changes anywhere in the app, React updates the components that depend on that. In other words, components "react" to data change.
To your question
If you use a simple let variable instead of React.useState in the above example, React will no longer get to know if the count variable has changed. useState is the key for React to "subscribe" to your component's state.
const CounterButton = () => {
// React will never get to know about this variable
let count = 0;
return (
<button onClick={() => count++}>
Count: {count}
</button>
);
};
In fact, for a functional component, let variables won't work in any case because while rendering a functional component, React internally runs the function. That would mean your let variable would be reset to its default value. The above reason is more relevant to class components. Using let variables to store component state is like hiding things from React - and that's not good because then there's no one else to rerender your component when component state changes :)
This part of the React docs is a bit relevant - it does not go into any details, though.
React re-renders the new / updated state on an event occurance storing value into a variable wont trigger re-render and data is passed on form parent component to child component through props and a change in state can be reflected among all the parts.
For example we need to print 100 elements on a page if an element is modified or updated in any way this triggers re-render but using var if the variable is modified or updated this won't cause re-render where in data wont be updated.

React "can't perform state update on unmounted component" with timeout

Questions about this warning have been asked and answered countless times on the Internet, yet - maybe actually because of that - I'm having difficulty finding a comment which touches on my own situation.
I'm working on an autosave feature, whereby when you start typing into the component's form it starts a timer. On completion, a) it dispatches an action (which is working fine) and b) it clears the timer state so that next time the user types it knows it can start a new one.
The issue comes when I unmount the component before the timer is complete: when it does expire I get the Can't perform a React state update on an unmounted component warning as I try to clear the timer state.
Now, just about all the solutions I've found online for this suggest I should create an isMounted state variable and check it before running the relevant setAutosave(null) state call. Except that - as far as I'm aware - the nature of Javascript timers means that the values available to the setTimeout callback (or Promise callback, for that matter) are those when the timer was started - when of course, the component was mounted.
Effectively, I'm stuck between a) the autosave feature requiring a state reset if the component is mounted, b) React demanding that the state reset cannot occur if the component isn't mounted, and c) the timer preventing any checking (that I can think of) of whether the component is or isn't mounted. Any ideas?
const { dispatch } = useContext(MyContext)
const [autosave, setAutosave] = useState(null)
const save = () => {
clearTimeout(autosave) // in case you manually submit the form
setAutosave(null)
dispatch({ type: "SAVE" }) // this line works fine
}
const onChange = () => {
if (!autosave) {
const timeoutId = setTimeout(save, 30000)
setAutosave(timeoutId)
}
}
<form onChange={onChange} onSubmit={save}>
...
I am not sure though, Try change your state using componentWillUnmount if your component is class-based component and useEffect functional components

React - updating state during render produces errors

I'm new to React and am trying to update the state of a parent component from the child everytime an onChange action happens. The onchange action comes from an input box that when letters are typed it updates the state of searchInputVal with the value of what has been typed. I have a parent <App/> component with the following properties and states here:
updateSampleFilteredState(filteredSamples) {
this.setState({
samples: filteredSamples
});
},
getInitialState () {
return {
samples:allSamples,
searchInputVal:""
}}
I pass the properties and states down to a child component here:
updateNewSampleState(filteredSamples){
return (
this.props.updateSampleFilteredState(filteredSamples)
)
}
render() {
const filteredSamples = this.props.samples.filter(sample => {
return sample.sampleFamily.toLowerCase().indexOf(this.props.searchInputVal.toLowerCase()) !== -1;
});
this.updateNewSampleState(filteredSamples);
return <div className="samples-container-inner-styling">
{
filteredSamples.map((sample) => {
return (...
Before I added the line this.updateNewSampleState(filteredSamples); the child component would render out the filtering just fine but obviously not update the state of sample with the new filtered state. When I the line this.updateNewSampleState(filteredSamples); to execute the function in the component to set the new state I get a list of re-occuring errors that eventually make my app crash. The errors say something about an anti pattern. I'm not sure how else to update the state?
You should't be updating the state from the render function, and you are facing the reason why that's a bad way to do things. Every time you call the setState the component re-renders, so if you call it inside the render function it will be called again and so on... You should ask yourself why are you calling that function there. I guess you could just do it in the onChange function you are using for the input.
As already mentioned by #César, setting the state in the renderer doesn't make sense, since setting the state triggers a rerender of the component, so you basically get something like an infinite render loop.
Given that you are computing filteredSamples only from the props, you could compute that state in the constructor:
The constructor is the right place to initialize state.
However, note the following when deriving state from props in the constructor:
It's okay to initialize state based on props if you know what you're doing. [...]
Beware of this pattern, as it effectively "forks" the props and can lead to bugs. Instead of syncing props to state, you often want to lift the state up.
If you "fork" props by using them for state, you might also want to implement componentWillReceiveProps(nextProps) to keep the state up-to-date with them. But lifting state up is often easier and less bug-prone.

Multiple React + Redux functions in HTML event

The general behavior that I want is on an event, update a property in the Redux state tree, and then access the updated property, right after the state tree has been updated.
But below, what happens is the synchronousReducer function makes it's call, and then the relevantProperty is undefined. Everything is re-rendered, and only then is relevantProperty updated.
How should I accomplish this goal the Redux way? No anti-patterns please.
const { relevantProperty } = props;
...
<button onClick={() =>
synchronousReducer(); // This updates relevantProperty in the Redux state tree.
console.log(relevantProperty); // undefined
loadEvent(relevantProperty);
} />

Categories

Resources