Search code examples
mobxmobx-reactmobx-react-form

Set form values with mobx-react-form using object/props


I'm using mobx-react-form and I need to fill a form with default values pulled from an object in my store. Unfortunately, if I try to use FormModel.$("email").set(object.email); inside my component mobx complains that I can't modify observed objects outside of an action and I exceed maxdepth.

Specifically my code looks like this (some details removed for clarity)

import React from 'react';
import ReactDOM from "react-dom"
import { observer } from "mobx-react-lite"
import validatorjs from 'validatorjs';
import MobxReactForm from 'mobx-react-form';

    const fields = [{
      name: 'email',
      label: 'Email',
      placeholder: 'Email',
      rules: 'required|email|string|between:5,25',
      // value: user.email,
    }, … 
    ]

const FormModel = new MobxReactForm({ fields }, { plugins, hooks }); //nothing exception here standard plugins/hooks

const UserForm = observer(({open, onClose, object}) => {  //My component…object has fields with names email…

  FormModel.$("email").set(object.email); //This works fine if I replace object.email with "foo" 

  return (<MobxInput field={FormModel.$("email")} fullWidth />);
});

export default UserForm;

Yes, I've checked the object has the appropriate fields (it's just a bare object passed in from parent …not even an observable object in this case).

My first approach was to simply put everything inside UserForm and simply fill the values in fields from object but when I do this typing doesn't work in the resulting form (I suspect that mobx is trying to observe an object created inside that observer and that doesn't work).

The problem is I need to use the same form sometimes with data suppled by a user object from my user store and sometimes with blank values to create a new user and I'm kinda stuck about how to do this now.


Solution

  • First of all, you can't do that:

    const UserForm = observer(({open, onClose, object}) => {
      // This won't really work very well
      FormModel.$("email").set(object.email);
    
      return (<MobxInput field={FormModel.$("email")} fullWidth />);
    });
    

    Because every time you change value in your input your whole UserForm component also rerenders (because it observes FormModel.$("email") value which just changed) and when it rerenders you instantly change new value to your old value from object. I am not sure why exactly you getting maxdepth error, but there might even be endless loop here as you can see in some cases. Modifying anything like that inside render is usually a bad practice. You need to use useEffect at least, or something like that.

    I can't modify observed objects outside of an action

    This happens because you need to do all mutations inside actions by default. You can configure it though, if you don't like it:

    import { configure } from "mobx"
    
    configure({
        enforceActions: "never",
    })
    

    But it is better to stick with it, it might catch some unwanted behaviour.

    I've made quick Codesandbox example with some of your code, it shows how you can make several forms and pass default values to them:

    const UserForm = observer(({ object }) => {
      const [FormModel] = useState(() => {
        const fields = [
          {
            name: 'email',
            label: 'Email',
            placeholder: 'Email',
            rules: 'required|email|string|between:5,25',
            value: object?.email || ''
          }
        ];
    
        return new MobxReactForm({ fields }, { plugins });
      });
    
      return (
        <form onSubmit={FormModel.onSubmit}>
          <input {...FormModel.$('email').bind()} />
          <p style={{ color: 'red' }}>{FormModel.$('email').error}</p>
    
          <button type="submit">submit</button>
        </form>
      );
    });
    

    That is just one of many ways, it all depends what you need in the end.