I'm having the following class that renders users based on a sort dropdown. Users will be listed in alphabetical order if i choose "alphabetical" and in group order when i choose "group".
render(){
return(
const {members, sort} = this.state
{ sort === "alphabetical" && <SortByAlphabet members={members} /> }
{ sort === "group" && <SortByGroup members={members}/> }
)
)
In <SortByAlphabet /> component I am setting a component state object from props.members in componentWillReceiveProps() function.
componentWillReceiveProps = props => {
this.setState({ members : props.members });
}
When I choose "group" sort, <SortByAlphabet /> component is unmounting and <SortByGroup /> is mounting in the DOM. Again when i switch back to "alphabetical" sort, the state variable (members) that was set previosly in <SortByAlphabet /> component becomes NULL because the component was removed from the DOM.
componentWillReceiveProps function is not triggering the second time when re-rendering <SortByAlphabet /> b'coz the props didn't change. But i want to update the state variable like i did it for the first time in componentWillReceiveProps function.
How to do that?
componentWillMount is called only once in the component lifecycle, immediately before the component is rendered. It is usually used to perform any state changes needed before the initial render, because calling this.setState in this method will not trigger an additional render
So you can update your staate using
componentWillMount ()
{
this.setState({ members : props.members });
}
As #Vikram also said, componentWillReceiveProps is not called for the first time, so when your component is initially mounted your state is not getting set, so you need to set the state with props in the componentWillMount/componentDidMount function(which are called only on the first render) also along with the componentWillReceiveProps function
componentWillReceiveProps = props => {
if(props.members !== this.props.members) {
this.setState({ members : props.members });
}
}
componentWillMount() {
this.setState({ members : this.props.members });
}
From version 16.3.0 onwards, you would make use of getDerivedStateFromProps method to update the state in response to props change,
getDerivedStateFromProps is invoked after a component is instantiated
as well as when it receives new props. It should return an object to
update state, or null to indicate that the new props do not require
any state updates.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.members !== prevState.memebers) {
return { members: nextProps.members };
}
return null;
}
EDIT:
There has been a change in getDerivedStateFromProps API from v16.4 where it receives props, state as arguments and is called on every update along with initial render. In such a case, you can either trigger a new mount of the component by changing the key
<SortByAlphabet key={members} />
and in SortByAlphabet have
componentWillMount() {
this.setState({ members : this.props.members });
}
or use getDerivedStateFromProps like
static getDerivedStateFromProps(props, state) {
if(state.prevMembers !== props.members) {
return { members: nextProps.members, prevMembers: props.members };
}
return { prevMembers: props.members };
}
Related
I have two components. A main and a child component.
Let's assume a function is triggered in the main component which cause its state to be mutated.
The state of the main component is passed down to the child component as a prop. The newly updated data in the props of the child component should now be used to to set the state of the child component.
I can't do this on ``componentDidUpdate since it would cause an infinite loop.
On the other hand I wouldn't want to lift the child's state to the main component since most code of it would be useless in the main component.
I hope you can help
You can use getDerivedStateFromProps as mentioned in the React docs:
export default class Child extends Component {
static getDerivedStateFromProps(newProps, currentState) {
return {
value : newProps.value
}
}
render() {
return (
<div>
{/* Your layout */}
</div>
);
}
}
componentDidUpdate takes prevProps as argument componentDidUpdate(prevProps, prevState, snapshot). So to not getting the code in infinite loop, you can compare this.props with prevProps and update the state accordingly.
componentDidUpdate(prevProps) {
if(this.props.data !== prevProps.data) {
// update the new state here this will not cause infinite loop
}
}
For a functional component using hooks.
function Child(props) {
const [whatever, setWhatever] = React.useState(props.whatever);
React.useEffect(() => {
setWhatever(props.whatever);
}. [whatever]);
}
export default Child;
Hope it helps.
I have a complete running code, but it have a flaw. It is calling setState() from inside a render().
So, react throws the anti-pattern warning.
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount
My logic is like this. In index.js parent component, i have code as below. The constructor() calls the graphs() with initial value, to display a graph. The user also have a form to specify the new value and submit the form. It runs the graphs() again with the new value and re-renders the graph.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
checkData: true,
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost !== nextProps.cost) {
this.setState({
checkData: true
});
}
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
if (this.props.cost.length && this.state.checkData) {
const tmp = this.props.cost;
//some calculations
....
....
this.setState({
theData: tmp,
checkData: false
});
}
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
<PieGraph theData={this.state.theData} />
</div>
);
}
}
The FormComponent is an ordinary form with input field and a submit button like below. It sends the callback function to the Parent component, which triggers the graphs() and also componentWillReceiveProps.
handleFormSubmit = (e) => {
this.props.onGpChange(this.state.value);
e.preventdefaults();
}
The code is all working fine. Is there a better way to do it ? Without doing setState in render() ?
Never do setState in render. The reason you are not supposed to do that because for every setState your component will re render so doing setState in render will lead to infinite loop, which is not recommended.
checkData boolean variable is not needed. You can directly compare previous cost and current cost in componentWillReceiveProps, if they are not equal then assign cost to theData using setState. Refer below updated solution.
Also start using shouldComponentUpdate menthod in all statefull components to avoid unnecessary re-renderings. This is one best pratice and recommended method in every statefull component.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost != nextProps.cost) {
this.setState({
theData: this.props.cost
});
}
}
shouldComponentUpdate(nextProps, nextState){
if(nextProps.cost !== this.props.cost){
return true;
}
return false;
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
{this.state.theData !== "" && <PieGraph theData={this.state.theData} />}
</div>
);
}
}
PS:- The above solution is for version React v15.
You should not use componentWillReceiveProps because in most recent versions it's UNSAFE and it won't work well with async rendering coming for React.
There are other ways!
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps is invoked right before calling the render
method, both on the initial mount and on subsequent updates. It should
return an object to update the state, or null to update nothing.
So in your case
...component code
static getDerivedStateFromProps(props,state) {
if (this.props.cost == nextProps.cost) {
// null means no update to state
return null;
}
// return object to update the state
return { theData: this.props.cost };
}
... rest of code
You can also use memoization but in your case it's up to you to decide.
The link has one example where you can achieve the same result with memoization and getDerivedStateFromProps
For example updating a list (searching) after a prop changed
You could go from this:
static getDerivedStateFromProps(props, state) {
// Re-run the filter whenever the list array or filter text change.
// Note we need to store prevPropsList and prevFilterText to detect changes.
if (
props.list !== state.prevPropsList ||
state.prevFilterText !== state.filterText
) {
return {
prevPropsList: props.list,
prevFilterText: state.filterText,
filteredList: props.list.filter(item => item.text.includes(state.filterText))
};
}
return null;
}
to this:
import memoize from "memoize-one";
class Example extends Component {
// State only needs to hold the current filter text value:
state = { filterText: "" };
// Re-run the filter whenever the list array or filter text changes:
filter = memoize(
(list, filterText) => list.filter(item => item.text.includes(filterText))
);
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
// Calculate the latest filtered list. If these arguments haven't changed
// since the last render, `memoize-one` will reuse the last return value.
const filteredList = this.filter(this.props.list, this.state.filterText);
return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
</Fragment>
);
}
}
I am new to react and so confused in handling and calling the onChange events.
Now , I have 2 components :-
1. Parent component -
updateField = e => {
console.log("update field e called");
this.setState({
value: e.target.value
});
};
<InputTypeahead value={this.state.value} label="Email" onChange={this.updateField} typeaheadItems={this.emailAdressess} /
where I am calling the onChange and taking the current value out. Till
now whatever I type in Input I get the value.
Now,
2.In Child component :
I want to take the value coming from this parent component and using that would like to setstate.
How to achieve this in React js ? I have tried using refs , but result was not successful.
Any Help is appreciated.Thanks.
From version i.e 16.3.0 onwards, you can make use of getDerivedStateFromProps method to update the state based on props like
class InputTypeahead extends React.Component {
state = {
value: ''
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.value !== prevState.value) {
return { value: nextProps.value};
}
return null;
}
}
According to the docs:
getDerivedStateFromProps is invoked after a component is
instantiated as well as when it receives new props. It should return
an object to update state, or null to indicate that the new props do
not require any state updates.
Before v16.3.0, you would make use of constructor along with componentWillReceiveProps like
class InputTypeahead extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value
}
}
componentWillReceiveProps(nextProps) {
if(nextProps.value !== this.props.value) {
this.setState({ value: nextProps.value});
}
}
}
I need to use current props and previous props value in my React component.
So i did it like this
state = {
current: null,
previous: null,
};
componentWillReceiveProps(nextProps) {
if (nextProps.amount !== this.state.current) {
this.setState({previous: this.state.current, current: nextProps.amount});
}
}
...
render() {
const {previous, current} = this.state;
return (
...
<CountUp className="counter" start={previous} end={current} duration={1}/>
...
)
}
It works fine, but is it good React practise to do it like this? Are there others "good" ways to do it?
As of v16.2.0, componentWillReceiveProps is the right place to update state, based on prop changes and since you want to use both current state and previous state in render, you need to maintain, two different state variables as you are doing
However, when you update the state based on previous state, use functional setState method
Check this answer for more details
When to use functional setState
componentWillReceiveProps(nextProps) {
if (nextProps.amount !== this.state.current) {
this.setState(prevState => ({ previous: prevState.current, current: nextProps.amount }));
}
}
According to the latest RFC to React
State derived from props/state
The purpose of this pattern is to calculate some values derived from props for use during render.
Typically componentWillReceiveProps is used for this, although if the calculation is fast enough it could just be done in render.:
From v16.3.0 onwards, you would make use of
static getDerivedStateFromProps(nextProps, prevState) {
if (
!prevState ||
prevState.current !== nextProps.amount
) {
return {
previous: prevState.current,
current: nextProps.amount
};
}
}
I'd like to update this answer for anyone else who comes here from Google. As of v16.8.6, componentWillReceiveProps has been marked as legacy, and is not recommended to use. Instead you should use componentDidUpdate and update your state based on the new props and previous props/previous state.
componentDidUpdate(prevProps, prevState) {
if (this.props.someVal !== prevState.someVal) {
this.setState({ previous: prevState.someVal, current: this.props.someVal });
}
}
Obviously, whether you check the previous state or the previous props is up to your discretion/situation. You can implement componentDidUpdate with or without the parameters, but if you want prevState you must declare prevProps.
React Update Lifecycle
componentDidUpdate()
You can use an arrow function in your setState object.
Like this :
this.setState((prevState) => {
return { yourState: prevState.yourState }
})
prevState is a default name but you can replace the name as you want
I have the following function:
update() {
this.currentItem = [];
//...Populate currentItem
this.setState({
currentItem
});
}
Which renders on the page like this;
render() {
const { currentItem } = this.state;
return {currentItem}
}
I then pass this function into a child component, like this:
<Component
update={this.update.bind(this)}
/>
and then call it like this in my child component:
let { update } = this.props;
if (typeof update === "function")
update();
The problem is that the update function does not re render the content I am updating on the parent page. As far as I understand this, whenever setState is called, render also gets called. render() does seem to be getting called, but it does not seem to display the updated value - why is this, and how can I resolve it?
I guess it could be to do with the fact that it is coming from a child component?
I have tried forceUpdate, but it does not re render the component either - what am I missing?
Try avoiding this.forceUpdate() because this will not fire shouldComponentUpdate() which is a performance hook for your Component in React. As, I saw that you are passing your state to child component and trying to update the parents state object from there, which against the law. You should create a method in parent and pass that method as a prop to the child component. It should look like this
constructor(props) {
super(props);
this.state = { loading: false };
this.update = this.update.bind(this);
}
update(newState) {
this.setState({loading: newState })
}
render() {
return <ChildComponent update={this.update} />
}
I am just guessing here but i think you set the initial value for the child component in the constructor and the value you want it to reflect points to its own state instead of the parents state
I needed to set the state to loading first, then when I set it to loading = false, it would re render that page
this.setState({
loading:true
});
//...Do the work
this.setState({
loading:false
});