Proper use of React getDerivedStateFromProps - reactjs

In this example
https://codepen.io/ismail-codar/pen/QrXJgE?editors=1011
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log("nextProps", nextProps, "\nprevState", prevState)
if(nextProps.count !== prevState.count)
return {count: nextProps.count};
else
return null;
}
handleIncrease(e) {
this.setState({count: this.state.count + 1})
}
handleDecrease(e) {
this.setState({count: this.state.count - 1})
}
render() {
return <div>
<button onClick={this.handleIncrease.bind(this)}>+</button>
{this.state.count}
<button onClick={this.handleDecrease.bind(this)}>-</button>
</div>;
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = { initialCount: 1 };
}
handleChange(e) {
this.setState({initialCount: e.target.value})
}
render() {
return <div>
<Counter count={this.state.initialCount} />
<hr/>
Change initial:<input type="number" onChange={this.handleChange.bind(this)} value={this.state.initialCount} />
</div>
}
}
ReactDOM.render(
<Main/>,
document.getElementById("root")
);
Expected:
Clicking + / - buttons and textbox change must be update count
Currently:
Main component stores initialCount in own state and passes initial count to child Counter Component.
If handleChange triggered from textbox and initialCount is updated also child Counter component is updated correctly because getDerivedStateFromProps static method provides this.
But changing count value in Counter component with updating local state via handleIncrease and handleDecrease methods it prolematic.
Problem is getDerivedStateFromProps re-trigger this time and resets count value. But I did not expect this because Counter component local state updating parent Main component is not updating. UNSAFE_componentWillReceiveProps is working this way.
Summary my getDerivedStateFromProps usage is incorrect or there is another solution for my scenario.
This version https://codepen.io/ismail-codar/pen/gzVZqm?editors=1011 is good with componentWillReceiveProps

Trying to "sync" state to props like you do is extremely error-prone and leads to buggy applications.
In fact even your example with componentWillReceiveProps has a bug in it.
If you re-render the parent component more often, you will lose user input.
Here is a demo of the bug.
Increment counter, then click “demonstrate bug” and it will blow away the counter. But that button’s setState should have been completely unrelated.
This shows why trying to sync state to props is a bad idea and should be avoided.
Instead, try one of the following approaches:
You can make your component fully "controlled" by parent props and remove the local state.
Or, you can make your component fully “uncontrolled”, and reset the child state from the parent when necessary by giving the child a different key.
Both of these approaches are described in this article on the React blog about avoiding deriving state. The blog post includes detailed examples with demos so I strongly recommend to check it out.

I'm not sure if I understood correctly but if you want to use the prop as a "seed" for the initial value to do it in the constructor and you don't even need getDerivedStateFromProps. You actually don't need to duplicate state:
class Counter extends React.Component {
render() {
return <div>
<button onClick={this.props.handleIncrease}>+</button>
{this.props.count}
<button onClick={this.props.handleDecrease}>-</button>
</div>;
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = { count: 1 };
}
handleIncrease() {
this.setState(prevState => ({count: prevState.count + 1}))
}
handleDecrease() {
this.setState(prevState => ({count: prevState.count - 1}))
}
render() {
return (
<div>
<Counter count={this.state.count} />
<hr/>
Change initial:
<input
type="number"
handleIncrease={this.handleIncrease.bind(this)}
handleDecrease={this.handleDecrease.bind(this)}
count={this.state.count}
/>
</div>
)
}
}

Related

Why this event handling function is being called twice and updates the state twice in react?

