22

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>
...
ilyo
  • 35,851
  • 46
  • 106
  • 159
  • are you setting state in componentWillReceiveProps. Also calling setState synchronously in componentWillMount won't trigger extra rendering – Shubham Khatri Dec 07 '17 at 12:49
  • @ShubhamKhatri I tried `componentWillMount `, `componentWillUpdate ` and `componentWillReceiveProps ` but none of them worked – ilyo Dec 07 '17 at 12:50
  • 1
    Can you add the relevant code of your componentWillReceiveProps – Shubham Khatri Dec 07 '17 at 12:50
  • @ShubhamKhatri it is the exact same code as the one I pasted with `componentWillMount ` – ilyo Dec 07 '17 at 12:53
  • Typo on handler: ` onChange={this.handleLanguaheChange} `... Edit mode won't allow me to fix unless I edit a further 5 characters – bammmmmmmmm Aug 26 '19 at 13:15
  • `return new Promise.resolve()`, this solved my issue https://stackoverflow.com/questions/39524855/how-to-trigger-off-callback-after-updating-state-in-redux – STEEL May 29 '20 at 10:24

5 Answers5

3

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 }))
pope_maverick
  • 902
  • 8
  • 15
  • 3
    **It's usual to setState in componentWillMount hook. But never set state in componentDidMount.** Never do that. You're not supposed to setState for unmounted component, it may cause memory loss. And there's no issue in setting state in ComponentDidMount. – Siraj Alam Aug 26 '19 at 10:01
2

To accomplish this with React Hooks:

  1. Track previous value with useRef()
  2. 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.

enter image description here

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.

NSjonas
  • 10,693
  • 9
  • 66
  • 92
1

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.

T Porter
  • 1,162
  • 7
  • 15
  • 2
    Yes, so I thought that once the props will update, it will trigger `componentWillReceiveProps` and thus update the state. Am I wrong? – ilyo Dec 07 '17 at 13:08
  • Sorry, what it is the purpose of the component's `state` (updated with `this.setState`) when `react-redux` maps the Redux's `state` to the `props` and not to the component's `state`? When do we use component's `state` then? – tonix Jun 07 '18 at 15:14
  • 1
    @tonix I believe you want to use props to change state externally, and state to change state internally (in relation to the component). – Kin Dec 26 '18 at 19:20
  • @Kin Like changing something which relates exclusively to the component and not the whole application. – tonix Dec 27 '18 at 09:59
0

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'
     }
   }
RIYAJ KHAN
  • 15,032
  • 5
  • 31
  • 53
  • Thanx, what is the difference between using `constructor` and `componentwillMount/reciveProps` in this case? – ilyo Dec 07 '17 at 13:10
  • Please share your render part where you are using it – RIYAJ KHAN Dec 07 '17 at 13:27
  • @ilyo `this.state.language ` is still there. You need to use `this.props.language ` after constructor changes – RIYAJ KHAN Dec 07 '17 at 13:32
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/160717/discussion-between-riyaj-khan-and-ilyo). – RIYAJ KHAN Dec 07 '17 at 13:35
  • I am confused, the whole point of setting the state is so (1) I can use it with forms (2) I will have a fallback value until the server populates the redux store – ilyo Dec 07 '17 at 13:35
0

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.

JESii
  • 4,678
  • 2
  • 39
  • 44