React + Formik: Use value for nested object

10,625

TL;DR

Change in your Form.Control the prop name to name.en/name.fr


First of all, initialValues is a prop that will be set and won't change unless you pass the prop enableReinitialize. So it isn't good to do this.state.project || { name: { 'en': '' } because it will only assume the first value of that, it can be this.state.project or { name: { 'en': '' }, but you will never know.

Second, to solve your problem, if you look at the docs about handleChange:

General input change event handler. This will update the values[key] where key is the event-emitting input's name attribute. If the name attribute is not present, handleChange will look for an input's id attribute. Note: "input" here means all HTML inputs.

But in your Form.Control you are passing the name attribute as name="name".

So it's trying to update name and not e.g. name.en.

You should change

   <Form.Control
        type="text"
        name="name"
        value={(values['name'] as I18n).en}
        onChange={handleChange}
    />
    // Input for FRENCH text
    <Form.Control
        type="text"
        name="name"
        value={(values['name'] as I18n).fr}
        onChange={handleChange}
    />

To

   <Form.Control
        type="text"
        name="name.en" // correct name
        value={(values['name'] as I18n).en}
        onChange={handleChange}
    />
    // Input for FRENCH text
    <Form.Control
        type="text"
        name="name.fr" // correct name
        value={(values['name'] as I18n).fr}
        onChange={handleChange}
    />

Here is the docs that shows why you should use name.en instead of just name.

Share:
10,625
mrks
Author by

mrks

Hello World.

Updated on June 13, 2022

Comments

  • mrks
    mrks almost 2 years

    I have the following model for my React (TypeScript) app:

    interface IProjectInput {
        id?: string;
        name: string | i18n;
        description: string | i18n;
    }
    
    export interface i18n {
        [key: string]: string;
    }
    
    

    I am using Formik and react-bootstrap to create a new ProjectInput from a Form:

    import { i18n as I18n, ... } from 'my-models';
    
    interface State {
        validated: boolean;
        project: IProjectInput;
    }
    
    /**
     * A Form that can can edit a project
     */
    class ProjectForm extends Component<Props, State> {
        constructor(props: any) {
            super(props);
            this.state = {
                project: props.project || {
                    name: {},
                    description: ''
                },
                validated: false
            };
        }
    
        async handleSubmit(values: FormikValues, actions: FormikHelpers<IProjectInput>) {
            let project = new ProjectInput();
            project = { ...project, ...values };
            console.log("form values", values);
            // actions.setSubmitting(true);
            // try {
            //     await this.props.onSubmit(project);
            // } catch (e) { }
            // actions.setSubmitting(false);
        }
    
        render() {
            const { t } = this.props;
            const getCurrentLng = () => i18n.language || window.localStorage.i18nextLng || '';
            const init = this.state.project || {
                name: {},
                description: ''
            };
    
            return (
                <div>
                    <Formik
                        // validationSchema={ProjectInputSchema}
                        enableReinitialize={false}
                        onSubmit={(values, actions) => this.handleSubmit(values, actions)}
                        initialValues={init}
                    >
                        {({
                            handleSubmit,
                            handleChange,
                            handleBlur,
                            values,
                            touched,
                            errors,
                            isSubmitting,
                            setFieldTouched
                        }) => {
    
                            return (
                                <div className="project-form">
                                    <Form noValidate onSubmit={handleSubmit}>
                                        <Form.Row>
                                            <Form.Group as={Col} md={{span: 5}} controlId="projectName">
                                                <Form.Label>
                                                    {t('projectName')}
                                                </Form.Label>
                                                // Input for ENGLISH text
                                                <Form.Control
                                                    type="text"
                                                    name="name"
                                                    value={(values['name'] as I18n).en}
                                                    onChange={handleChange}
                                                />
                                                // Input for FRENCH text
                                                <Form.Control
                                                    type="text"
                                                    name="name"
                                                    value={(values['name'] as I18n).fr}
                                                    onChange={handleChange}
                                                />
                                            </Form.Group>
    

    So in the end it should look like:

    {
      "name": {
        "en": "yes",
        "fr": "oui"
      },
      "description" : "test",
      ...
    }
    

    My problem is, that the value for the name input stays empty.

    I tried to add const init = this.state.project || { name: { 'en': '' }, in my render or for my state, but this did not do anything.