import React, { Component } from 'react'
class TestState extends Component{
constructor(props){
super(props)
this.state = {
count:1
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
console.log(this.state)
this.setState (state=>{
return state.count++
})
}
render(){
return <div>
<button onClick={this.handleClick} >Click</button>
{this.state.count}
</div>
}
}
export default TestState
In the above code, when I click the button the counter increases by double value every time I click it .. e.g. On clicking the button the count will increase by 1, 3, 5, 7?
But state.count should only increase once because of the ++ operator.
you should update the state like this
handleClick(){
console.log(this.state)
this.setState (state=>{
return { count: state.count + 1}
})
}
because you return a object that you wish update not a integer
The first answer is a solution, but the lifecycle method can be executed another way:
In my experience, ES6 fashion lends a DRY approach to updating state. Use of anonymous functions and implicit returns allow omission of the return keyword even though they aren't required if you don't rely on the value for another calculation ( Not recommended due to #setState's async nature ).
handleClick() {
console.log( this.state )
this.setState( state => ({
counter: state.count + 1
}))
}
For more on the matter see: https://reactjs.org/docs/state-and-lifecycle.html
Mutating state directly, as you do in your example, can lead to odd behavior and is never advisable. Here is a quote from the React docs:
NEVER mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
By doing this state.count++ you throw React for a loop and as such see unexpected results.
I would rewrite your code like this: (https://codesandbox.io/s/red-lake-yk9gy?file=/src/App.js):
import React, { Component } from "react";
class TestState extends Component {
constructor(props) {
super(props);
this.state = { count: 1 };
}
handleClick = count => {
count++;
this.setState({count});
};
render() {
const { count } = this.state;
return (
<div>
<button onClick={() => this.handleClick(count)}>Click</button>
{count}
</div>
);
}
}
export default TestState;

React: how to use setState and render component when prop changes

This app is supposed to filter words by a specific input. I want to call a function with setState() when rendering a component and technically it's working but there is warning in the console.
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
I guess that this is because I'm calling the function in the render function which I shouldn't, but what should I do instead?
class UsersList extends React.Component {
constructor(props) {
super(props);
this.state = {
allUsers: ["Michał", "Ania", "Kasia", "Tomek", "Hubert", "Jan", "Martyna", "Rafał", "Bartłomiej"],
filteredUsers: [],
input: null
}
}
filter() {
if (this.state.input !== this.props.inputValue) {
const filtered = this.state.allUsers.filter(user => user.toLowerCase().includes(this.props.inputValue));
this.setState({
filteredUsers: filtered.map(user => <li key={user}>{user}</li>),
input: this.props.inputValue
})
}
return this.state.filteredUsers;
}
render() {
this.filter()
return (
<ul>
{this.state.filteredUsers}
</ul>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {input: ""};
this.handleInput = this.handleInput.bind(this);
}
handleInput(e) {
this.setState({input: e.target.value})
}
render() {
return (
<div>
<input onChange={this.handleInput} type="search"/>
<UsersList inputValue={this.state.input} />
</div>
);
}
}
The issue here is caused by changes being made to your component's state during rendering.
You should avoid setting component state directly during a components render() function (this is happening when you call filter() during your component's render() function).
Instead, consider updating the state of your component only as needed (ie when the inputValue prop changes). The recommended way to update state when prop values change is via the getDerivedStateFromProps() component life cycle hook.
Here's an example of how you could make use of this hook for your component:
class UsersList extends React.Component {
constructor(props) {
super(props);
this.state = {
allUsers: ["Michał", "Ania", "Kasia", "Tomek",
"Hubert", "Jan", "Martyna", "Rafał",
"Bartłomiej"],
filteredUsers: [],
input: null
}
}
/* Add this life cycle hook, it replaces filter(). Props are updated/incoming
props, state is current state of component instance */
static getDerivedStateFromProps(props, state) {
// The condition for prop changes that trigger an update
if(state.input !== props.inputValue) {
const filtered = state.allUsers.filter(user => user.toLowerCase().includes(props.inputValue));
/* Return the new state object seeing props triggered an update */
return {
allUsers: state.allUsers
filteredUsers: filtered.map(user => <li key={user}>{user}</li>),
input: props.inputValue
}
}
/* No update needed */
return null;
}
render() {
return (<ul>{this.state.filteredUsers}</ul>)
}
}
Hope this helps
The error is coming up as it could create an endless loop inside the component. As render method is executed whenever the state is updated and your function this.filter is doing a state update. Now as the state updates, your render method triggers the function again.
Best way to do that would be in lifecycle methods or maintain the uses in the App and make UserList a dumb component by always passing the list of filtered users for it to display.

React dumb component with UI state

I want to build a select input component with React.
The select should be dumb component as it's only a UI Component,
but it also have it's own state (Whether to show the options list, or not)
How should I manage this state?
return (
const Select = (props) => {
<div>
<label>{placeholder}</label>
{/*some toggle state*/ && <div>props.children</div>}
</div>
}
)
thanks!
You should not get too confused by the fact that "it's only a UI component". Anything that has an internal state should be a class.
Your code, a dropdown, is my go-to example of when you should use internal state.
Manage your state with setState().
Now your component is stateless, but you need a stateful.
For example:
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {value: '', toggle: false};
}
render() {
return (
<div>
<label>{placeholder}</label>
{this.state.toggle && <div>this.props.children</div>}
</div>
);
}
}
And you should change state with setState function.
For more information, check this article.
According to your code, what you are rendering is a stateless component, so it will not have any state.
What you can do is pass the state from the parent to this component like so:
constructor(props) {
this.state = { showDumbComponent:true }
}
render() {
<DumbComponent show={this.state.showDumbComponent} />
}

Update same variable from two different components

I want to use the same state variable say count and update and retrieve the updated one.
I wrote the following code as a higher order component consisting of one button and one label . Both updates the count but they have separate instances. So how can I re-align my code to keep the same copy of the variable count.
const HOC = (InnerComponent) => class extends React.Component{
constructor(){
super();
this.state = {
count: 0
}
}
update(){
this.setState({count: this.state.count + 1})
}
render(){
return(
<InnerComponent
{...this.props}
{...this.state}
update = {this.update.bind(this)}
/>
)
}
};
class App extends React.Component {
render() {
return (
<div>
<Button>Button</Button>
<hr />
<LabelHOC>Label</LabelHOC>
</div>
);
}
}
const Button = HOC((props) => <button onClick={props.update}>{props.children} - {props.count}</button>)
class Label extends React.Component{
render(){
return(
<label onMouseMove={this.props.update}>{this.props.children} - {this.props.count}</label>
)
}
}
const LabelHOC = HOC(Label)
export default App;
You need to do some "thinking-in-react".
React is just a rendering library, it renders the state, so you need to do some thinking about where that state should live. It's common for your scenario to start look at some sort of Flux library that can handle this "one source of truth" (keep you state in one place only), like Redux for example. If you're using Redux then the Redux store would hold the "count" state for both components and they could both update and read it, so that would be my suggestion in the long run. But to solve your immediate question, you must let a higher component hold the state and then of course also modify that state, you do that by passing down the state and a update function as props to the children.
This is snippet of how it could look, just send the state (count) and the update function down to the child components. I excluded HOC component because i think it just adds to your confusion here. But i'm sure you can imagine how it would work. :)
class App extends React.Component {
constructor(){
super();
this.state = {
count: 0
}
this.update = this.update.bind(this); //Bind it once
}
update(){
this.setState({count: this.state.count + 1})
}
render() {
return (
<div>
<Button count={this.state.count} update={this.update}>Button</Button>
<hr />
<LabelHOC count={this.state.count} update={this.update}>Label</LabelHOC>
</div>
);
}
}
Good reads from the docs:
Components and props
Data flows down

react immutability helper to render only changed subset of data

Please see the example here http://jsfiddle.net/8xzxkteu/1/
I'm trying to only render part of the data which is changed. In this example, state of component Main, data, is indexed by id and I am using react immutability helper to set only the changed one. But, if you click on the output, it renders all the children, as indicated by the counter. I though using immutability helper react can detect only part of the data changed hence only render it. I probably could use shouldComponentUpdate and compare object values for each child, but is there a better way doing this with immutability helper.
class Child extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this)
this.state = {
count: 0
};
}
componentWillReceiveProps(nextProps) {
var count = this.state.count + 1;
this.setState({ count: count });
}
onClick() {
this.props.onClick(this.props.name);
}
render() {
return <p onClick={this.onClick}>{this.props.name}: {this.props.value} {this.state.count}</p>;
}
}
class Main extends React.Component{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
this.state = {
data: {
"a" : "a",
"b" : "b",
}
};
}
handleChange(id) {
this.setState({
data: React.addons.update(this.state.data, { [id]: { $set: 'x' } })
});
}
render() {
const keys = Object.keys(this.state.data);
const children = keys.map(k => {
return <Child name={k} value={this.state.data[k]} onClick={this.handleChange}/>
})
return <div>
{children}
</div>;
}
}
React.render(<Main />, document.getElementById('container'));
When you change state of component react call shouldComponentUpdate of this component and if it is return true react call render of this component.
After that react call componentWillReceiveProps, then shouldComponentUpdate, then render (if shouldComponentUpdate return true) of all child component.
By default, if there no shouldComponentUpdate method, it is considered that it has returned true. It does not matter whether you use immutable data or not - react does not know about it.
If you have immutable data you want avoid rerender, you should use shouldComponentUpdate. You can use pure-render-decorator, for example – it's check component state and props.
But if you change your state in componentWillReceiveProps you still get rerender because componentWillReceiveProps is called before shouldComponentUpdate.

Resources