Search code examples
javascriptreactjsreduxreact-redux-form

How to avoid weird nesting of forms objects using react-redux-forms and custom reducer


I'm attemtping to use react-redux-form to manage my forms but am having issues working out how to inject it into the store when I setup my other reducers.

My store is created as follows

import { createStore, applyMiddleware, compose } from 'redux'

import { browserHistory }   from 'react-router'
import { routerMiddleware } from 'react-router-redux'

import { makeRootReducer }  from '../reducer'

const makeStore = (initialState = {}) => {
  const middleware = [
    routerMiddleware(browserHistory)
    // other middlewares
  ]

  const store = createStore(
    makeRootReducer(),
    initialState,
    compose(
      applyMiddleware(...middleware)
    )
  )
  store.asyncReducers = {}

  return store
}

module.exports = makeStore

My root reducer is as follows

import { combineReducers } from 'redux'
import { routerReducer }   from 'react-router-redux'
import { combineForms }    from 'react-redux-form'

import { initialLoginState } from './components/forms/login'

const makeRootReducer = (asyncReducers) => {
  const customReducers = {
    // other of my own imported reducers
  }

  const forms = {
    login: initialLoginState
  }

  const allReducers = {
    ...customReducers,
    forms: combineForms(forms, 'forms'),
    routing: routerReducer,
    ...asyncReducers
  }

  return combineReducers(allReducers)
}

// mutate the store just this once.
const injectReducer = (store, { key, reducer }) => {
  store.asyncReducers[key] = reducer // eslint-disable-line no-param-reassign
  store.replaceReducer(makeRootReducer(store.asyncReducers))
}

module.exports = {
  makeRootReducer,
  injectReducer
}

and my form is like this

  <Form model='forms.login' onSubmit={login => console.debug(login)}>
    <label>Email</label>
    <Control.text model='.username' />
    <label>Password</label>
    <Control type='password' model='.password' />
    <button type="submit">Login</button>
  </Form>

When I fire up my app and inspect the state I see the forms sitting in the state as follows:

first state example

state:
  forms:
    login:
      username: ''
      password: ''
    forms:
      $form:
        focus: false
        # ... etc
      login:
        $form:
          focus: false
          # ... etc
        username: ''
        password: ''

And the form behaves as expected.

If I change allreducers remove the second 'forms' param, so it looks like this:

  const allReducers = {
    ...customReducers,
    forms: combineForms(forms),
    routing: routerReducer,
    ...asyncReducers
  }

My state looks the same

second state example

state:
  forms:
    login:
      username: ''
      password: ''
    forms:
      $form:
        focus: false
        # ... etc
      login:
        $form:
          focus: false
          # ... etc
        username: ''
        password: ''

but the form itself no longer works.

What I actually want is a state that looks like

state:
  forms:
    login:
      $form:
        focus: false
        # ... etc
      username: ''
      password: ''

without all the weird nested duplication.

Ultimately in my form Component I need to be able to access the form's errors object and I'd rather not have to dig into state.forms.forms.login.$form.errors as that just seems broken to me when I ought to be able to look in state.forms.login.$form.errors.

How ought I inject combinedForms into my combinedReducer along with the other reducers?


Solution

  • In the example you have given, state.forms.login will contain the model values. state.forms.forms.login will contain the react-redux-form specific information about the form/field state.

    This is the expected functionality of react-redux-form. Your confusion may have stemmed from using 'forms' as the model name for your combined forms.