immerjs / use-immer

Use immer to drive state with a React hooks
MIT License
4.04k stars 92 forks source link

[Feature request] Dispatch thunks in useImmerReducer ? #81

Closed cbdeveloper closed 3 years ago

cbdeveloper commented 3 years ago

Hey everyone, thanks for making this package.

It would be nice to be able to also dispatch thunks from the useImmerReducer hook.

Instead of dispatching an action, we would dispatch an async function, just like in redux-thunk.

It would really help with async actions involving API calls.

Is it possible?

mweststrate commented 3 years ago

@cbdeveloper could you provide a minimal example in a code sandbox to explain what you are looking for / why you want async support? In general using async updaters is a footgun, as explained here https://immerjs.github.io/immer/async (see also the vid). TBH I regret adding that to Immer in the first case :) It is best to do all the async work first, and then call the updater in the end.

cbdeveloper commented 3 years ago

@mweststrate Thanks for your reply. I have some form states where I'm usually handling with Redux (toolkit implements immer produce by default). I'm doing it in Redux because it's very convenient to decouple state update logic and form UI. It's a blog edit form and you can see the preview for post you are writing right next to it. It's a fully controlled form.

The form state is actually a local state, so it shouldn't necessarily be on my Redux store. So started looking for a way to move it out of my Redux store, and that's when I came upon this package. It's so convenient to build the UI and simply dispatch actions and thunks from it. And to have a formSlice where all the state update is handled.

The main reason I need thunks is on my async actions, which currently are:

The image upload goes something like this:

import { ACTIONS as A } from "./formSlice"

const UPLOAD_POST_THUMBNAIL = ({ file } : UploadPostThumbnailParams)
: AppThunk => {
  return async (dispatch,getState) => {
    try {
      dispatch(A.UPLOAD_POST_THUMBNAIL_START());
      const src = await uploadImageToStorage(file,"blog");
      dispatch(A.UPLOAD_POST_THUMBNAIL_SUCCESS({src, sizeKb}));
    }
    catch(err) {
      dispatch(A.UPLOAD_POST_THUMBNAIL_FAILURE({error: err}));
    }
  };
};

Not sure this is the best way, but it works. And it feels like the nicest way of keeping the upload status in sync with my form.

mweststrate commented 3 years ago

Would you mind setting it up in a sandbox, I'm don't have experience with RTK so I don't know how the dispatch above and implementation below relate :). I'd expect if you have an event handler, that calls your async action, but passes the sync dispatch as argument, it should work fine?

cbdeveloper commented 3 years ago

It should be something like this:

https://codesandbox.io/s/old-water-f6w8s?file=/src/App.tsx

Besides the dispatch, every thunk also get access to a getState() function which can be used to get the current state from the store.

mweststrate commented 3 years ago

Sorry, I don't really get the question yet, and don't see an useImmer in the example. I suspect it is better to file this issue at React's useReducer itself, as we basically follow the same api, with just the convenience of applying produce to the reducer.