getDerivedStateFromProps doesn't work as expected - reactjs

Im refactoring my react application and I replaced componentWillRecieveProps with getDerivedstateFromProps.
Then it gives an error saying Cannot read property 'setState' of undefined
This is my code
static getDerivedStateFromProps = nextProps => {
const { auth, history } = nextProps;
redirectIfAuthenticated(auth.isAuthenticated, history, './dashboard');
if (nextProps.errors) {
this.setState({
errors: nextProps.errors
});
}
}
What am I doing wrong here?

getDerivedStateFromProps is not just a rename of componentWillReceiveProps, but has a different purpose and syntax. It doesn't have access to the class instance since it is static
Also getDerivedStateFromProps is supposed to only update state and not have any side-effects. All side-effects must go in componentDidUpdate
static getDerivedStateFromProps(props, state) {
if (props.errors !== state.prevErrors) {
return {
errors: props.errors,
prevErrors: props.errors
}
}
return { prevErrors: props.errors}
}
render() {
const { auth, history } = props;
if(this.props.auth.isAuthenticated) {
return <Redirect to={'/dashboard'} />
};
// extra logic here
}

Related

How to reset state in a component on prop change

How should I reset the state of a Child on every prop change?
Parent component:
render() {
const { show, month } = this.props; // January, February, ...
return (
{ show ? <Child selectedMonth={month} /> : null }
);
}
Child component:
componentDidMount() {
this.resetChildState();
// this will never run if the Parent's month prop is changed
// only if show becomes false from true
}
I want resetChildState to run on every month change.
Just try to use componentDidUpdate, in the body of this method you can compare whether props from parent changed or not. So if they did just call your reset method or whatever you want.
For more info visit https://reactjs.org/docs/react-component.html#componentdidupdate
componentDidUpdate(prevProps, prevState) {
if(this.props.selectedMonth!== prevProps.selectedMonth) {
this.resetChildState();
}
}
You can use getDerivedStateFromProps()
static getDerivedStateFromProps(nextProps, prevState) {
if(monthChanged){
return initialState; //reset to initialState that you have defined.
}
return null;
}
if you want just reset your state exactly after changing the props, you can use the componentWillReceiveProps react lifecycle as below:
class Children extends React.Component {
// ...
state = {
name: "test"
};
componentWillReceiveProps(nextProps) {
this.setState({name: ""});
}
// ...
}
use ComponentDidUpdate
componentDidUpdate() {
if (this.props.id !== this.state.section_id) {
this.setState({
section_id:this.props.id,
filteredProducts:[],
productsByPage:[],
pageNumber:0,
hasMore:true,
showloading:true
},()=>this.fetchData());
}
}

How to migrate componentWillReceiveProps in react 16.0.0?

