1
votes

Update: Here is an example Pen

https://codepen.io/anon/pen/vwzGYY?editors=0011

Preface

Based on my research, it seems like I need a completely different approach. Maybe you can suggest one?

Context

I'm using a Redux-Form (technically an older version, but the API's in question seem really stable. We can burn that bridge when we get there.) to set some "filters" for a sort of search results list.

In particular, since I want the pages to be link-able, I'm also setting the form content in the URL query params, via React-Router, or initially setting it on page load via similar mechanism.

The only field so far is "organization_name", a text field, used to set the query param value, and trigger an API request for /endpoint?name={some_name}.

E.g.,

<Field
  name="organization_name"
  component="input"
  type="text"
  placeholder="Organization Name"
  value={value}
/>

I've tried several things, but here's a recent shot:

I'm grabbing reset, change, and other things from default props. I'm passing in a handleSubmit as required.

handleSubmit works correctly, to do some state updating, set/push the URL query params with React Router, and then make a new API call/update display of new results! Woot!

What I want / expect

In the long run, I would like a "reset filters" button that sets all filter values back to defaults (e.g., set the "name" value to empty string), and re-submits the form (thus triggering handleSubmit).

What I first tried to implement was a button, as such:

<button
  name="reset_filters_button"
  type="button"
  onClick={resetAndSubmit}
  disabled={pristine || submitting}
  >
    Clear Search
</button> 

Where resetAndSubmit is defined on the form container as such:

const resetAndSubmit = event => {
  reset();
  handleSubmit();
};

What actually happens... (submit takes precedence over dispatched events?)

Using the Chrome dev tools debugger, I can clearly see that the reset method is called, and returns it's dispatch(...)'d event. However, the form and state values are not updated before handleSubmit() runs and submits the form.

I think this might have to do with the submit event taking priority?

I have also tried something janky, like importing change (default prop for the container) and defining the reset button thus:

<button
  name="reset_filters_button"
  type="button"
  onClick={() => {
    change('organization_name', '');
    methodThatDispatchesSubmitAction();
  }}
  disabled={pristine || submitting}
>
  Clear Search
</button>

Which (if I remove methodThatDispatchesSubmitAction()) works correctly to set the field value back to blank, making the form technically "pristine" again as well.

methodThatDispatchesSubmitAction() (if it's not obvious) is bound on the parent via dispatchToProps, and passed in to the form container, where it uses the "remote submit" suggestion, e.g,

// organization_list_filter == name of the Redux-Form to submit. 
dispatch(submit('organization_list_filter')); 

TL;DR and final question:

How does one properly reset a form and submit its' default/empty values?

Every time I dispatch or directly call Redux Form 'submit', it ends up submitting the form before clearing values from state, or the UI. I have walked through this with a debugger and it's not skipping my call to reset or change. It's like an async/race issue, but I admit I am out of my league in this particular case for sure.

Am I just Straight Up Doing It Wrong?

1

1 Answers

1
votes

It is most definitely a race condition issue (or since we aren't actually dealing with threads, an order of events issue).

The reason using a methodThatDispatchesSubmitAction works when your current example does not, is because a dispatched action has the benefit of reading data directly from the redux store. Your example is not reading from the redux store, it's reading from a property that is passed in. Yes, this property comes from the redux store, but the problem you are seeing is that it hasn't been updated in your component yet.

Bear with me as this next piece is not going to be entirely accurate but it should suffice to explain what you are seeing.

Submit is clicked
    -> Reset action is dispatched
       -> Reducer receives action and returns updated state
    -> Handle submit is fired using values prop (old state data still)
Component is updated with new props from redux state

As you can see, the order of events don't allow for an updated state to be given to the property until our click code has finished running. If you've ever watched a video on the JS Event Loop (I highly recommend it), you'll know that our onClick handle will run in full before any other async operations (or sync operations that come after our click) have a chance to run.

There are good reasons why Components aren't given updated props right away but the primary one is performance. You can see that this order is in fact the problem by wrapping the handleSubmit in an async event that fires immediately (it doesn't actually fire immediately, all other sync/async operations queued before it will finish).

const resetAndSubmit = (event) => {
  reset();
  setImmediate(() => handleSubmit());
}

This changes the order of events as follows:

Submit is clicked
    -> Reset action is dispatched
       -> Reducer receives action and returns updated state
    -> Handle submit is queued on the event loop (not run yet)
Component is updated with new props from redux state
Event loop reaches queued code and runs is
    -> Handle submit is fired using values prop (new state data)

Hopefully, this helps you understand why the problem is occurring. As for solutions to fix it. Obviously, you can queue the handle submit as I've shown above. Another option would the one you've described as using a dispatch to perform the submit. A third option would be to use something a bit heavier like redux-thunk or redux-sagas that tie the resetAndSubmit action into a single dispatch. Although honestly, this is the same as option two, just reduced into a single dispatch. Option four, don't use redux for all your data. Obviously, this fourth option comes with trade-offs but my point being, just because you are using redux in a project doesn't mean every single piece of data needs to be in redux. Though it completely defeats the purpose of redux-forms.

I should also add, you are not alone in being confused by this. When you introduce redux, it messes with how you traditionally think about working with code. Normally you think, I do A then B. But with redux, you do A, wait for A's changes to make it through the system, and then you do B. That's where Sagas or Thunks can be nice. You move more logic to the store to act on the dispatch rather than wait for it to all make its way back down to a component via props.