dvdzkwsk / react-reformed

Make React forms simple again, see it here:
https://react-reformed.zuko.me/
MIT License
541 stars 32 forks source link

How to implement isSubmiting #4

Closed kolpav closed 8 years ago

kolpav commented 8 years ago

Hello, First of all I want to thank you for such a cool library. I am still trying to wrap my head around it so I was wondering if you could give me advice how would you go about passing isSubmiting state into form from HOC with asumption you will always have onSubmit prop. I would like to keep my LoginForm stateless

Code example of what I am trying to achieve

export default compose(
  connect(
    state => ({
      initialModel: {...}
    }), {
      onSubmit: login
    }
  ),
  reformed(),
  isSubmiting()
)(LoginForm)

const LoginForm = ({
  model,
  bindInput,
  onSubmit,
  isSubmiting
}) => (
  <form onSubmit={onSubmit} >
    <input
      name='username'
      {...bindInput('username'))}
    />
    <Button
      value={isSubmiting
        ? 'Click me'
        : 'I am submiting now'}
    />
  </form>
)
dvdzkwsk commented 8 years ago

Without knowing how your redux state is setup, here are 2 options.

Idea 1

Just use redux state. redux-form passes through your props, so if you track isSubmitting in your redux state you can just pass that in with your connect higher order component.

// imagine your `login` state looks (something) like this:
{
  hasPendingLogin: boolean,
  isAuthenticated: boolean,
  user: ?Object,
}

// then you can just select that pending login state, since that would be tied to your login action
export default compose(
  connect(
    state => ({
      initialModel: {...},
      isSubmitting: state.session.hasPendingLogin,
    }), {
      onSubmit: login
    }
  ),
  reformed()
)(LoginForm)

Idea 2

You could write your isSubmitting middleware as something like this:

// Note: I wrote this directly in github, so there could easily be a syntax error somewhere
const submittable = (WrappedComponent) => {
  return class SubmittableComponent extends React.Component {
    state = {
      isSubmitting: false,
    }

    // we assume that `this.props.onSubmit` is a function that returns a
    // promise. We can proxy the call to that function so that we can hook
    // into the promise to set a loading state.
    _onSubmit = (model) => {
      this.setState({ isSubmitting: true })
      return this.props.onSubmit(model)
        .then((res) => {
          this.setState({ isSubmitting: false })
          return res
        })
        .catch((err) => {
          this.setState({ isSubmitting: false })
          throw err // re-throw so anybody handling this sees it fail correctly
        })
    }

    render () {
      return React.createElement(WrappedComponent, {
        ...this.props,
        // we proxy `this.props.onSubmit`
        onSubmit: this._onSubmit,
        isSubmitting: this.state.isSubmitting,
      })
    }
  }
}

Usage:

export default compose(
  connect(
    state => ({
      initialModel: {...},
    }), {
      onSubmit: login
    }
  ),
  submittable,   // <---------------
  reformed()
)(LoginForm

Note that you could also use this to apply submitError or submitSuccess properties as well, if you wanted. The cool thing about this approach is that the only thing your onSubmit prop must do is return a promise and any form with this wrapper will be able to accurately reflect the submission state.

Personally, if most of my app state was in redux, I'd lean toward the first solution of just leveraging redux for what it's designed to do because you do not want to start duplicating state across the app. If, however, this is not the case, solution #2 means you don't have to worry about creating a bunch of actions and reducers for things that you might not really care about on the global state.

Hope that helps.

kolpav commented 8 years ago

What a quick response :smile: The first approach is what I was doing till now I don't even had to write complicated reducers actions etc. I only stored name of a form currently being submited (I don't need to store form values in redux store as my forms are very simple and few so isSubmiting is only form state I need)

// form reducer //
export const initialState = null
export default handleActions({
  [SET_FORM_SUBMITING]: (state, { payload }) => payload,
  [CLEAR_FORM_SUBMITING]: () => null
}, initialState)
// selectors //
export const isLoginFormSubmiting = state => state === 'loginForm'
export const isRegisterFormSubmiting = state => state === 'registerForm'
// etc...

The second approach is what I wanted and I was so close to figuring out this myself but I could find a way how to proxy the onSubmit. Your solution is perfect thank you!