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

Allow to unwrap async thunks automatically #775

Closed
bisubus opened this issue Oct 26, 2020 · 11 comments · Fixed by #970
Closed

Allow to unwrap async thunks automatically #775

bisubus opened this issue Oct 26, 2020 · 11 comments · Fixed by #970
Milestone

Comments

@bisubus
Copy link

bisubus commented Oct 26, 2020

It makes sense that the result of async thunk is wrapped by default and points made in #618 are valid. However, users could be allowed to change this behaviour if they need to reduce verbosity:

try {
  result = unwrapResult(await dispatch(asyncAction()));
  ...

The suggestion is to add an option that will apply unwrapResult automatically:

let asyncAction = createAsyncThunk(
  'asyncAction',
  ...,
  { unwrap: true }
);
@msutkowski
Copy link
Member

msutkowski commented Oct 26, 2020

Hey there! Just chiming in, but you can do this really easily without library modifications if you want to. I'd think most folks would prefer this route as well as you can better handle errors and it's very transparent that this could potentially throw.

// hooks.js
export const useUnwrapAsyncThunk = () => {
  const dispatch = useDispatch();
  return useCallback((asyncThunk) => {
    return dispatch(asyncThunk).then(unwrapResult);
  }), [dispatch]);
};

// component.js
const unwrap = useUnwrapAsyncThunk();

unwrap(yourAsyncThunk());

@msutkowski
Copy link
Member

msutkowski commented Oct 27, 2020

Following up with a TS version of this. Thanks to @phryneas for helping with this 🥇

import { useCallback } from 'react';
import { AsyncThunkAction, unwrapResult } from '@reduxjs/toolkit';
import { useAppDispatch } from '@root/store';

export const useUnwrapAsyncThunk = () => {
  const dispatch = useAppDispatch();
  return useCallback(
    <R extends any>(asyncThunk: AsyncThunkAction<R, any, any>): Promise<R> =>
      dispatch(asyncThunk).then(unwrapResult),
    [dispatch]
  );
};

@tomasbruckner
Copy link

@msutkowski hi, is there simple solution when you are not using hooks?

Same goes for default serializeError function

@markerikson
Copy link
Collaborator

@tomasbruckner can you clarify what you're trying to do?

@tomasbruckner
Copy link

@markerikson there are 2 cases I would like to achieve.

1st is that I want to unwrap every createAsyncThunk (same as the original author).

But I am using connected components instead hooks.

So instead of

await asyncAction().then(unwrapResult);

I would like to have

await asyncAction();

2nd requirement is that I want to access the original axios error if asyncAction throws and I don't want to define custom serializeError for each createAsyncThunk or rejectWithValue in each payloadCreator body.

I understand that I am not using Redux best practices, but we are migrating 100k lines of TS project from redux to redux toolkit and we are not able to rewrite the whole logic in each component.

@phryneas
Copy link
Member

phryneas commented Feb 8, 2021

You can access the original error message by passing in a serializeError that just passes through the original value at thunk creation.

So you would define

const myThunk = createAsyncThunk(
  "something", 
  () => { /*...*/ },
  { serializeError: x => x }
)

And if you don't want to do that every time, you can put that { serializeError: x => x } into a variable you define once and add it everywhere.

As for automatically unwrapping, you could just change

const mapDispatchToProps = {
  foo: myThunk
}

to

function unwrapped = thunk => (...args) => thunk(...args).then(unwrapResult);

const mapDispatchToProps = {
  foo: unwrapped(myThunk)
}

Yes, those are small hacks that will make you add a little more code. But given that coming from 100k lines redux+TS code you'll probably come down to 25k lines RTK code, that little extra should not hurt too much.

@flaviuse
Copy link

flaviuse commented Feb 12, 2021

Following up with a TS version of this. Thanks to @phryneas for helping with this 🥇

export const useUnwrapAsyncThunk = () => {
  const dispatch = useAppDispatch();
  return <R extends any>(
    asyncThunk: AsyncThunkAction<R, any, any>
  ): Promise<R> => dispatch(asyncThunk).then(unwrapResult);
};

Hey fellow googlers, if like me you had problems of infinite loops when using that code with dependencies array don't forget to memoize the callback :

export const useUnwrapAsyncThunk = () => {
 return useCallback(
   <R extends any>(asyncThunk: AsyncThunkAction<R, any, any>): Promise<R> =>
     store.dispatch(asyncThunk).then(unwrapResult),
   [],
 );
};

Note : I use directly store.dispatch instead of declaring a useAppDispatch() but you can add both if you don't want to unwrap everytime

@defint
Copy link

defint commented Mar 17, 2021

Is there any chance to do this feature with the ability to auto-unwrap action? We have a very ridiculous code with the custom wrapping of all places on the project.

@markerikson
Copy link
Collaborator

markerikson commented Mar 17, 2021

@defint please see the previous discussions linked from #910 (comment)

I'm tentatively open to the idea, but it's not an immediate priority for us to work on atm. If you or someone else would like to open a PR that makes the changes, we can look at that.

@phryneas
Copy link
Member

@markerikson @defint generally though, this is a change that probably requires a lot of extra code and extra complexity for (seeing the many viable workaroungs in this topic alone) little gain.
The TypeScript types would almost duplicate.

Even implementing something like await dispatch(async(3, { unwrap: true })) has significant complexity overhead: 94c67a2

What would be thinkable and what we are trying out in rtk-query at the moment would be the ability to
await dispatch(async(3)).unwrap(). That would actually be possible to implement with very little overhead.

@markerikson
Copy link
Collaborator

Ah, yes, I like that idea!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants