Reactjs -- cannot setState within button listener - javascript

My application has two pages: Main and CreateProfile. React-router is used for navigation.
User Flow
Start on Main
Navigate to CreateProfile with hashHistory.push('/register');
Navigate back to Main with hashHistory.push('/');
After rendering Main, it appears that React-Router automatically POPS Main, but
Main doesn't unmount
Afterward, I can successfully update state within Main's componentWillReceiveProps(). However, when the user clicks a button within Main and the button's listener invokes this.setState({ ... }), I get the following error.
Warning: setState(...): Can only update a mounted or mounting component.
It appears that the button listener has an outdated reference to this.state.
How can I resolve this?
Edit: This might not be a react-router specific problem. It could be that listeners are not bound correctly, but I cannot confirm.

This might be caused because you're calling this.setState({..}) inside the constructor. Instead, try to call it inside componentWillMount or componentDidMount ;)
EDIT: React router does not unmount the component, but it does pass the new props. These can be captured using componentWillReceiveProps(nextProps)

The issue was that, within render(), I was invoking arrow-functions. Consequently, they had an outdated this bound to them. The below code works.
_renderListItem() {
return <ListItem
onTouchTap={
() => {
this.setState({foo: 'bar'})
}
}
primaryText={'Test'}
/>
}
render() {
return (
<List>
{this._renderListItem()}
</List>
)
}
However, I will get the error (below), if I were to change from
_renderListItem() { ... }
to the arrow-style
_renderListItem = () => { ... }
Warning: setState(...): Can only update a mounted or mounting
component.

Related

componentDidUpdate not firing after the first time the component is mounted (React)

I have a function that switches windows (components) on a single page by clicking a button.
The function that swaps windows:
getProfileContent = () => {
var html = [];
if (this.state.content === "picks") {
html.push(<Picks picks={this.state.picks} deletePick={this.deletePick} />);
}
if (this.state.content === "api") {
html.push(<Admin admin="admin" />);
}
if (this.state.content === 'settings') {
html.push(<Settings />);
}
return html;
};
The content defaults to "picks" when the parent component initially loads and the first time the "Picks" component loads everything works fine because the componentDidUpdate update function below is triggered:
"Picks" component update function:
componentDidUpdate(prevProps, prevState) {
console.log('here')
if (Object.keys(prevProps.picks).length !== Object.keys(this.props.picks).length) {
this.sortPicks();
}
}
However, after swapping windows via getProfileContent and coming back to the "Picks" component the componentDidUpdate function is not triggered. I have also tried adding a different "key" value to the Picks component in hopes the new prop would trigger the componentDidUpdate, but no luck. I have the console log outside if the condition so I know componentDidUpdate isn't being called regardless of the condition. Any help is appreciated, thanks!
This question has been solved with help from #lanxion. Basically, the first time the component mounts the componentDidUpdate function is called, but only because of the parent component updating and passing in new props. The second time the component is mounted the parent already has the correct props, thus only componentDidMount is called and not componentDidUpdate. Placing the code in componentDidMount and componentDidUpdate (with conditionals) solved my issue,
It is possible that the props values are not changing, thus the same props values are being passed down and thus have the same length. Quite possibly it IS reaching the componentDidUpdate() hook, but the condition returns false and thus you don't go into the sortPicks() function. Hard to say without knowing the rest of the code.

React component render twice using useState

