Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: easier ways to get state of the enclosing form on context #4037

Closed
jedwards1211 opened this issue May 21, 2018 · 8 comments
Closed
Labels
c:context relating to context

Comments

@jedwards1211
Copy link
Contributor

jedwards1211 commented May 21, 2018

I often want to make generic components that I can plunk into any form to show stuff like the form's submission status and error. For instance:

<SubmitStatus
  submitting={submitting}
  submittingText={submittingText}
  submitSucceeded={submitSucceeded}
  submitSucceededText={submitSucceededText}
  submitFailed={submitFailed}
  error={error}
/>

But this is a PITA to use because I have to either

  • Pass in the submitting, submitSucceeded, submitFailed, and error injected into each one of my forms (who's got time for that?)
  • do reduxForm({form: 'ugh'})(SubmitStatus) (still too much work. I'm lazy and make no apologies for it!)

redux-form puts everything I need to know on context and in redux state, so I accept nothing less than a component that magically fetches those props with no extra effort on my part:

<SubmitStatus
  submittingText={submittingText}
  submitSucceededText={submitSucceededText}
/>

I wrote some elegant utilities to de-suck this situation. Perhaps you may be interested in some or all of their functionality in a PR. Here is what the resulting code looks like:

// @flow

import * as React from 'react'
import {connect} from 'react-redux'
import {isSubmitting, hasSubmitSucceeded, hasSubmitFailed, getFormError} from 'redux-form'
import createFormSelector from './createFormSelector'
import FormContext from './FormContext'
import SubmitStatus from './SubmitStatus'
import type {Props} from './SubmitStatus'

class SubmitStatusContainer extends React.Component<Props> {
  ConnectedSubmitStatus = connect(createFormSelector({
    submitting: isSubmitting,
    submitSucceeded: hasSubmitSucceeded,
    submitFailed: hasSubmitFailed,
    error: getFormError,
  }))(SubmitStatus)

  render(): ?React.Node {
    const {ConnectedSubmitStatus} = this
    return (
      <FormContext>
        {({form}) => <ConnectedSubmitStatus {...this.props} form={form} />}
      </FormContext>
    )
  }
}

Here is the FormContext render props component:

// @flow

import * as React from 'react'
import PropTypes from 'prop-types'
import type {ReactContext, Context} from 'redux-form/lib/types'

export type Props = {
  +children: (_reduxForm: Context) => React.Node,
}

const FormContext = ({children}: Props, {_reduxForm}: ReactContext): React.Node => children(_reduxForm)
FormContext.contextTypes = {
  _reduxForm: PropTypes.object.isRequired,
}

export default FormContext

And here is the magic createFormSelector function:

// @flow

import mapValues from 'lodash.mapvalues'
import {formValueSelector} from 'redux-form'
import {createSelector, createStructuredSelector} from 'reselect'

/**
 * This may look silly but it makes selecting parts of redux-form state not suck.
 *
 * Example:
 *
 * import createFormSelector, {value} from './createFormSelector'
 * import {isSubmitting, hasSubmitSucceeded, hasSubmitFailed, getFormError} from 'redux-form'
 *
 * const selectStatus = createFormSelector({
 *   submitting: isSubmitting,
 *   submitSucceeded: hasSubmitSucceeded,
 *   submitFailed: hasSubmitFailed,
 *   error: getFormError,
 *   foo: value('foo'),
 *   bar: value('bar'),
 * })
 *
 * selectStatus(state, {form: 'myForm'})
 */
export default function createFormSelector<S, IP: Object, OP: Object>(
  selectors: $ObjMap<OP, <V>(V) => ((form: string) => (state: S) => V)>,
  options: {
    +selectForm?: ?(state: S, props: IP) => string,
  } = {}
): (state: S, props: IP) => OP {
  const selectForm = options.selectForm || ((state: S, {form}: any) => form)
  const selectSelector = createSelector(
    selectForm,
    (form: string) => createStructuredSelector(mapValues(selectors, selector => selector(form)))
  )
  return (state: S, props: IP) => selectSelector(state, props)(state)
}

export function value<S>(field: string): (form: string) => (state: S) => any {
  return (form: string) => {
    const selector = formValueSelector(form)
    return (state: S) => selector(state, field)
  }
}
@danielrob danielrob added the c:context relating to context label May 22, 2018
@danielrob
Copy link
Collaborator

That great type of developer laziness that leads us to spend hours and great effort to perfect our code ⭐️✅.

My tendency would be to start with implementing #3246, and then users can pair that with the various already available selectors.

Then implementing your (effectively) createFormNameBindingStructuredSelector function could be a user-land option if needed. Unconvinced it'd be suitable as a part of redux-form itself... not sure that it's a common enough case.

@jedwards1211
Copy link
Contributor Author

does #3246 really need to be a HOC? Could just be a render props component.

@danielrob
Copy link
Collaborator

Good point, following the recent trend on that one seems beneficial - a render props component would be great. Would you be willing to raise a PR for that? I'm also increasingly convinced on second read that your createStructuredFormSelector / createFormSelector is pretty awesome. @erikras @gustavohenke, thoughts?

@jedwards1211
Copy link
Contributor Author

Yeah, I'll make a PR for the render props comp!

@jedwards1211
Copy link
Contributor Author

I made the PR!
As far as my createStructuredFormSelector thing, since it depends on reselect and redux-form doesn't, I should just release it in a separate package.

@danielrob
Copy link
Collaborator

@jedwards1211 thanks again for this, very awesome. With #4038 merged, I'll go ahead and close this ;).

@erikras
Copy link
Member

erikras commented Jun 12, 2018

Fix released in v7.4.0.

@lock
Copy link

lock bot commented Jun 12, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Jun 12, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
c:context relating to context
Projects
None yet
Development

No branches or pull requests

3 participants