How to reset state in a component on prop change - reactjs

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());
}
}

Related

ReactJS: Prop referencing a state var. is not updated after parent's state change

I have a Top component which has a checkbox and also a child MyValidator component. When the checkbox changes, I have a handler in the Top component which sets its State var, this.state.activeItemLocked. The MyValidator child component specified in Top has a Prop with this state var.
However, when the Top Component changes its state, the MyValidator is not refreshed with this new change, even though it should render the current value which is its own state var received from Props:
Top
class Top extends React.Component {
constructor(props) {
super(props);
this.state = {
activeItemLocked: false;
};
}
render() {
return (
<div>
<input type="checkbox" defaultChecked={this.state.activeItemLocked} id="bChkboxLocked" onChange={this.handleCheckboxChange}></input>
<label htmlFor="bChkboxLocked">Locked</label>
<MyValidator value={this.state.activeItemLocked}></MyValidator>
<div>
);
}
// Handler to set State var. in Top upon Checkbox Toggle
handleCheckboxChange = (e) => {
this.setState({
activeItemLocked: e.target.checked ? true : false
});
};
}
MyValidator
class MyValidator extends Component {
constructor(props) {
super(props);
this.state = {
value: this.props.value // Initialize MyValidator's State var. from Props
};
}
render() {
return (
<div style={{"color":"red"}}>Checkbox current state: {this.state.value}</div>
);
}
}
Issue : Upon Top's state change, the Prop-based MyValidator state change does not occur (it does not display the current value).
Keeping a class component, there are many ways you can achieve this. 2 of them are:
Don't copy a props to a state variable. Instead just use the props.
class MyValidator extends Component {
render() {
return (
<div style={{"color":"red"}}>Checkbox current state: {this.props.value}. </div>
);
}
}
Use getDerivedStateFromProps
class MyValidator extends Component {
static getDerivedStateFromProps(props, state) {
if (props.value !== state.value) {
return {value: props.value}
}
return state;
}
render() {
return (
<div style={{"color":"red"}}>Checkbox current state: {this.state.value}</div>
);
}
}
Constructor only runs once. So you need to use getDerivedStateFromProps to update child state before render:
static getDerivedStateFromProps(props){
return {
value: props.value
};
}
also convert the boolean value to a string so that it gets printed out:
{this.state.lol.toString()}
Sandbox: https://codesandbox.io/s/late-https-3eh52?fontsize=14&hidenavigation=1&theme=dark

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.

child component does not rerender with shouldComponentUpdate

My child component changes its state when its prop is changed. I want to re-render child component ImageSlide so call different string when state changes. The className is well changed and console shows the changed values well. But it does not rerender view.
I tried shouldComponentUpdate, but it did not work.
How can I re-render ImageSlide?
let languages = {
en: require('textEnglish'),
kr: require('textKorean')
}
class ImageSlide extends Component {
constructor(props) {
super(props);
this.state={
lang: this.props.lang,
url: this.props.url
}
}
languageSelect=()=> {
if (this.state.lang === 'kr') {
return 'kr';
} else {
return 'en';
}
}
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.lang !== prevState.lang |
nextProps.url !== prevState.url )
{
return {lang : nextProps.lang, url: nextProps.url} ;
}
}
shouldComponentUpdate(nextProps, nextState){
return true;
//I tried also:
// if (nextProps.url !== this.state.url |
// nextProps.lang !== this.state.lang)
// return true;
}
render() {
const Text=languages[this.languageSelect()];
return (
<div className="test-transition">
{console.log(this.state.lang)}
{console.log(this.state.url)}
{console.log(Text["p1_3_"+String(this.state.url)])}
<div className={`pic${this.state.url}`}>
{Text["p1_3_"+String(this.state.url)]}
</div>
</div>
);
}
}
Check if the new props is changes or not using any of the lifecycle event like componentDidUpdate or shouldComponentUpdate or you can also try componentWillReceiveProps (not recommended since its deprecated)
componentDidUpdate()
{
/*Check for prev props condition*/
this.forceUpdate(); /*this method renders the component*/
}

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 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
}

Resources