37

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.

rwold
  • 2,216
  • 1
  • 14
  • 22

3 Answers3

74

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

rwold
  • 2,216
  • 1
  • 14
  • 22
1

It makes sense actually. Most of the time you don't want to clobber what the user may be doing on the form just because the state changed. In fact you would probably want to if the user hasn't touched anything yet but not after.

Either way they give you the flag to control it...

Sean O'Leary
  • 548
  • 5
  • 5
0

Had the same issue. Values need to be in string, It is not taking numbers try converting to string or use template string ``.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 25 '22 at 01:31
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/33479695) – Vojin Purić Dec 26 '22 at 13:54