Does it anti-pattern to change state parameter in render? - reactjs

I need to make simple toggle animation, if state in component was changed. But I don't want to show this animation each time when react calls component render. I found a way how to do it, but I don't like that I need to change state variable directly without setState and make it in render. Also I don't like idea to generate new key each time on animation. I think it it anti-pattern. How can I achieve same result but with different way?
onClick() {
if(this.props.items.length>=5) {
this.setState({
toggleWarning: true
});
}else {
this.props.onStepAdd();
}
}
render() {
let toggle = "";
let toggleKey = null;
if(this.state.toggleWarning) {
toggle = " "+style.toggleMsg;
toggleKey = {key:Math.random()};
this.state.toggleWarning = false;
}
return (
<div>
<h1>Add New</h1>
<div>
<Button
onClick={this.onClick}
label="Add Step"
/>
</div>
<div
className={style.noteMsg + toggle}
{...toggleKey} >
<h3>Note: You can create only up to 5 steps for form wizard</h3>
</div>
</div>
);
}

not sure I understand the objective, but if you want to add a class after a click and then remove the class after some time:
previousTimeout = undefined
onClick = () => {
if(this.props.items.length>=5) {
this.setState({toggleWarning: true});
clearTimeout(this.previousTimeout);
this.previousTimeout = setTimeout(() => {
this.setState({toggleWarning: false})
}, 500);
}
render() {
...className={style.noteMsg + this.state.toggleWarning ? ' '+style.toggleMsg : ''}
}

Related

Prevent infinite loop when updating state within render()

I have the following use case where I have an input and whenever that changes, I need to re-calculate some things and re-render them.
My problem is that I need to update the state in a function called within render(). However, that would case an infinite loop since every such update would cause render() to re-run.
class Test extends Component {
state= {
inputVal='a'
count=0
}
inputChanged = event => this.setState({inputVal: event.target.value});
calculateThingsToBeRendered = () => {
// the value for "count" is calculated (using a hardcoded value to simulate)
let localCount = 5
// need to update the value of the state's "count" here (render() will re-run)
return (
<div> Irrelevant thing to render </div>
);
}
render() {
let thingsToRender = this.calculateThingsToBeRendered();
return {
<div>
<input type="text" onChange={this.inputChanged} value={this.state.inputVal} />
{this.state.count}
<div>
{thingsToRender}
</div>
</div>
}
}
}
Using shouldComponentUpdate wouldn't satisfy my needs since the updated value for the count wouldn't update in the UI.
Moving the calculations done within the calculateThingsToBeRendered (so that the count part of the state gets updated there) to inputChanged wouldn't work because I need the JSX returned by the same function in render.
I don't get in the first place why you "need" to run a function that updates the state in the render function.
I guess you're trying to calculate something from the input.
So there is an state update from input and you want to calculate that afterwards an put that into the state (which is now in a function used in render)
one of many options is to calculate your new state when the event happens.
class Test extends React.Component {
state = {
inputVal: "a",
count: 0
};
inputChanged = event => this.setState({
inputVal: event.target.value,
count: this.calculate(event.target.value)
});
thingsToBeRendered = () => {
return <div> {this.state.count} </div>;
};
calculate = (value) => {
// update your stuff here
return value.length()
}
render() {
let thingsToRender = this.thingsToBeRendered();
return (
<div>
<input
type="text"
onChange={this.inputChanged}
value={this.state.inputVal}
/>
{this.state.count}
<div>{thingsToRender}</div>
</div>
);
}
}

React Parent component checkbox state updates with one step delay

I have a Parent component:
import React, { Component } from "react";
import { Button } from "./Button";
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
numbers: [],
disabled: false
};
this.setNum = this.setNum.bind(this);
}
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(prevState => ({
numbers: [...prevState.numbers, num]
}));
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums });
console.log(this.state.numbers);
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
render() {
return (
<div className="board-container">
<div className="board">
<div className="row">
<Button
id="1"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="2"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="3"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="4"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
</div>
</div>
</div>
);
}
}
... and a Child component:
import React, { Component } from "react";
export class Button extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
isChecked: !this.state.isChecked
});
var num = e.target.value;
this.props.onChange(num);
}
render() {
const { isChecked } = this.state;
if (isChecked === true) {
var bgColor = "#f2355b";
} else {
bgColor = "#f7f7f7";
}
let disabled = this.props.disabled;
if (this.props.numbers.includes(this.props.id)) {
disabled = false;
}
return (
<div className="number-container" id="checkboxes">
<label
className={!isChecked && disabled === false ? "num" : "checked-num"}
style={{ backgroundColor: bgColor }}
>
{" "}
{this.props.id}
<input
type="checkbox"
name={this.props.id}
value={this.props.id}
id={this.props.id}
onChange={this.handleChange}
checked={isChecked}
disabled={disabled}
/>
</label>
</div>
);
}
}
Whenever any Button component is clicked, the Parent component gets the child Button's id value and puts it into its numbers state array. Whenever a Button is unchecked, the Parent updates is numbers state by removing the id of the child Button.
If my code is right, the expected behavior is whenever a Button checkbox is clicked, the Parent numbers state will be updated immediately (adding or removing a number). However, it always updates with one step lag behind.
I know, that the issue is dealing with the React states not being updated instantly, and I've checked similar issues on Stackoverflow. The problem is that I can't figure it out how to make this two components interact with each other in a proper way. What would be the solution for this issue?
Here are three screenshots from codesandbox
If you want to play with it please find the link https://codesandbox.io/s/w2q8ypnxjw
What I did was, I basically copied and pasted your code and updated setNum function to reflect the changes Think-Twice suggested
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums }, () => {
console.log("state logged inside else if", this.state.numbers);
});
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
So before going further let's quickly address a couple of things regarding to React and setState
As B12Toaster mentioned and provided a link which contains a
quote from official documentation
setState() does not always immediately update the component. It may
batch or defer the update until later.
Think-Twice's also points out that by stating
Basically setState is asynchronous in React. When you modify a value
using setState you will be able to see the updated value only in
render..
So if you want to see the immediate state change in a place which
you trigger setState, you can make use of a call back function as
such setState(updater[, callback])
There are two approaches when it comes to and updater with setState,
you could either pass an object, or you could pass a function So in
Think-Twice's example, an object is passed as an updater
this.setState({ numbers: nums } //updater, () => {
console.log(this.state.numbers); //this will print the updated value here
});
When a function is used as an updater (in your setNum function you
already do that), the callback function can be utilized like below
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
}
Your current implementation and communication structure seems fine. It is actually called Lifting State Up which is recommended also by official documentation.
Basically you store the state of array numbers in a parent component (which can be considered as the source of truth) and you pass the method that changes the state as a prop to it's child component.
In the codesandbox link I provided, the functionalities works the way I expect (at least this is what I expect from your code)
Basically setState is asynchronous in React. When you modify a value using setState you will be able to see the updated value only in render. But to see updated state value immediately you need to do something like below
this.setState({ numbers: nums }, () => {
console.log(this.state.numbers); //this will print the updated value here
});

