Red Baron
Red Baron

Reputation: 7642

Why is this createAsyncThunk behaving strangely?

I have some issues with this createAsyncThunk from the redux toolkit API

export const getPayments = createAsyncThunk('getPayments/all', async ({ rejectWithValue }) => {
    try {
        const response = await fetch(`/payments`, {
            method: 'GET',
            headers: { 'Content-Type': 'application/json' },
        })
        console.log('res:', response)
        return response
    } catch (e) {
        console.log('ERROR:', e)
        rejectWithValue(e)
    }
})

Whenever I add rejectWithValue as a parameter, it ALWAYS shows as rejected in the redux devtools. and when I remove it, it's ALWAYS fulfilled. what is going on here? I just want to call this function below if there is an error from the fetch? why is it always rejecting it?

EDIT: I am seeing this Cannot destructure property 'rejectWithValue' of 'undefined' as it is undefined. in the response now, which makes sense why it's always rejected, why is this happening though and how do I fix it?

Upvotes: 1

Views: 1874

Answers (1)

HMR
HMR

Reputation: 39250

Here is an example how you can do it, as commented the rejectWithValue of the payload creator is a property of the second argument and the payload creator needs to return the result of the rejectWithValue call, here is an example:

// toggle reject
const reject = ((shouldReject) => () =>
  (shouldReject = !shouldReject))(true);
// test thunk action creator
const testAsyncThunk = createAsyncThunk(
  'some/test',
  //  arg is the argument passed to the action creator, can be ignored if not used
  async (arg, { rejectWithValue }) => {
    console.log('argument passed to action creator:', arg);
    if (reject()) {
      //return result of rejectWithValue call
      return rejectWithValue('rejected');
    }
    return Promise.resolve('resolved');
  }
);

When you dispatch a thunk created with createAsyncThunk the resulting promise will not reject unless you use unwrapResult.

Here is a minimal app demonstrating the behaviour:

import React from 'react';
import ReactDOM from 'react-dom';
import {
  createAsyncThunk,
  unwrapResult,
} from '@reduxjs/toolkit';

import { Provider, useDispatch } from 'react-redux';
import {
  createStore,
  applyMiddleware,
  compose,
} from 'redux';

// toggle reject
const reject = ((shouldReject) => () =>
  (shouldReject = !shouldReject))(true);
// test thunk action creator
const testAsyncThunk = createAsyncThunk(
  'some/test',
  //  arg is the argument passed to the action creator, can be ignored if not used
  async (arg, { rejectWithValue }) => {
    console.log('argument passed to action creator:', arg);
    if (reject()) {
      //return result of rejectWithValue call
      return rejectWithValue('rejected value');
    }
    return Promise.resolve('resolved value');
  }
);

const reducer = (state, { type, payload }) => {
  return state;
};
//creating store with redux devtools and thunk middleware
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  {},
  composeEnhancers(
    applyMiddleware(
      ({ dispatch, getState }) => (next) => (action) =>
        //minimal implementation of thunk middleware
        typeof action === 'function'
          ? action(dispatch, getState)
          : next(action)
    )
  )
);
const App = () => {
  const dispatch = useDispatch();
  return (
    <button
      onClick={() =>
        dispatch(testAsyncThunk('argument passed'))
          .then(
            (resolved) => {
              console.log('action resolved with', resolved);
              return resolved;
            },
            (rejected) =>
              // this never executes because promise returned
              //   by dispatch(tunkaction) will not reject
              console.log('action rejected with:', rejected)
          )
          .then(
            //after unwrap result you have a promise that will
            //  reject
            unwrapResult
          )
          .catch((err) =>
            console.log('rejected with...', err)
          )
      }
    >
      dispatch action
    </button>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Upvotes: 3

Related Questions