Next.js: gitInitialProps not available when using Redux-Form

Created on 12 May 2017  路  6Comments  路  Source: vercel/next.js

I have a component using redux-from and I'm trying to setup a url route to pass params to edit the object Im passing to it. When I remove the redux-form from the component, getInitialProps works.

Here is my current component:

import React from 'react'
import { initStore } from '../store'
import withRedux from 'next-redux-wrapper'
import { bindActionCreators } from 'redux'
import standardLayout from '../hocs/standardLayout'
import { Field, reduxForm } from 'redux-form'
import checkBox from '../components/inputs/checkbox'
import renderField from '../components/inputs/renderField'
import env from '../config/envConfig'
import { addStore } from '../actions/storesActions'
import { toastr } from 'react-redux-toastr'
import Router from 'next/router'

class addStoreForm extends React.Component {
  static getInitialProps ({ store, res, query }) {
    console.log('query from HOC')
    // console.log(query)
    const post = 'string'

    return { post }
  }

  constructor (props, context) {
    super(props, context)
    this.handleFormSubmit = this.handleFormSubmit.bind(this)
    this.edit = false
    //query setup for edit or add page
    const params = this.props.url.query
    if (params.id) {
      this.edit = true
    }
  }

  componentDidMount () {
    console.log('CDM')
  }

  handleFormSubmit (formProps) {
    this.props
      .addStore(formProps)
      .then(r => {
        toastr.success('Saved', 'Store Saved Successfully!')
        Router.push(`/store?params=${r.slug}`, `/store/${r.slug}`)
      })
      .catch(e => {
        toastr.error('Error:', e)
      })
  }

  render () {
    const { handleSubmit, valid, errorMessage } = this.props
    return (
      <div className='inner'>
        <h2>{this.edit ? 'Edit Store' : 'Add Store'}</h2>
        <form
          className='card'
          onSubmit={handleSubmit(this.handleFormSubmit)}
          encType='multipart/form-data'
        >
          <Field
            name='name'
            type='text'
            component={renderField}
            label='Name:'
          />
          <label htmlFor='description'>Description</label>
          <Field name='description' component='textarea' label='Description:' />
          <ul className='tags'>
            {env.TAGS.map(tag => (
              <Field
                key={tag}
                value={tag}
                name={`tags.[${tag}]`}
                id={tag.toLowerCase().replace(' ', '_')}
                type='checkbox'
                component={checkBox}
                label={tag}
              />
            ))}
          </ul>

          <input
            type='submit'
            className='button'
            value='Save'
            disabled={valid === false ? 'disabled' : ''}
          />

        </form>

      </div>
    )
  }
}

const validate = values => {
  const errors = {}

  if (!values.name) {
    errors.name = 'Required'
  }

  return errors
}

const editStore = reduxForm({
  form: 'signin',
  validate
})(addStoreForm)

const mapDispatchToProps = dispatch => {
  return {
    addStore: bindActionCreators(addStore, dispatch)
  }
}

export default withRedux(initStore, null, mapDispatchToProps)(
  standardLayout(editStore, 'Edit Store')
)

Most helpful comment

@spencersmb My guess, if I'm not mistaking (went trough similar things) is that next tries to call the getInitialProps of the exposed component, but reduxForm doesn't call yout static method. The solution is usually to move the getInitialProps declaration to a higher component, such as standardLayout, or if this hoc does call the base component getInitialProps manually, to the editStore component.

You would end up with something like:

const editStore = reduxForm({
  form: 'signin',
  validate
})(addStoreForm)

editStore.getInitialProps = ({ store, res, query }) {
    console.log('query from HOC')
    // console.log(query)
    const post = 'string'

    return { post }
  }

But for this to work, you need to make sure that standardLayout does declare its own getInitialProps, and calls the wrapped component's own getInitialProps (that is actually something that withRedux does):

const standardLayout = BaseComponent => {
    // The hoc declaration
    static async getInitialProps(context) {

        const childProps = typeof BaseComponent.getInitialProps === 'function'
            ? await BaseComponent.getInitialProps(context)
            : {};
        return {
            ...childProps,
           ...{}, // whatever standardLayout wants to add
            };
    }
}

This last example is inspired by the use of HOC by an example, see for instance how they use getInitialProps to inject some data, while leaving to the page component the ability to expose its own getInitialProps

All 6 comments

Hi @spencersmb thanks for reporting. Is there a way for you to provide a repo or a more comprehensive app to make it easier to test?

thank you

@spencersmb My guess, if I'm not mistaking (went trough similar things) is that next tries to call the getInitialProps of the exposed component, but reduxForm doesn't call yout static method. The solution is usually to move the getInitialProps declaration to a higher component, such as standardLayout, or if this hoc does call the base component getInitialProps manually, to the editStore component.

You would end up with something like:

const editStore = reduxForm({
  form: 'signin',
  validate
})(addStoreForm)

editStore.getInitialProps = ({ store, res, query }) {
    console.log('query from HOC')
    // console.log(query)
    const post = 'string'

    return { post }
  }

But for this to work, you need to make sure that standardLayout does declare its own getInitialProps, and calls the wrapped component's own getInitialProps (that is actually something that withRedux does):

const standardLayout = BaseComponent => {
    // The hoc declaration
    static async getInitialProps(context) {

        const childProps = typeof BaseComponent.getInitialProps === 'function'
            ? await BaseComponent.getInitialProps(context)
            : {};
        return {
            ...childProps,
           ...{}, // whatever standardLayout wants to add
            };
    }
}

This last example is inspired by the use of HOC by an example, see for instance how they use getInitialProps to inject some data, while leaving to the page component the ability to expose its own getInitialProps

@impronunciable here is my current repo: https://github.com/spencersmb/node-master-client
You can get to the page by click on the ADD button in the navigation.

@AugustinLF - Great example I see what you are saying. I was thinking along the same lines, but moving the form into its own component. I'm gonna try that first since I will need to reuse the form and see how that goes.

Thanks! for the help!

That's exactly the case. getInitialProps is called on the exposed component. So a HOC has to be aware of Next.js to properly call getInitialProps and pass the props through.

FWIW here's my little helper for this:

export function withForm(options) {
  return Child => {
    const hoc = reduxForm(options)(Child);
    hoc.getInitialProps = ctx => Child.getInitialProps ? Child.getInitialProps(ctx) : {};
    return hoc;
  };
}

This thread has been automatically locked because it has not had recent activity. Please open a new issue for related bugs and link to relevant comments in this thread.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

olifante picture olifante  路  3Comments

knipferrc picture knipferrc  路  3Comments

lixiaoyan picture lixiaoyan  路  3Comments

swrdfish picture swrdfish  路  3Comments

wagerfield picture wagerfield  路  3Comments