What's the good way to get an event target's siblings' value in React?

function NewItem(props) {
return (
<div>
<input
id = "content"
placeholder = "add a new item..."
/>
<input
id = "score"
placeholder = "score(points per hour)"
/>
<button
onClick = {
(e) => props.onAddItem(e)
}
>
add
</button>
</div>
);
}
The button click handler is implemented in father class, what I'm trying to do is when I click "add", the father class could be able to get the values of these two inputs, so that it could add an item in its "itemList" state. Is there a good way for me to get the values? I know I can manipulate DOM to do so, but I guess it's not a good way.
Below is the click handler and the render method of father class:
handleAddItem(e) {
const newList = this.state.itemList;
const itemCount = this.state.itemCount;
newList.unshift({
itemInfo: {
content: ,
score: ,
time: ,
}
key: itemCount,
index: itemCount,
});
this.setState({
itemList: newList,
itemCount: itemCount + 1,
});
}
render() {
return (
<div id = "todo">
<NewItem
onAddItem = {
(e) => this.handleAddItem(e)
}
/>
<ItemList
itemList = { this.state.itemList }
onClick = {
(e) => this.handleDeleteItem(e)
}
/>
</div>
)
}
what I'm trying to do is when I click "add", the father class could be able to get the values of these two inputs
One solution is to wrap your inputs in a <form> and send it all together.
function NewItem(props) {
return (
<form onSubmit={props.onAddItem}>
<input name="content"/>
<input name="score"/>
<button type="submit">add</button>
</form>
);
}
class Parent extends Component {
handleAddItem(e) {
e.preventDefault();
const content = e.target.content.value;
const score = e.target.score.value;
// ...
}
render() {
return (
<NewItem onAddItem={this.handleAddItem}/>
);
}
}
You might want to check out references or "refs". I generally avoid trying to use them, but sometimes it is just a cleaner way to deal with a problem.
Here's a snippet that might help you out.
import React from 'react'
class TestComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
controlledValue: 'controlled'
}
this._handleControlledChange = this._handleControlledChange.bind(this)
}
_handleControlledChange(e) {
this.setState({
controlledValue: e.target.value
})
}
render(){
return (
<div>
{/* Bind this to 'this.field1' */}
<h3>Function Binding</h3>
<input ref={f => this.field1 = f} />
{/* bind this to this.refs.field2 */}
<h3>Bind to Refs</h3>
<input ref='field2' />
<h3>Controlled Component</h3>
<input
value={this.state.controlledValue}
onChange={this._handleControlledChange}
/>
<button
onClick = {
e => {
let field1Value = this.field1.value
let field2Value = this.refs.field2.value
alert('Field 1 ' + field1Value + '\n Field 2 ' + field2Value + '\nConrolled: ' + this.state.controlledValue )
}
}
>
Ref test
</button>
</div>
)
}
}
Basically what's going on is I'm binding the component to the class so I can reference it later. Typically React code is going to depend on the state and not allowing components to manage themselves, but sometimes this is the behavior you want (a one off form or something you don't want to manage the state for).
Hopefully this helps. I demonstrated the three main ways you will likely want to control your components. Check out projects like https://material-ui.com/ and the tutorials for some more examples.
#wdm's form is a clever solution, I have not used that but I like it.

Trigger Re-Render of Child component

I'm new to React and am running into the same problem a few times. In this particular situation, I'm trying to get an option in a select dropdown to update when I update a text input.
I have a parent, App, with the state attribute "directions", which is an array. This gets passed as a property to a child, GridSelector, which creates the text field and dropdown. When the text field is changed, a function triggers to update the parent state. This in turn causes the GridSelector property to update. However, the dropdown values, which are originally generated from that GridSelector property, do not re-render to reflect the new property value.
I'm trying to figure out the most React-ful way to do this and similar manuevers. In the past, I've set a state in the child component, but I think I've also read that is not proper.
My working site is at amaxalaus.bigriverwebdesign.com
Here's the pertinent code from each file:
App.js
class App extends React.Component {
constructor(props){
super(props);
this.state = {
directions: [],
dataRouteDirections: '/wp-json/wp/v2/directions',
currentDirectionsIndex: 0
}
this.addImageToGrid = this.addImageToGrid.bind(this);
this.changeTitle=this.changeTitle.bind(this);
}
componentDidMount(){
fetch(this.state.dataRouteDirections)
.then(data => data=data.json())
.then(data => this.setState({directions:data}));
}
addImageToGrid(image) {
this.refs.grid.onAddItem(image); //passes image add trigger from parent to child
}
createNewDirections(){
var directions= this.state.directions;
var index = directions.length;
var lastDirections = directions[directions.length-1];
var emptyDirections= {"id":0,"acf":{}};
emptyDirections.acf.grid="[]";
emptyDirections.acf.layout="[]";
emptyDirections.title={};
emptyDirections.title.rendered="New Directions";
if (lastDirections.id!==0 ) { ///checks if last entry is already blank
this.setState({
directions: directions.concat(emptyDirections), //adds empty directions to end and updates currentdirections
currentDirectionsIndex: index
});
}
}
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
render() {
var has_loaded; //has_loaded was added to prevent double rendering during loading of data from WP
this.state.directions.length > 0 ? has_loaded = 1 : has_loaded = 0;
if (has_loaded ) {
/* const currentGrid = this.state.directions;*/
return ( //dummy frame helpful for preventing redirect on form submit
<div>
<div className="fullWidth alignCenter container">
<GridSelector
directions={this.state.directions}
currentDirectionsIndex={this.state.currentDirectionsIndex}
changeTitle={this.changeTitle}
/>
</div>
<Grid ref="grid"
currentGrid={this.state.directions[this.state.currentDirectionsIndex]}
/>
<ImageAdd addImageToGrid={this.addImageToGrid}/>
<div className="fullWidth alignCenter container">
<button onClick={this.createNewDirections.bind(this)}> Create New Directions </button>
</div>
</div>
)
} else {
return(
<div></div>
)
}
}
}
GridSelector.js
class GridSelector extends React.Component {
constructor(props) {
super(props);
var currentDirections = this.props.directions[this.props.currentDirectionsIndex];
this.state = {
currentTitle:currentDirections.title.rendered
}
}
createOption(direction) {
if (direction.title) {
return(
<option key={direction.id}>{direction.title.rendered}</option>
)
} else {
return(
<option></option>
)
}
}
handleChangeEvent(val) {
this.props.changeTitle(val); //triggers parent to update state
}
render() {
return(
<div>
<select name='directions_select'>
{this.props.directions.map(direction => this.createOption(direction))}
</select>
<div className="fullWidth" >
<input
onChange={(e)=>this.handleChangeEvent(e.target.value)}
placeholder={this.state.currentTitle}
id="directionsTitle"
/>
</div>
</div>
)
}
}
You made a very common beginner mistake. In React state should be handled as an immutable object. You're changing the state directly, so there's no way for React to know what has changed. You should use this.setState.
Change:
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
To something like:
changeTitle(newTitle){
this.setState(({directions,currentDirectionsIndex}) => ({
directions: directions.map((direction,index)=>
index===currentDirectionsIndex? ({...direction,title:{rendered:newTitle}}):direction
})

React prop not transferring in Firefox

Having a weird problem with React props in Firefox. Using Redux and Babel as well.
I'm trying to hide a form, once it has submitted. This works fine on Chrome, but for some reason doesn't work on FF and IE.
So here I have a simple component, a div which houses a form. display class comes from parent component:
class MyForm extends Component {
handleFormSubmit(e) {
// fires an action that sets submitInfo to true
}
render() {
const { display } = this.props;
return (
<div className={display}>
<form onSubmit={ (e) => this.handleFormSubmit(e) }>
// some inputs here
</form>
</div>
);
}
}
When the form submits, an action is fired that sets submitInfo (Redux state) is set to true.
The parent component looks like this:
import { submitInfo, hideForm } from '../actions/main.js'
class App extends Component {
render() {
const {submitInfo, hideForm} = this.props;
var showForm;
if ((submitInfo == true) || (hideForm == true)) {
console.log('evaluating showForm');
showForm = 'hide';
} else {
console.log('evaluating showForm');
showForm = '';
}
return (
<div>
<MyForm display={ 'main-form' + ' ' + showForm } />
</div>
);
}
}
function mapStateToProps(state) {
const { submitInfo, hideForm } = state;
return { submitInfo, hideForm }
}
The parent component checks Redux state for submitInfo = true or hideForm = true. If true then pass value of 'hide' to child component.
Can seem to figure out what is wrong. In Chrome, my console.logs within the parent component seem to be firing every time the state object is re-rendered (ie. whenever an action is fired), but this doesn't happen in Firefox.
State object is being updated correctly, so I can see submitInfo: true and hideForm: true when they're supposed appropriate.
You should use a conditional instead of a class to determine whether to show a component.
The parent component's render method would look something like this:
class App extends Component {
render() {
return (
<div>
{!(this.props.submitInfo && this.props.hideForm) ? <MyForm /> : null}
</div>
);
}
}
Now we can also clean up the child component:
class MyForm extends Component {
handleFormSubmit(e) {
// fires an action that sets submitInfo to true
}
render() {
return (
<div className="main-form">
<form onSubmit={(e) => this.handleFormSubmit(e)}>
...
</form>
</div>
);
}
}

Resources