I like to update my code to use getDerivedStateFromProps instead of componentWillReceiveProps as the I am receiving deprecated error. The component is receiving a date prop and every time the date is changed in need to run the getList with the new date. To do this I am using the follow code below
componentWillReceiveProps(nextProps) {
const {date} = this.props;
if (nextProps.date !== date) {
console.log('prop changed to : ', nextProps.date);
this.getList(nextProps.date);
}
}
I tried the following but it does not work
static getDerivedStateFromProps(props, state) {
const {date} = this.props;
if (props.date !== date) {
console.log('prop changed to : ', props.date);
this.getList(props.date);
}
return;
}
getDerivedStateFromProps does not look like the right tool for what you are trying to do. Instead use componentDidUpdate:
componentDidUpdate(prevProps) {
const { date } = this.props;
if (prevProps.date !== date) {
this.getList(date);
}
}
It's pretty rare to use getDerivedStateFromProps. For more information on when to use getDerivedStateFromProps I recommend this article
Related
I need to update a child's state when a prop gets updated, so I made something like this:
componentDidUpdate(prevProps) {
const { searchValue, searchCriterion } = this.props;
if (searchValue !== prevProps.searchValue) {
this.setState({ searchValue });
}
if (searchCriterion !== prevProps.searchCriterion) {
this.setState({ searchCriterion });
}
}
ESLint's airbnb guide is complaining about it and throwing this warning, even though I can't see any noticeable rendering or performance issues.
The warning says:
Updating the state after a component update will trigger a second
render() call and can lead to property/layout thrashing.
Is there a better way to update a child's state when a prop has a new value?
Or should I ignore the warning?
The problem of your code is that it might create an infinite loop. When you call the setState method, you trigger a second render, just like the warning says, and consequently a new componentDidUpdate.
In order to solve this problem, you might want to create a break condition for certain values of your state to get out of your loop. For example, you could simply use a flag like so:
componentDidUpdate(prevProps) {
const { searchValue, searchCriterion } = this.props;
if (this.state.isAlreadyUpdated === true) {
this.setState({ isAlreadyUpdated: false });
break;
} else {
this.setState({ isAlreadyUpdated: true });
}
if (searchValue !== prevProps.searchValue) {
this.setState({ searchValue });
}
if (searchCriterion !== prevProps.searchCriterion) {
this.setState({ searchValue });
}
}
I have two components placed in a container. One is calling the Id of a publication and the other is calling a date of an issue from that specific publicationId.
When i called an Id for example the 100 then it will also load the issues from that Id. After that i can also filter on issueDate. But when i change the component with the Id again to have other list of issues then it won't rerender the component with the issues.
I heard that this problem is solved when using componentDidMount but i don't know what to do after this step:
public componentDidUpdate(): void {
const{ data, date } = this.state
if (this.state.data !== data) {
//don't know what to do in this if statement
}
This is the code to get publication Id with axios:
constructor(props: TTestProductionProgressStatus) {
super(props);
this.state = {
date: null,
data: undefined,
prodItem: []
};
}
public componentDidMount(): void {
const urlDate = this.props.location.pathname.split("/")[3];
const date = urlDate
? new Date(
`${urlDate.substr(0, 4)}-${urlDate.substr(4, 2)}-${urlDate.substr(
6,
2
)}T00:00:00.000+01:00`
)
: null;
axios
.get<Definitions.ProductionProgressStatus[]>(
"http://app30:665/v1/productionProgressStatus/crud?publicationId=" +
this.props.id
)
.then(res => {
this.setState(
{
data: res.data
},
() => this.handleDateChange(date)
);
})
.catch(error => console.log(error.response));
}
The first thing to address is that your comparison is incorrect:
const{ data, date } = this.state
if (this.state.data !== data) {
Here you're comparing this.state.data with this.state.data; you're just extracting it two different ways, but it's the same properties you're comparing. The way to compare old versus new state is via the parameters passed to componentDidUpdate:
componentDidUpdate(prevProps, prevState) {
if (this.state.data !== prevState.data) {
As to what you do in the if statement, you're probably wanting to make some kind of call to setState, which will cause another update. Generally it's better to avoid cascading updates when possible so you don't provoke two different re-renders unnecessarily; in other words, if you find an opportunity to change things without using componentDidUpdate, that would be better.
I have built a simple counter app:
class Counter extends React.Component {
constructor(props) {
super(props);
this.handleAddOne = this.handleAddOne.bind(this);
this.handleMinusOne = this.handleMinusOne.bind(this);
this.handleReset = this.handleReset.bind(this);
this.state = {
count: 0
};
}
componentDidMount() {
const stringCount = localStorage.getItem('count');
const count = parseInt(stringCount);
if (isNaN(count) === false) {
this.setState(() => ({ count }));
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
localStorage.setItem('count', this.state.count);
console.log('componentDidUpdate');
}
}
handleAddOne() {
this.setState((prevState) => {
return {
count: prevState.count + 1
}
});
}
handleMinusOne() {
console.log('handleMinusOne');
this.setState((prevState) => {
return {
count: prevState.count - 1
}
});
}
handleReset() {
this.setState(() => {
return {
count: 0
}
});
}
render() {
return (
<div>
<h1>Count: {this.state.count}</h1>
<button onClick={this.handleAddOne}>+</button>
<button onClick={this.handleMinusOne}>-1</button>
<button onClick={this.handleReset}>reset</button>
</div>
);
}
}
ReactDOM.render(<Counter />, document.getElementById('app'));
The question I have is with componentDidUpdate(). In it, I am checking to see if the prevState.count is not the same as the this.state.count. If it is not the same, then I set localStorage to the new count. If it is same, I do nothing.
In the current componentDidUpdate(), I need prevProps as an argument for this function to work correctly. For example, if I just have this:
componentDidUpdate(prevState) {
if (prevState.count !== this.state.count) {
localStorage.setItem('count', this.state.count);
console.log('componentDidUpdate');
}
}
Then the component sets localStorage every time the reset button is pressed repeatedly, even though the count remains at 0.
What is going on? Why do I need prevProps for componentDidUpdate() to work correctly, if I am never using props in that function?
The first parameter in componentDidUpdate is prevProps. The second parameter is prevState. The documentation clearly states that:
componentDidUpdate(prevProps, prevState, snapshot)
This
componentDidUpdate(prevState) {...}
is not a correct signature for the hook. Even though the first parameter was called prevState, it contains previous props.
It's possible to alternate function parameters based on its arity but this isn't implemented in React and considered a bad practice in general because this leads to more complex signatures.
To not cause linter warnings, unused parameters can be underscored by convention:
componentDidUpdate(_prevProps, prevState) {...}
It doesn't matter if you are using prevProps in your function or not, it has to be included, since the arguments order in functions does matter.
componentDidUpdate(prevProps, prevState)
If you'd omit the prevProps argument, the prevState (even tho the name would say clearly that it's previous version of state) it would stand for the previous version of props.
It's just a blank. You can use either an underscore, to tell others (or just yourself) that it's not used in that function.
componentDidUpdate(_, prevState)
Because componentDidUpdate receives prevProps as the first argument and prevState as second. If you write the function as componentDidUpdate(prevState) it is receiving prevProps but it will be named prevState. No matter how you name the arguments, they will be prevProps, prevState, and snapshot, respectively.
If you don't need the first argument, you can use an underscore eg. componentDidUpdate(_, prevState).
First of all, I'm really new into React, so forgive my lack of knowledge about the subject.
As far as I know, when you setState a new value, it renders again the view (or parts of it that needs re-render).
I've got something like this, and I would like to know if it's a good practice or not, how could I solve this kind of issues to improve, etc.
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
key: value
}
this.functionRender = this.functionRender.bind(this)
this.changeValue = this.changeValue.bind(this)
}
functionRender = () => {
if(someParams !== null) {
return <AnotherComponent param={this.state.key} />
}
else {
return "<span>Loading</span>"
}
}
changeValue = (newValue) => {
this.setState({
key: newValue
})
}
render() {
return (<div>... {this.functionRender()} ... <span onClick={() => this.changeValue(otherValue)}>Click me</span></div>)
}
}
Another component
class AnotherComponent extends Component {
constructor (props) {
super(props)
}
render () {
return (
if (this.props.param === someOptions) {
return <div>Options 1</div>
} else {
return <div>Options 2</div>
}
)
}
}
The intention of the code is that when I click on the span it will change the key of the state, and then the component <AnotherComponent /> should change because of its parameter.
I assured that when I make the setState, on the callback I throw a console log with the new value, and it's setted correctly, but the AnotherComponent doesn't updates, because depending on the param given it shows one thing or another.
Maybe I need to use some lifecycle of the MyComponent?
Edit
I found that the param that AnotherComponent is receiving it does not changes, it's always the same one.
I would suggest that you'll first test it in the parent using a simple console.log on your changeValue function:
changeValue = (newValue) => {
console.log('newValue before', newValue);
this.setState({
key: newValue
}, ()=> console.log('newValue after', this.state.key))
}
setState can accept a callback that will be invoked after the state actually changed (remember that setState is async).
Since we can't see the entire component it's hard to understand what actually goes on there.
I suspect that the newValue parameter is always the same but i can't be sure.
It seems like you're missing the props in AnotherComponent's constructor. it should be:
constructor (props) {
super(props) // here
}
Try replacing the if statement with:
{this.props.param === someOptions? <div>Options 1</div>: <div>Options 2</div>}
also add this function to see if the new props actually get to the component:
componentWillReceiveProps(newProps){
console.log(newProps);
}
and check for the type of param and someOptions since you're (rightfully) using the === comparison.
First, fat arrow ( => ) autobind methods so you do not need to bind it in the constructor, second re-renders occur if you change the key of the component.
Ref: https://reactjs.org/docs/lists-and-keys.html#keys
I'm refactoring the deprecated life-cycle method componentWillRecieveProps() with its new successor static getDerivatedPropsFromState()
The problem that I'm facing with this refactor is that componentWillRecieveProps() use a class function inside:
componentWillRecieveProps(nextProps) {
if(this.props.theme !== nextProps.theme) {
this.changeTheme()
}
}
So when I try to access this function inside of the new life-cycle method:
static getDerivatedPropsFromState(nextProps, prevState) {
if(prevState.theme !== nextProps.theme) {
this.changeTheme();
return { prevState: nextProps.theme };
}
return null;
}
An error jump saying this.changeTheme() is undefined.
How should I make reference to this function inside of static getDerivatedPropsFromState()?
Any help and explanation on why this is happening will be really appreciated.
getDerivatedPropsFromState is used for only updating the state. You should use componentDidUpdate in place of componentWillReceiveProps if you are planning to do updates.
See https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#fetching-external-data-when-props-change
In the blog it mentions:
The recommended upgrade path for this component is to move data updates into componentDidUpdate. You can also use the new getDerivedStateFromProps lifecycle to clear stale data before rendering the new props.
So instead of
componentWillRecieveProps(nextProps){
if(this.props.theme !== nextProps.theme){
this.changeTheme()
}
}
One alternative is to do this
static getDerivatedPropsFromState(nextProps, prevState) {
if(prevState.theme !== nextProps.theme) {
return { theme: nextProps.theme };
}
return null;
}
componentDidUpdate(prevProps, prevState) {
if (this.state.theme !== prevState.theme) {
this.changeTheme();
}
}