Search code examples
reactjstypescriptcomponentsmaterial-uisetstate

React: SetState does not update visual component


A function component does not perform its visual update when the state changes.

I provide a minimal example of my component where I set a new state but the component does not update in the browser.

import * as React from "react"
import { Button, makeStyles, Grid, TextField, createStyles } from '@material-ui/core'
import ReactDOM from "react-dom";


const styles = makeStyles(theme => createStyles({
  container: {
    display: 'flex',
    flexWrap: 'wrap',
  },
  textField: {
    marginLeft: theme.spacing(1),
    marginRight: theme.spacing(1),
  },
  button: {
    margin: theme.spacing(1),
  }  
}))


export interface RTestData {
    title: string
}

interface Props {
  data: RTestData[]
}

const testData: RTestData[] = [{title: "AA"}, {title: "BB"}, {title: "CC"}, {title: "DD"}, {title: "EE"}]

export const RTestContainer: React.FunctionComponent<Props> = (props) => {
    const [count, setCount] = React.useState(0)
    const [data, setData] = React.useState(props.data[count])

    const classes = styles()

    const handleClick = () => (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
        console.log(`BUTTON click`)
        const target = e.target
        setCount(count + 1)
        setData(props.data[count])
        console.log("data " + JSON.stringify(props.data) + "  ->" + JSON.stringify(data))
    }

    return (
        <Grid container spacing={2}>
            <Grid item xs={12}>
                <RTestForm title={data.title}/>
            </Grid>

            <Grid item>
                    <Button
                    variant="contained"
                    color="primary"
                    className={classes.button}
                    onClick={handleClick()}
                    >
                    Next!!
                </Button>
            </Grid> 
        </Grid>
    )
}

export const RTestForm: React.FunctionComponent<RTestData> = (props) => {
    const [title, setTitle] = React.useState(props.title)

    const handleChange = () => (e: React.ChangeEvent<HTMLInputElement>) => {
        const target = e.target
        const value = target.value
        console.log("Title change ->" + title)
        setTitle(value)
    }

    const classes = styles()

    return (
        <form className={classes.container} noValidate autoComplete="off">
            <Grid container spacing={2}>
                <Grid item xs={12} sm={6} lg={6}>
                    <TextField
                        required
                        label="Title"
                        value={title}
                        onChange={handleChange()}
                        className={classes.textField}
                        margin="normal"
                        variant="filled"
                    />
                </Grid>
            </Grid>
        </form>
    )
}


ReactDOM.render(<div id="root">
        <RTestContainer data={testData}/>
    </div>, document.getElementById('root'));

A click on the button should select and display the next data item, e. g. the textfield displays "AA", "BB" and so on.


Solution

  • On handleClick you change RTestContainer state but it doesn't affect RTestForm state.

    //                 v On handleClick changes `data` state
    <RTestForm title={data.title} />
    
    //  Changing props won't re-initial the state. v
    const [title, setTitle] = React.useState(props.title);
    

    Try adding a listener for props.title change with useEffect:

    export const RTestForm: React.FunctionComponent<RTestData> = props => {
      const [title, setTitle] = React.useState(props.title);
    
      React.useEffect(() => {
        setTitle(props.title);
      }, [props.title]);
    
      return <>...</>
    
    };
    

    Edit Q-58579793-useEffectListener