Formik values not updating with state

rwold picture rwold · May 21, 2019 · Viewed 12.6k times · Source

Here's the template for a form I'm writing with Formik and react-bootstrap. I'm finding a very strange error: if I initialise my state with dummy data in the constructor, it works fine; but if I call setState with the exact same data in componentDidMount to simulate an API call, it breaks horribly.

Specifically, I find that the state variable alertVehicles array can have non-zero length, but the corresponding Formik values.alertVehicles variable can be empty. Right now, the form as written renders no checkboxes. If I use alertVehicles instead of values.alertVehicles in my guard clause, then it blows up with an error Cannot read property 'selected' of undefined.

import React, {Component} from 'react';
import Form from 'react-bootstrap/Form';
import { Formik } from 'formik';
import Button from 'react-bootstrap/Button';


class Alerts extends Component {
    constructor(props) {
        super(props);
        this.loadAlertData = this.loadAlertData.bind(this);
        this.state = {
            alertRecipient: {},
            alertId: '',
            alertVehicles: []
        }
    }

    componentDidMount(){
        this.loadAlertData();
    }

    loadAlertData(){
        // this will be an API call eventually, obviously.
        // if this is initialised in the constructor then everything works!
        this.setState( {
            alertRecipient: {
                name: 'Rob',
                simNumber: '0123456789',
            },
            alertId: 1,
            alertVehicles: [
                {id: 1, vrn: 'vehicle A', selected: true },
                {id: 2, vrn: 'vehicle B', selected: false },
                {id: 3, vrn: 'vehicle C', selected: true }
            ]
        })
    }

    render() {
        const { alertRecipient, alertId, alertVehicles } = this.state;
        return (
            <>
                <Formik
                    initialValues={{ alertRecipient, alertId, alertVehicles }}
                    onSubmit={ values => {
                            window.alert(JSON.stringify(values))
                        }
                    }
                    render={({values, handleChange, handleSubmit}) => (
                        <Form onSubmit={handleSubmit}>
                            <Form.Label>Name</Form.Label>
                            <Form.Control
                                type="text"
                                name="alertRecipient.name"
                                value={values.alertRecipient.name}
                                onChange={handleChange}
                            />
                            <Form.Label>Phone number</Form.Label>
                            <Form.Control
                                type="text"
                                name="alertRecipient.simNumber"
                                value={values.alertRecipient.simNumber}
                                onChange={handleChange}
                            >
                            </Form.Control>
                            <Form.Label>Vehicles</Form.Label>
                            {
                                //get an error if we just use alertVehicles.length here??
                                values.alertVehicles.length === 0 ? null : alertVehicles.map((veh, index) => (

                                    <Form.Check type="checkbox"
                                                key={veh.id}
                                                label={veh.vrn}
                                                name={`alertVehicles[${index}].selected`}
                                                checked={values.alertVehicles[index].selected}
                                                onChange={ handleChange }
                                    />
                                ))
                            }
                            <Button type="submit">Save</Button>
                        </Form>
                    )
                    }
                />
            </>
        )
    }

}

export default Alerts;

I don't understand

  1. Why the code works when I set my dummy data in the constructor but not in componentDidMount
  2. Why values.alertVehicles doesn't appear to be in sync with alertVehicles.

Thanks in advance for any help.

Answer

rwold picture rwold · May 22, 2019

For some reason this is Formik's default behaviour, and you need to supply the enableReinitialize prop to override it: https://github.com/jaredpalmer/formik/issues/811