How to update component state on redux state change? - javascript

Since I have a component with forms, I need the forms to be connected to the component state. The initial data comes from Redux so try to initialize and update the component by setting the state with the props:
componentWillMount = () => {
this.setState({
language: this.props.language || 'en'
})
}
language is a connected prop and I checked that it is updated in the store.
const mapStateToProps = state => ({
language: state.language
})
I also tried to use componentWillReceiveProps and componentWillUpdate but it doesn't work. I get the initial state, and even though the store and the connected props change, the component's state doesn't update.
{this.props.language} // updates
{this.state.language} // doesn't change
What is the correct way to manage forms from Redux data?
The render part:
render () {
const {classes, theme, web} = this.props
const language = (
<CardContent>
<Typography type="headline">
Language
</Typography>
<Divider/>
<form className={classes.container} autoComplete="off">
<FormControl fullWidth margin="normal">
<InputLabel htmlFor="language">Select Block</InputLabel>
<Select
value={this.state.language} // <==================== language
onChange={this.handleLanguaheChange}
input={<Input id="language"/>}
>
<MenuItem value={'en'}>English</MenuItem>
<MenuItem value={'he'}>עברית</MenuItem>
</Select>
</FormControl>
</form>
</CardContent>
)
...
return (
<Grid
container
spacing={theme.spacing.unit * 3}
justify={'space-between'}
className={classes.gridWrap}
>
<Grid item xs={6}>
<Card className={classes.card}>
{language}
</Card>
...

First, you are using an arrow function for componentWillMount. Rule of thumb is, do not use arrow functions for life-cycle hooks(componentWillMount, shouldComponentUpdate, ...etc). It's usual to setState in componentWillMount hook. But never set state in componentDidMount.
please try to re-write it as,
constructor(props) {
super(props)
this.state = {
language: 'en',
}
}
componentWillMount() {
const { language } = this.props
if (language) this.setState(prevState => ({ language: prevState.language = language }))
}
in some exceptional cases, such as i wrote two classes in a single .js file(like i said, some exceptions) and i couldn't be able to modify it from componentWillMount as expected(later noted, the props are modified by the child class).
in such cases, you can override it in render
render() {
const { language } = this.props
if (language) this.setState(prevState => ({ language: prevState.language = language }))

To accomplish this with React Hooks:
Track previous value with useRef()
Compare with previous value and conditionally update the local component state
The posted example didn't really make sense to me so here is the problem I faced:
I have a form with component state that I needed to clear out when the redux state changed.
To accomplish this my component looks like this:
import { useSelector } from 'react-redux';
import React, { useState, useEffect, useRef } from 'react';
const CreateCase = () => {
//redux state
const accounts = useSelector(state => state.accounts);
//component state
const [productId, setProductId] = useState(undefined);
const prevAccountRef = useRef<string>();
useEffect(() => {
//compare current with previous account and clear productId if changed
if (account.id != prevAccountRef.current) {
setProductId(undefined);
}
//set previous account for next render
prevAccountRef.current = account.id;
});
//... render
}
It's very important that you only run setState inside of useEffect conditionally.

even though the store and the connected props change, the component's state doesn't update
The way you have it written, the state won't update unless you explicitly update it using setState() (most likely in the componentWillReceiveProps() method).
When you use mapStateToProps() with the Redux connect() HOC, you are mapping your Redux state to your component through its props, so in your case this.props.language will update when the Redux stored updates.

componentWillReceiveProps will be called only when your component re-rendered.
Initially when component first time mounting, it not going to triggered.
You can not call setState inside componentwillupdate.
In order to initialise the initial state of component from redux store,you should use constructor.
constructor(props) {
super(props);
this.state = {
language: this.props.language || 'en'
}
}

Not sure if this applies, but I ran into a similar problem recently and my case was due to the fact that calling this.setState does not guarantee an instant update to state; it only says that the state change will be effected eventually.
From the react-component documentation:
Think of setState() as a request rather than an immediate command to
update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
If you need to make some "instant" changes to state or things that depend on state, then there's that callback on setState() that you can use to lock things in place. The callback is what worked for me.

Related

How to avoid unnecessary re-render in other child when I only make change in specific child

In my react project
I have three components
One is called BigForm
The other two are called Form A and Form B.
In BigForm, there are two state, DataA and DataB, which would be passed into FormA and FormB.
Question:
Whenever I add input value into Form A or Form B, it will also trigger unnecessary render on the other components.
How can I avoid it? Example code would be appreciated. (please also see my edit before trying) thanks ; I heard redux may help, but I am not sure how to put this in work in this example
import React, {useState, useEffect} from "react";
const FormA = (props) => {
useEffect(()=>{ console.log('Form A was just rendered')})
const { dataA, setDataA } = props;
return (
<div>
<input onChange={(e) => setDataA(e.target.value)}></input>
<p>Input Form A{dataA}</p>
</div>
);
};
const FormB = (props) => {
const { dataB, setDataB } = props;
useEffect(()=>{ console.log('Form B was just rendered')})
return (
<div>
<input onChange={(e) => setDataB(e.target.value)}></input>
<p>Input Form B{dataB}</p>
</div>
);
};
export function BigForm (props) {
const [dataA,setDataA] = useState()
const [dataB,setDataB] = useState()
return (
<div className="App">
<FormA dataA={dataA} setDataA={setDataA}></FormA>
<FormB dataB={dataB} setDataB={setDataB}></FormB>
</div>
);
}
Edit:
For some reason, it is my intention to set the state in parent, instead of having the child to held its own state, because at the end, I need to aggregate the data from all other forms for other purpose.
.Memo is not what I want too coz in my real examples, it is not working due to there is other complexity preventing it to work.
There are a few ways to optimize re-rendering in React. The first way is shouldComponentUpdate(). This is a function that is called by React before a component is re-rendered. If shouldComponentUpdate() returns false, then the component will not be re-rendered.
The second way to optimize React components is to use PureComponent. PureComponent is a React component that is similar to Component, but it implements shouldComponentUpdate() with a shallow comparison of props and state. This means that if two values are ===, then PureComponent will not re-render the component.
The third way to optimize React components is to use React. memo(). React.memo() is a higher order component that allows you to memoize the result of a component so that it doesn't have to be re-rendered unless the props or state change.

Prevent re-render when sending function prop from functional component

When sending props to a PureComponent or a functional component, you can optimize performance by using props that don't change for every render, which will prevent the component from re-rendering.
When using class components this is simple:
class Component extends React.Component {
render() {
return <List createRows={this.createRows}/>;
}
createRows = () => this.props.data.map(dataToRow);
}
Given List being either a PureCompoment or a functional component, the createRows prop will never cause a re-render of List.
But if the Component is a functional component, this is no longer possible:
function Component(props) {
return <List createRows={createRows}/>;
function createRows() {
return props.data.map(dataToRow);
}
}
Since createRows is created every time Component renders, the prop will change, causing a re-render of List every time Component is re-rendered. This can cause a big loss in performance. Notice also that the createRows cannot be placed outside the functional component, since it is dependent on the data prop of List.
Now, with the introduction on Hooks, it is possible to hold the createRows in a useState hook:
function Component(props) {
const [ createRows ] = useState(() => () =>
props.data.map(dataToRow);
);
return <List createRows={createRows}/>;
}
Since the createRows is saved in a state hook, it will not change with each render, and no re-render of List will occour, like we want.
However, this seems more like a hack than a solution.
What is best practice for sending a function prop from a functional components to a child component, without causing unnecessary re-renders of the child component?
useCallback hook exists exactly to solve this problem. I advise you to carefully read the official guide to hooks, it pretty much answers all possible questions
function Component(props) {
const createRows = useCallback(() =>
props.data.map(dataToRow);
), []); // provide dependencies here
return <List createRows={createRows}/>;
}
This is the purpose of useCallback. You can find more details in some of my related answers below.
Trouble with simple example of React Hooks useCallback
What is the intension of using React's useCallback hook in place of useEffect?
React Hooks useCallback causes child to re-render

Need to update state on stateless component using react-redux

I have a component that is connected to a store using react-redux (I have shortened my code for simplicity sake):
const Address = (props) => {
const { userAddresses, removeAddress } = props
let { showEdit } = props
return (
<div>
{userAddresses.map(address => (
<div key={address.id}>
<p>{address.name}</p>
<Button onClick={removeAddress.bind(this, address)}>Delete</Button>
</div>
))}
<Button
onClick={() => { showEdit = true }}>
Add new
</Button>
{showEdit ? (
// show some edit stuff here!!!
): null}
</div>
)
}
const mapState = state => {
return {
userAddresses: state.account.userAddresses,
showEdit: false
}
}
const mapDispatch = (dispatch) => {
return {
addAddress: address => dispatch(addUserAddress(address)),
removeAddress: address => dispatch(removeUserAddress(address)),
}
}
export default connect(mapState, mapDispatch)(Address)
When you click the button (Add new), a form is supposed to popup (marked as show some edit stuff here!!!). I know this can be easily done if Address was a state component. However, I need to use react-redux, and as far as I know, you have to use a stateless component to use react-redux. Can anyone point me in the right direction?
No, you do not "have to use a stateless/function component" to use React-Redux!
connect accepts both class components and function components (and even "special" React components like React.memo()), and it doesn't matter whether they use component state (or hooks) inside or not.
Also, as a side note, you can simplify your code using the "object shorthand" form of mapDispatch:
const mapDispatch = {
addAddress : addUserAddress,
removeAddress : removeUserAddress
}
(Note that that could be even shorter if your prop names were named the same as the functions.)
Keeping to strictly use redux, you should have another action to dispatch when the user clicks the button. Then, a reducer will update the value of the showEdit property, which will cause a re-render of your stateless component allowing you to conditionally render the editing form.
But, this is an information (the visibility or not of the editing form) not useful to the rest of your application, so it could be the case to transform your component into a stateful one and track the showEdit property in the local state.
A third option could be the use of useState hook, but it depends on the version of React you have in your project, because they are currently in alpha...

Using refs with conditional rendering

I have a problem with ref and conditional rendering.
I would like to focus an input tag when I click on a button tag.
Basically, I have this simplified code.
class App extends React.Component {
textInput
constructor(props) {
super(props)
this.state = {isEditing: false}
this.textInput = React.createRef()
}
onClick = () => {
this.setState({isEditing: !this.state.isEditing})
this.textInput.current.focus();
}
render () {
let edit = this.state.isEditing ?
(<input type="text" ref={this.textInput} />)
: ""
return (
<div>
<button onClick={this.onClick}>lorem </button>
{edit}
</div>
);
}
}
When I click on the button, the input tag is displayed but the ref textInput is still set to null. Thus I can't focus the input.
I found some workaround like:
set autoFocus property in the input tag
hide the input tag with css when isEditing == false
But actually it is a very basic pattern and I would like to know if there is a clean solution.
Thank you
TL;DR:
Change this:
this.setState({isEditing: !this.state.isEditing})
this.textInput.current.focus();
to this:
this.setState(previousState => ({isEditing: !previousState.isEditing}), () => {
this.textInput.current.focus();
});
Update: Functional Components / Hooks
It's been asked in the comments how to do this with useState and functional components. Rafał Guźniczak's answer explains it, but I wanted to provide a bit more explanation and a runnable example.
You still don't want to read state immediately after setting it, but instead of using a second argument callback to setState, you need to run some code after the state is updated and the component has re-rendered. How do we do that?
The answer is useEffect. The purpose of effects are to synchronize external "things" (for example: imperative DOM things like focus) with React state:
const { useEffect, useRef, useState } = React;
const { render } = ReactDOM;
function App(props) {
const [isEditing, setIsEditing] = useState(false);
const textInputRef = useRef(null);
const toggleEditing = () => setIsEditing(val => !val);
// whenever isEditing gets set to true, focus the textbox
useEffect(() => {
if (isEditing && textInputRef.current) {
textInputRef.current.focus();
}
}, [isEditing, textInputRef]);
return (
<div>
<button onClick={toggleEditing}>lorem </button>
{isEditing && <input type="text" ref={textInputRef} />}
</div>
);
}
render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Details:
You're running into a common problem many people run into with React, which is the assumption that setting state is synchronous. It's not. When you call setState, you're requesting that React update the state. The actual state update happens later. This means that immediately after the setState call, the edit element hasn't been created or rendered yet, so the ref points to null.
From the docs:
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.
Thank a lot for your answer #rossipedia. I was wondering if I can do it with hooks.
And apparently you can't pass second parameter to useState setter as in setState. But you can use useEffect like this (note second parameter in useEffect):
const [isEditing, setIsEditing] = React.useState(false);
React.useEffect(() => {
if (isEditing) {
textInput.current.focus();
}
}, [isEditing]);
const handleClick = () => setIsEditing(isEditing);
And it worked! ;)
Source: https://www.robinwieruch.de/react-usestate-callback/

setState doesn't update the state immediately [duplicate]

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
The useState set method is not reflecting a change immediately
(15 answers)
Closed 8 months ago.
I would like to ask why my state is not changing when I do an onClick event. I've search a while ago that I need to bind the onClick function in constructor but still the state is not updating.
Here's my code:
import React from 'react';
import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import BoardAddModal from 'components/board/BoardAddModal.jsx';
import style from 'styles/boarditem.css';
class BoardAdd extends React.Component {
constructor(props) {
super(props);
this.state = {
boardAddModalShow: false
};
this.openAddBoardModal = this.openAddBoardModal.bind(this);
}
openAddBoardModal() {
this.setState({ boardAddModalShow: true }); // set boardAddModalShow to true
/* After setting a new state it still returns a false value */
console.log(this.state.boardAddModalShow);
}
render() {
return (
<Col lg={3}>
<a href="javascript:;"
className={style.boardItemAdd}
onClick={this.openAddBoardModal}>
<div className={[style.boardItemContainer,
style.boardItemGray].join(' ')}>
Create New Board
</div>
</a>
</Col>
);
}
}
export default BoardAdd
Your state needs some time to mutate, and since console.log(this.state.boardAddModalShow) executes before the state mutates, you get the previous value as output. So you need to write the console in the callback to the setState function
openAddBoardModal() {
this.setState({ boardAddModalShow: true }, function () {
console.log(this.state.boardAddModalShow);
});
}
setState is asynchronous. It means you can’t call it on one line and assume the state has changed on the next.
According to React docs
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Why would they make setState async
This is because setState alters the state and causes rerendering. This
can be an expensive operation and making it synchronous might leave
the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better
UI experience and performance.
Fortunately setState() takes a callback. And this is where we get updated state.
Consider this example.
this.setState({ name: "myname" }, () => {
//callback
console.log(this.state.name) // myname
});
So When callback fires, this.state is the updated state.
You can get mutated/updated data in callback.
For anyone trying to do this with hooks, you need useEffect.
function App() {
const [x, setX] = useState(5)
const [y, setY] = useState(15)
console.log("Element is rendered:", x, y)
// setting y does not trigger the effect
// the second argument is an array of dependencies
useEffect(() => console.log("re-render because x changed:", x), [x])
function handleXClick() {
console.log("x before setting:", x)
setX(10)
console.log("x in *line* after setting:", x)
}
return <>
<div> x is {x}. </div>
<button onClick={handleXClick}> set x to 10</button>
<div> y is {y}. </div>
<button onClick={() => setY(20)}> set y to 20</button>
</>
}
Output:
Element is rendered: 5 15
re-render because x changed: 5
(press x button)
x before setting: 5
x in *line* after setting: 5
Element is rendered: 10 15
re-render because x changed: 10
(press y button)
Element is rendered: 10 20
Live version
Since setSatate is a asynchronous function so you need to console the state as a callback like this.
openAddBoardModal(){
this.setState({ boardAddModalShow: true }, () => {
console.log(this.state.boardAddModalShow)
});
}
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props. For instance, suppose we wanted to increment a value in state by props.step:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
Think of setState() as a request rather than an immediate command to
update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
Check this for more information.
In your case you have sent a request to update the state. It takes time for React to respond. If you try to immediately console.log the state, you will get the old value.
The above solutions don't work for useState hooks.
One can use the below code
setState((prevState) => {
console.log(boardAddModalShow)
// call functions
// fetch state using prevState and update
return { ...prevState, boardAddModalShow: true }
});
This callback is really messy. Just use async await instead:
async openAddBoardModal(){
await this.setState({ boardAddModalShow: true });
console.log(this.state.boardAddModalShow);
}
If you want to track the state is updating or not then the another way of doing the same thing is
_stateUpdated(){
console.log(this.state. boardAddModalShow);
}
openAddBoardModal(){
this.setState(
{boardAddModalShow: true},
this._stateUpdated.bind(this)
);
}
This way you can call the method "_stateUpdated" every time you try to update the state for debugging.
Although there are many good answers, if someone lands on this page searching for alternative to useState for implementing UI components like Navigation drawers which should be opened or closed based on user input, this answer would be helpful.
Though useState seems handy approach, the state is not set immediately and thus, your website or app looks laggy... And if your page is large enough, react is going to take long time to compute what all should be updated upon state change...
My suggestion is to use refs and directly manipulate the DOM when you want UI to change immediately in response to user action.
Using state for this purspose is really a bad idea in case of react.
setState() is asynchronous. The best way to verify if the state is updating would be in the componentDidUpdate() and not to put a console.log(this.state.boardAddModalShow) after this.setState({ boardAddModalShow: true }) .
according to React Docs
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately
According to React Docs
React does not guarantee that the state changes are applied immediately.
This makes reading this.state right after calling setState() a potential pitfall and can potentially return the existing value due to async nature .
Instead, use componentDidUpdate or a setState callback that is executed right after setState operation is successful.Generally we recommend using componentDidUpdate() for such logic instead.
Example:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = {
counter: 1
};
}
componentDidUpdate() {
console.log("componentDidUpdate fired");
console.log("STATE", this.state);
}
updateState = () => {
this.setState(
(state, props) => {
return { counter: state.counter + 1 };
});
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.updateState}>Update State</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
this.setState({
isMonthFee: !this.state.isMonthFee,
}, () => {
console.log(this.state.isMonthFee);
})
when i was running the code and checking my output at console it showing the that it is undefined.
After i search around and find something that worked for me.
componentDidUpdate(){}
I added this method in my code after constructor().
check out the life cycle of react native workflow.
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
Yes because setState is an asynchronous function. The best way to set state right after you write set state is by using Object.assign like this:
For eg you want to set a property isValid to true, do it like this
Object.assign(this.state, { isValid: true })
You can access updated state just after writing this line.

Categories

Resources