I'm having a really hard time to figure out what's happening when there is nothing being used to trigger re-render the component.
Events.js Component renders twice when I remove the useState() from the Event.js it renders once, but I need to keep it. when I use useEffect() inside Event components, renders fourth time.
I just kept the dummy data to give you to fill the emptiness and tried to remove React.memo, nothing happens. the problem is with the Event.js component I believe. I'm also using the Context API, but forth time rendering is too much.
useEffect inside App.js is getting some value from the localStorage, I can't access that direct 'cause the value is undefined by default
sandbox code here: https://codesandbox.io/s/event-manager-reactjs-nbz8z?file=/src/Pages/Events/Events.js
The Events.js file is located on /Pages/Events/Events.js
example code is below
Event.js ( child component )
function Events() {
// Sate Managing
const [allEvents, setAllEvents] = React.useState(null);
console.log('Rendering EventsJs', allEvents);
React.useEffect(() => {
setAllEvents(['apple', 'banana']);
}, []);
return (
<div className="events">
{ console.log('Event Rendered.js =>') }
</div>
)
}
export default React.memo(Events, (prevProps, nextProps) => {
return true;
} );
App.js ( parent component )
import { BrowserRouter, Route, Redirect } from 'react-router-dom';
function App() {
const [userId, setUserId] = React.useState(null);
React.useEffect(() => {
setUserId(1);
}, []);
// Login
return (
<BrowserRouter>
<Navigation />
<Route path='/events' component={Events} />
{console.log('App Rendered')}
</BrowserRouter>
);
}
export default App;
Error:
Your app is working fine. It is rendering as it should. As we know:
A React component re-renders whenever its props or state change.
And react component lifecycle order is:
Initial props/state --> render --> DOM update --> mounted
props/state changed --> render --> DOM update --> updated ... so on
In the example below, it is rendering 2 times and that's correct:
First one (first console.log) is due to initial render with state as []
Second one (second console.log) is due to state change (caused by useEffect) to ['apple', 'banana']
function Events() {
const [allEvents, setAllEvents] = React.useState([]);
console.log('Event Rendered', allEvents);
useEffect(() => {
setAllEvents(['apple', 'banana']);
}, []);
return <>Events</>;
}
About using React.memo:
React.memo only checks for props changes. If your function component wrapped in React.memo has a useState or useContext Hook in its implementation, it will still rerender when state or context change.
You can not skip re-render using React.memo due to change in state. You can only optimize to skip re-rendering caused by change in props.
But in the example above, you don't have props passed from the parent component, the only props passed to Events are those passed by react-router i.e. route props. So, there is no need to use React.memo.
Here is sandbox, check the console.logs. You will see only 3 logs: "App render", "Event render with initial state", "Event render with new state".
EDIT:
If we remove StrictMode from index.html, and add below console.logs in components:
App.js --> console.log('App rendered')
Evenets.js --> console.log('Event rendered', allEvents, isLoading) // (allEvents and isLoading are state variables here)
And go to http://localhost:3000, we see 1 log:
App Rendered
Now click on "Events", we see 3 logs:
1: Event Rendered, [], true
2: Event Rendered, [{}, ... 54 items], true
3: Event Rendered, [{}, ... 54 items], false
which is correct behavior (refer lifecycles order written above):
1st log: render with initial state ([], true)
2nd log: render with new allEvents (54 items) and old isLoading (true)
3rd log: render with old allEvents (54 items) and new isLoading (false)
Below are the right questions to ask now:
Question1:
Why 2nd and 3rd render (log) are separate, should not they be batched (merged) and applied together as they are written in the same function?
fetch('url').then(() => {
// ... code here
setAllEvents([...events])
setLoading(false)
})
Answer:
No, they will not be batched in above code. As explained by Dan Abramov:
This is implementation detail and may change in future versions.
In current release, they will be batched together if you are inside a React event handler. React batches all setStates done during a React event handler, and applies them just before exiting its own browser event handler.
With current version, several setStates outside of event handlers (e.g. in network responses) will not be batched. So you would get two re-renders in that case.
There exists a temporary API to force batching. If you write ReactDOM.unstable_batchedUpdates(() => { this.fn1(); }); then both calls will be batched. But we expect to remove this API in the future and instead batch everything by default.
So, you can write (inside fetch's then), if you want, it will save 1 render:
ReactDOM.unstable_batchedUpdates(() => {
setAllEvents([...events])
setLoading(false)
})
Question2:
What's React event handler in above quote?
Answer: foo in example below. These 2 set states will be batched.
const foo = () => {
setAllEvents([
{ _id: '5ede5af03915bc469a9d598e', title: 'jfklsd', },
])
setLoading(false)
}
<button onClick={foo}>CLICK</button>
Question3:
Does it update HTML DOM as many times as it renders (prints console.log)?
Answer: No. React compares calculated virtual DOMs before updating real DOM, so only those changes are applied to real DOM which are required to update the UI.
Question4:
Why was rendering doubled when we use StrictMode?
Answer: Yes, StrictMode will intentionally double invoke "render" and some other lifecycle methods to detect side-effects. Strict mode checks are run in development mode only; they do not impact the production build.
Well actually this is caused by your usage of React.memo, its second parameter is called areEqual, and you pass in () => false, so you are basically telling React that the props are always changing. Therefore whenever App rerenders, Events rerenders too.
You should let React.memo check for prop changes. By passing () => false you are actually telling that its props always change (they are never equal).
export default React.memo(Events);
Here's a working example.

React component setState() and meanwhile the parent component rerender itself

I built a React page like this:
.
The switcher is binded to a callback function from the parent componentA and componentA get this function from top-level page. The callback updates top-level page's state. So when the switcher clicked, then top-level page will rerender the componentA.
The switcher I'm using is a component from React-Switch library: https://www.npmjs.com/package/react-switch
Then sometimes, when I click the switcher, there will be a warning:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the ReactSwitch component.
Here is some code snippet.
In top-level page:
// In the top-level page, there is a state to decide whether or not to apply some data filter
onSwitcherClicked(event) {
this.setState({
applyFilter: event,
});
render() {
const filter = this.state.applyFilter ? '{paths:["/some_filters"],}' : '{paths:["/none"],}';
return (
<div>
<ComponentA
filter = {filter}
applyFilter = {this.applyFilter}
callBack = {this.onSwitcherClicked}
/>
</div>
);
In Component A
// Component A
componentWillMount() {
// Send some API request according to this.props.filter and load the data
// So every time the switcher clicked, the page will update the filter and pass some new props to componentA,
// then ComponentA will remount it self
}
render() {
return (
<div>
<DataViewer>
{/*A component to display the data*/}
</DataViewer>
<Switch onChange={this.props.callBack}
checked={this.props.applyFilter}/>
</div>
)
}
Here is the error message"
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the ReactSwitch component.
printWarning warning.js:33
warning warning.js:57
React 3
getInternalInstanceReadyForUpdate
enqueueSetState
setState
$onDragStop react-switch.dev.js:252
$onMouseUp react-switch.dev.js:277
I also copied the code in react-switch.dev.js:252 and react-switch.dev.js:277:
// react-switch.dev.js:252
252 this.setState({
253 $isDragging: false,
254 $hasOutline: false
255 });
256 this.$lastDragAt = Date.now();
// switch.dev.js:277
276 ReactSwitch.prototype.$onMouseUp = function $onMouseUp(event) {
277 this.$onDragStop(event);
278 window.removeEventListener("mousemove", this.$onMouseMove);
279 window.removeEventListener("mouseup", this.$onMouseUp);
280 };
I guess it's because when I click the switcher, it will reset the its own status. Meanwhile, the parent component's state is changed too. So when the switcher calls setState(), itself has been unmounted. Am I correct? Is there some approach to fix it?
Thank you!
Too long for a comment, but your component structure should look very similar to this kind of thing, in which case you wouldn't be getting that warning. I have a feeling you might be trying to duplicate the source of truth for state instead of just letting it flow down.
const ParentComponent = () => {
const [isClicked,setIsClicked] = useState(false);
return (
...
<Switcher selected={isClicked} onClick={() => setIsClicked(!isClicked)}/>
)
}

Child component constructor called multiple times

I have a parent component which is a flat list which contains a header HeaderComponent. This HeaderComponent is a custom component that I have created which contains 2 child components of its own. Whenever i refresh the list, I am passing a boolean to the HeaderComponent as props which get passed onto its own children, I am doing this so I can check if each component needs to fetch new data or not. The problem is that whenever the parent refreshes and sets a new state the constructors of the child components get called everytime. Shouldn't the constructor be called only the first time the parent initializes and then all further calls involve calling the shouldComponentUpdate method of the children in order to see if it needs an update or not.
Parent component
_renderHeader = () => {
return <HeaderComponent Items={this.state.Data} refresh={this.state.refresh}/>;
};
render() {
console.log("TAG_RENDER render called " + this.state.refresh);
return (
<FlatList
refreshing={this.state.refresh}
onRefresh={() => {
console.log("onRefresh");
this.setState({
refresh: true
}, () => {
this._fetchData();
});
}}
......
ListHeaderComponent={() => this._renderHeader()}
.......
/>
);
}
Header Component
export default class HeaderComponent extends React.Component {
constructor(props) {
super(props);
console.debug("HeaderComponent");
}
render() {
return (
<MainHeader Items={this.props.Items}/>
<SubHeader refresh={this.props.refresh}/>
);
}
}
The constructor of MainHeader and Subheader gets called whenever the parent component refreshes. Does this mean that it is creating new child components each time it refreshes because I can see the render of the children also being called multiple times.
Control your index.js file. If you see <React.StrictMode>, you should change to <>. This is solved my problem.
It should be like:
ReactDOM.render(
<>
<App/>
</>,
document.getElementById('root')
);
As correctly stated in the one of the answers , removing the strict mode fixes the issue. Coming to why it does that, its because the strict mode intentionally calls the 'render' method twice in order to detect potential problems.
React works in two phases:render and commit. Render phase checks and determines the new changes to be applied. And commit phase applies it.
Render phase lifecycle includes methods like : constructor, UNSAFE_componentWillMount,UNSAFE_componentWillReceiveProps, ...,render and few more.
The render phase is time consuming and is often broken into pieces to free up the browser. Render phase might be called multiple times before the commit phase(usually very fast).
Since the render phase methods are called more than once, its important that none of those method have any problems or side effects.
Thus just in order to highlight the possible side effects to make them easy to spot, react explicitly double invoke the render phase methods.
You can read more about this on :https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects :)
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate
methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
https://reactjs.org/docs/strict-mode.html
As stated in the site,
Note:
This only applies to development mode. Lifecycles will not be double-invoked in production mode.

componentDidMount called BEFORE ref callback

Problem
I'm setting a react ref using an inline function definition
render = () => {
return (
<div className="drawer" ref={drawer => this.drawerRef = drawer}>
then in componentDidMount the DOM reference is not set
componentDidMount = () => {
// this.drawerRef is not defined
My understanding is the ref callback should be run during mount, however adding console.log statements reveals componentDidMount is called before the ref callback function.
Other code samples I've looked at for example this discussion on github indicate the same assumption, componentDidMount should be called after any ref callbacks defined in render, it's even stated in the conversation
So componentDidMount is fired off after all the ref callbacks have
been executed?
Yes.
I'm using react 15.4.1
Something else I've tried
To verify the ref function was being called, I tried defining it on the class as such
setDrawerRef = (drawer) => {
this.drawerRef = drawer;
}
then in render
<div className="drawer" ref={this.setDrawerRef}>
Console logging in this case reveals the callback is indeed being called after componentDidMount
Short answer:
React guarantees that refs are set before componentDidMount or componentDidUpdate hooks. But only for children that actually got rendered.
componentDidMount() {
// can use any refs here
}
componentDidUpdate() {
// can use any refs here
}
render() {
// as long as those refs were rendered!
return <div ref={/* ... */} />;
}
Note this doesn’t mean “React always sets all refs before these hooks run”.
Let’s look at some examples where the refs don’t get set.
Refs don’t get set for elements that weren’t rendered
React will only call ref callbacks for elements that you actually returned from render.
This means that if your code looks like
render() {
if (this.state.isLoading) {
return <h1>Loading</h1>;
}
return <div ref={this._setRef} />;
}
and initially this.state.isLoading is true, you should not expect this._setRef to be called before componentDidMount.
This should make sense: if your first render returned <h1>Loading</h1>, there's no possible way for React to know that under some other condition it returns something else that needs a ref to be attached. There is also nothing to set the ref to: the <div> element was not created because the render() method said it shouldn’t be rendered.
So with this example, only componentDidMount will fire. However, when this.state.loading changes to false, you will see this._setRef attached first, and then componentDidUpdate will fire.
Watch out for other components
Note that if you pass children with refs down to other components there is a chance they’re doing something that prevents rendering (and causes the issue).
For example, this:
<MyPanel>
<div ref={this.setRef} />
</MyPanel>
wouldn't work if MyPanel did not include props.children in its output:
function MyPanel(props) {
// ignore props.children
return <h1>Oops, no refs for you today!</h1>;
}
Again, it’s not a bug: there would be nothing for React to set the ref to because the DOM element was not created.
Refs don’t get set before lifecycles if they’re passed to a nested ReactDOM.render()
Similar to the previous section, if you pass a child with a ref to another component, it’s possible that this component may do something that prevents attaching the ref in time.
For example, maybe it’s not returning the child from render(), and instead is calling ReactDOM.render() in a lifecycle hook. You can find an example of this here. In that example, we render:
<MyModal>
<div ref={this.setRef} />
</MyModal>
But MyModal performs a ReactDOM.render() call in its componentDidUpdate lifecycle method:
componentDidUpdate() {
ReactDOM.render(this.props.children, this.targetEl);
}
render() {
return null;
}
Since React 16, such top-level render calls during a lifecycle will be delayed until lifecycles have run for the whole tree. This would explain why you’re not seeing the refs attached in time.
The solution to this problem is to use
portals instead of nested ReactDOM.render calls:
render() {
return ReactDOM.createPortal(this.props.children, this.targetEl);
}
This way our <div> with a ref is actually included in the render output.
So if you encounter this issue, you need to verify there’s nothing between your component and the ref that might delay rendering children.
Don't use setState to store refs
Make sure you are not using setState to store the ref in ref callback, as it's asynchronous and before it's "finished", componentDidMount will be executed first.
Still an Issue?
If none of the tips above help, file an issue in React and we will take a look.
A different observation of the problem.
I've realised that the issue only occurred while in development mode.
After more investigation, I found that disabling react-hot-loader in my Webpack config prevents this problem.
I am using
"react-hot-loader": "3.1.3"
"webpack": "4.10.2",
And it's an electron app.
My partial Webpack development config
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
module.exports = merge(baseConfig, {
entry: [
// REMOVED THIS -> 'react-hot-loader/patch',
`webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
'#babel/polyfill',
'./app/index'
],
...
})
It became suspicious when I saw that using inline function in render () was working, but using a bound method was crashing.
Works in any case
class MyComponent {
render () {
return (
<input ref={(el) => {this.inputField = el}}/>
)
}
}
Crash with react-hot-loader (ref is undefined in componentDidMount)
class MyComponent {
constructor (props) {
super(props)
this.inputRef = this.inputRef.bind(this)
}
inputRef (input) {
this.inputField = input
}
render () {
return (
<input ref={this.inputRef}/>
)
}
}
To be honest, hot reload has often been problematic to get "right". With dev tools updating fast, every project has a different config.
Maybe my particular config could be fixed. I'll let you know here if that's the case.
The issue can also arise when you try to use a ref of a unmounted component like using a ref in setinterval and do not clear set interval during component unmount.
componentDidMount(){
interval_holder = setInterval(() => {
this.myref = "something";//accessing ref of a component
}, 2000);
}
always clear interval like for example,
componentWillUnmount(){
clearInterval(interval_holder)
}

Categories