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

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.

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

ReactJS Change Sibling State via Parent

My React structure is
- App
|--SelectStudy
|--ParticipantsTable
In SelectStudy there is a button whose click triggers a message to its sibling, ParticipantsTable, via the App parent. The first Child->Parent transfer works. But how do I implement the second Parent->Child transfer? See questions in comments.
App
class App extends Component {
myCallback(dataFromChild) {
// This callback receives changes from SelectStudy Child Component's button click
// THIS WORKS
alert('SelectStudy Component sent value to Parent (App): ' + dataFromChild.label + " -> " + dataFromChild.value);
// QUESTION: How to Update State of ParticipantsTable (SelectStudy's Sibling) next?
// ........................................................
}
render() {
return (
<div className="App">
<SelectStudy callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable></ParticipantsTable>
</div>
);
}
SelectStudy
class SelectStudy extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.state;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
handleButtonClick = () => {
this.props.callbackFromParent(this.state.selectedStudy);
}
}
ParticipantsTable - this needs to receive a certain variable, e.g. study in its State
class ParticipantsTable extends React.Component {
constructor(props) {
//alert('Constructor');
super(props);
// Initial Definition of this component's state
this.state = {
study: null,
items: [],
error: null
};
}
// THIS METHOD IS AVAILABLE, BUT HOW TO CALL IT FROM App's myCallback(dataFromChild)?
setStudy = (selectedStudy) => {
this.setState({study: selectedStudy});
}
render() {
return ( <div>{this.state.study}</div> );
}
}
The state should live definitively at the App level, not in the child. State needs to live one level above the lowest common denominator that needs access to it. So if both SelectStudy and ParticipantsTable need access to the same bit of state data, then it must live in their closest common ancestor (or above).
This is a core concept of React, known as "lifting state up", so much so that it has its own page in the official React documentation.
In your case, it would look something like this. Notice how state lives in only one place, at the <App /> level, and is passed to children via props.
import React from 'react';
class App extends React.Component {
// State lives here at the closest common ancestor of children that need it
state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
myCallback = (dataFromChild) => {
this.setState(dataFromChild);
};
render() {
return (
<div className="App">
{/* State is passed into child components here, as props */}
<SelectStudy data={this.state} callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable study={this.state.selectedStudy} />
</div>
);
}
}
class SelectStudy extends React.Component {
handleButtonClick = () => {
// Here we execute a callback, provided by <App />, to update state one level up
this.props.callbackFromParent({ ...this.props.selectedStudy, isButtonLoading: true });
};
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.props.data;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
}
// This component doesn't need to track any internal state - it only renders what is given via props
class ParticipantsTable extends React.Component {
render() {
return <div>{this.props.study}</div>;
}
}
I think what you need to understand is the difference between state and props.
state is internal to a component while props are passed down from parents to children
Here is a in-depth answer
So you want to set a state in the parent that you can pass as props to children
1 set state in the parent
this.state = {
value: null
}
myCallback(dataFromChild) {
this.setState({value: dataFromChild.value})
}
2 pass it as a prop to the children
class ParticipantsTable extends React.Component {
constructor(props) {
super(props);
this.state = {
study: props.study,
items: [],
error: null
};
}
Also, although not related to your question, if you learning React I suggest moving away from class-based components in favour of hooks and functional components as they have become more widely used and popular recently.

Where to set state when I need that state in render?

I am getting this error below:
react_devtools_backend.js:2430 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
From the error, I know I am getting it because I am setting state in the render.
But I am not sure where to set the state because I need that state element, developerTitle further down inside the render method.
Where can I put it if not in render?
Thanks!
Here is my code:
export default class Game extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
developerTitle: ''
}
}
render() {
const { indieDeveloperId } = this.props;
this.setState({ developerTitle: this.getDeveloperTitle(game.indieDeveloperId) });
<div>
<h3>{this.state.developerTitle}</h3>
...
...
</div>
}
//by-indie-developer/{indieDeveloperId
async getDeveloperTitle(indieDeveloperId) {
const r = await axios.get(`/api/developer/by-indie-developer/${indieDeveloperId}`);
const developerTitle = r.data;
this.setState({
...this.state, ...{
developerTitle: developerTitle
}
});
}
}
You can't set a state in render(). But you can set a state when the component is loaded using the componentDidMount() function.
Add a function with that name like this to your component:
componentDidMount() {
this.setState({ developerTitle: this.getDeveloperTitle(game.indieDeveloperId) });
}
You dont have to call the function. The state will automatically be set.

How to keep track of a value after I update the component in react 16.3

I just update to the react 16.3. I have a value to keep tracking of a value that I need to post to the server. I want to save this.value after some props changed. I found out that a lot of life cycle functions are deprecated. And I cannot save the value into redux before rendered. Could anyone give me a good way to handle it? Thanks.
class Foo extends Component {
constructor(props) {
super(props);
this.value = {};
}
render() {
return (
//some other components
<Bar onChange={value => this.value = value} />
)
}
}
I would most likely handle it with this
class Foo extends Component {
state = {
text: ''
}
render() {
return (
//some other components
<Bar onChange={value => this.setState({text:value})} />
)
}
}
Keep in mind this is ES7 way to do it. A bit cleaner than doing it in constructor(). If you don't use new syntax just initiate state in constructor as,
constructor(props){
super(props)
this.state = {
text: ''
}
}
if you would like to get your hands dirty more with handling value that user is giving could also pass onChange value to own function and setState on there. Many prefer it that way.
e.g.
handleChange = (text) => {
// Some amaizing text manipulation
this.setState({text})
}
render() {
return (
//some other components
<Bar onChange={this.handleChange} />
)
}
and with redux dispatch function
constructor(props) {
super(props)
/**
* Bind funtions
*/
const { dispatch } = props
this.patchReservation = params =>
dispatch(ActionCreators.patchReservation(params))
}
Then you just attach e.g. this.patchReservation to onChange -listener. ActionCreators is one of my import's which contains my Redux action -functions.
Cheers!

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