I have a reactcomponent that has a few obsolete events:
componentWillMount() {
const { applicationStages } = this.props;
if (applicationStages && applicationStages.length > 0) {
this.setState({
appColumnsSorted: this.getSortedAppColumns(someVar),
});
}
}
componentWillReceiveProps(nextProps) {
const {
presets: { sortCriteria: sortBy, customCriteria },
} = nextProps;
const { appColumnsSorted } = this.state;
const sortedColumnsUpdated = this.getSortedAppColumns(
appColumnsSorted,
sortBy,
true
);
this.setState({
appColumnsSorted: sortedColumnsUpdated,
});
}
getSortedAppColumns = (appColumns, sortBy, criticalFirst) => {
//returns object
};
'componentWillMount' is basically to initialize the appColumnsSorted. The issue is that with v16 this event is obsolete. So what can event can I use for this now? Also what is the way to migrate 'componentWillReceiveProps' in this scenario?
What you're using componentWillMount for can be done in the constructor so
componentWillMount() {
const { applicationStages } = this.props;
if (applicationStages && applicationStages.length > 0) {
this.setState({
appColumnsSorted: this.getSortedAppColumns(someVar),
});
}
}
will change to
export default class YourClass extends Component {
constructor(props) {
// keep a separate method just to avoid writing code in constructor for readability
this.state = constructInitialState(props);
}
constructInitialState(props) {
const state={};
//More state handling as required
const { applicationStages } = props;
if (applicationStages && applicationStages.length > 0) {
state.appColumnsSorted = this.getSortedAppColumns(someVar);
}
return state;
}
}
This approach is slightly better because getDerivedStateFromProps will be called before each render and will waste computation.
From the code snippet it is not obvious why you want to store it in state. If you do save it to state then the only way you have would be to use componentDidUpdate as mentioned in the other answer by Aaditya Thakkar. This will require you to mirror your props in state only for comparison purpose (Mapping props to state is not the best way, more on this link https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#anti-pattern-unconditionally-copying-props-to-state)
I would, however, recommend calling your sort computation method and return its result in render directly; this would avoid extra checks in componentDidUpdate to render the second time. As I'm not aware if these props are coming from redux or a parent React component, another option would be to avoid expensive computation in this class and simply provide the correct value from either the parent component or calculate the value in the redux store and send the final prop directly for use.
ComponentWillReceiveProps can be replaced with getDerivedStateFromProps. 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. It's a static method, so this can not be used inside it.
Hence, you can no longer reference this.getSortedAppColumns from getDerivedStateToProps, you need componentDidUpdate lifecycle for that. Here, I have broken down ComponentWillReceiveProps into getDerivedStateFromProps and componentDidUpdate:
static getDerivedStateFromProps(nextProps, prevState) {
const {
presets: { sortCriteria: sortBy },
} = nextProps;
if (sortBy === prevState.sortBy) return null;
return ({ sortBy: nextProps.sortBy });
}
componentDidUpdate(_, prevState) {
const { appColumnsSorted, sortBy } = this.state;
if (sortBy !== prevState.sortBy) {
const sortedColumnsUpdated = this.getSortedAppColumns(
appColumnsSorted,
sortBy,
true
);
this.setState({
appColumnsSorted: sortedColumnsUpdated,
});
}
}

How to access component function inside getDerivedStateFromProps

How do I call a component function inside static getDerivedStateFromProps?
This is my code
class Box extends Component {
updateVariables() {
this.statColorClass = "no-change";
if(this.props.dir === "up") {
this.statColorClass = "up";
this.dirIcon = <GoArrowUp />;
} else if (this.props.dir === "down") {
this.statColorClass = "down";
this.dirIcon = <GoArrowDown />;
}
}
static getDerivedStateFromProps() {
this.updateVariables(); // not working
}
render() {}
}
The line this.updateVariables(); is not working. How do I call updateVariables() ?
this cannot be accessed inside static functions, that is by design. Still if you want to get this inside getDerivedStateFromProps, you could use the below code, but you should never use like this.
constructor() {
super();
this.state = {
currentInstance: this
}
}
Now inside your getDerivedStateFromProps() access currentInstance which is same as this
static getDerivedStateFromProps(props, state) {
state.currentInstance.updateVariables();
}
getDerivedStateFromProps is intended to update component state from received props. It was deliberately designed to not allow access to component instance.
statColorClass, etc. properties represent component state, but they aren't parts of state, this is rarely needed in React stateful components. A proper solution would be to refactor component to use state:
static getDerivedStateFromProps(props, state) {
state = {...state, statColorClass: "no-change" };
if (props.dir === "up") {
state.statColorClass = "up";
state.dirIcon = <GoArrowUp />;
} else if (props.dir === "down") {
state.statColorClass = "down";
state.dirIcon = <GoArrowDown />;
}
return state;
}
This method doesn’t have access to the component instance. If you want to reuse functionality then you could create pure components outside the class. The whole point of this function is to convert props to new state, which is what the static function should return. It is not intended to cause side effects. Not sure why you are using instance vars to handle state.

How to show navigation tab when it was already mounted

I have hidden TabNavigator (react-navigation), and I need to show it after property event changed.
My component:
export default class Quotes extends Component {
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state;
return {
tabBarVisible: params && params.showNavigation
};
};
UNSAFE_componentWillMount() {
this.props.navigation.setParams({ showNavigation: this.props.profileCreated });
}
render() {
...
}
}
I manipulate tabBarVisible with showNavigation option that connected to this.props.profileCreated. But I don't know where to move this code, to check every time props changed. When I'm trying to move it to render or componentWillReceiveProps it's not allowed to setState there.
Updated
When I'm trying to add:
static getDerivedStateFromProps(nextProps, prevState) {
if (!this.props.profileCreated && nextProps.profileCreated) {
this.props.navigation.setParams(
{ showNavigation: this.props.profileCreated }
);
}
}
I have next warnings:
ExceptionsManager.js:71 Warning: Quotes: Did not properly initialize state during construction. Expected state to be an object, but it was undefined.
...
Quotes.js:25 getDerivedStateFromProps
Quotes.js:26 {screenProps: undefined, navigation: {…}, unsubmitted: Array(0), quotes: {…}, fields: {…}, …}
ExceptionsManager.js:71 Warning:
Quotes.getDerivedStateFromProps(): A valid state object (or null) must be returned. You have returned undefined.
Note that you can use componentWillReceiveProps, but you just need to add an if statement to ensure you don't end up in the infinite update loop:
UNSAFE_componentWillReceiveProps(nextProps) {
if (!this.props.profileCreated && nextProps.profileCreated) {
this.props.navigation.setParams(
{ showNavigation: this.props.profileCreated }
);
}
}
However, it's recommended to use the static method getDerivedStateFromProps(nextProps, prevState) now. This method is called whenever a component gets updated (and also on initial mount).
static getDerivedStateFromProps(nextProps, prevState) {
if (!this.props.profileCreated && nextProps.profileCreated) {
this.props.navigation.setParams(
{ showNavigation: this.props.profileCreated }
);
}
return {} // or any state change you need
}

Warning: setState(...): Cannot update during an existing state transition

I'm getting the following error:
Warning: setState(...): 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 component:
import React from 'react';
import { Redirect } from 'react-router'
import Notifications from 'react-notification-system-redux';
constructor(props) {
super(props);
this.state = {
invite_token: this.props.match.params.token,
FormSubmitSucceeded: false,
inviteRequestSubmitSucceeded: false
};
}
....
inviteAlreadyUsed() {
const notificationOpts = {
message: 'Invitation already used!',
};
this.props.createNotificationSuccess(notificationOpts);
}
render() {
const { invite } = this.props;
if (invite && invite.status === "completed") {
this.inviteAlreadyUsed();
return <Redirect to={{ pathname: '/' }}/>;
}
...
Any suggestions on how to avoid this warning? Is this not how you would handle a redirect?
this.inviteAlreadyUsed(); in render -> reducer updating a state -> it call new render -> this.inviteAlreadyUsed(); -> reducer update a state and again and again...
Just don't call inviteAlreadyUsed in render.
First, I think you should bind the inviteAlreadyUsed() function. You can use arrow function () => {}.
inviteAlreadyUsed = () => {
const notificationOpts = {
message: 'Invitation already used!',
};
this.props.createNotificationSuccess(notificationOpts);
}
Second, seems like you set the state with props in constructor. Setting it in componentWillMount() might be a better approach.
constructor(props) {
super(props);
this.state = {
invite_token: '',
FormSubmitSucceeded: false,
inviteRequestSubmitSucceeded: false
};
}
componentWillMount() {
this.setState({
invite_token: this.props.match.params.token
})
}

Resources