fseb
fseb

Reputation: 135

Is Redux Thunk Middleware really needed in a React Hooks based App?

In a React App designed using mostly hooks. I don't see the need for the redux-thunk middleware, or am I missing something?

Redux-thunk approach

const Component = () => {
const dispatch = useDispatch()
const user = useSelector(state => state.user)

useEffect(() => {
  dispatch(getUserFromApiAndDispatch())
}, []) 

return <div>{user}</div>
}

Or just simply

const Component = () => {
const dispatch = useDispatch()
const user = useSelector(state => state.user)

useEffect(() => {
  getUserFromApi().then(({data}) => dispatch({type: 'SET_USER'; payload: data.user})
}, []) 

return <div>{user}</div>
}

Upvotes: 1

Views: 3532

Answers (3)

trn450
trn450

Reputation: 351

I realize this is an old post, but the question is likely to come up many times over and figured I'd add an additional perspective.

The way you phrased your question, the answer is definitively 'no'. You do not need Thunks to to make async AJAX calls and do any associated data mutation and pre-processing you might do in a Thunk.

In my mind, the question is: do Thunks offer any notable benefit? I personally do not think so. In fact, I think they introduce unnecessary complexity and take away from the elegant nature of React development. But, I'll attempt to play devils advocate here.

Redux helps us with state management in single page applications where, without Redux, state management in nested components can be complicated using the React framework. For example, if you've got a child component that's n levels deep and you need to return data to the parent, you've got to back-propagate (prop drilling) that data up n layers, and that's both ugly and cumbersome.

By having a singleton object which maintains state app state in a way that's accessible to all components of the application, we avoid that scenario.

In my opinion, the major benefit to thunks is that we can keep the state logic and the fetch logic for that component (usually reduced to a Redux slice) in a single file whose entire purpose is state management. There's a bit of elegance to this in theory.

With that, when dispatching a thunk one still has to write at least two lines of code. One to reference the useDispatch hook, and another to actually dispatch the update. And, from a readability perspective, this is no "cleaner" and no less code than using a custom hook. And, people will argue over whether or not it makes sense to organize the code such that fetch logic and state management logic is associated the same way they'll argue over where to keep your type definitions. So, IMO, localizing the fetch logic to the slice is a weak argument at best.

Importantly, the createAsyncThunk API is a little bit clumsy to work with. Doing things like getting state, accessing dispatch methods, etc. feel like some strange mash-up of react-redux and the vanilla redux library. Except, you don't actually have access to anything from the Redux library. And, there are some very useful hooks in the React library with respect to data access. And, then you're also obligated to implement 'extraReducers'. The redux slice/store, the async thunk, and the extra reducers feel somewhat disconnected or discontinuous.

If you choose to write a custom hook, you can continue to write code in a way that is clearly React code and the relationship between the AJAX call and the various React Hooks one might look to leverage is quite clear. You can use the hooks that react-redux library supplies that are clearly intended for use with React components. That is, if you decided to integrate a hook like useMemo or useCallback for performance optimization, it naturally integrates into a hook whereas if you choose to do that in association with a Thunk it's not so clear and there are third-party libraries (read: yet another dependency for your project) which were designed to tackle this particular problem. And, the way AJAX operations and state management are connected to the component are all easy to discern. In addition, custom hooks are amazingly powerful tools that also allow developers to be very creative. And, I would argue a developer will grow far more learning how to master custom hooks than they will writing thunks.

Using Thunks

// MyComponent.tsx
const MyComponent = () => {
  const dispatch = useDispatch()
  const onSomeAction = () => {
    dispatch({action});
  }
  return <>{/*stuff*/}</>
}
// mySlice.ts

const mySlice = createSlice({
  name: 'my-slice',
  initialState,
  reducers: {
    // my reducers
  }
  extraReducers: (builder) => {
    builder.addCase(myThunk.pending, (state, action) => {
     // update state
    }
    builder.addCase(myThunk.rejected, (state, action) => {
     // update state
    }
    builder.addCase(myThunk.fulfilled, (state, action) => {
      // update state
    }
  }
});

export const myThunk = createAsyncThunk(
  'my-slice/myThunk',
  async (data: SomeType, { getState }): Promise<SomeOtherType> => 
  {
    const someValueImWatching = (getState() as MyRootStateType).someSlice.someProperty
    
   // do something with someValueImWatching

   // implement the rest of the thunk
  }

Using Custom Hooks

// MyComponent.tsx
const MyComponent = () => {
  const dispatch = useDispatch()
  const lazyFetch = useMyCustomHook(data: SomeType);
  const onSomeAction = async () => {
    const res = await lazyFetch();
    dispatch(setMyData(res.data))
  }
  return <>{/*stuff*/}</>
}

function useMyCustomHook(data: SomeType) {
   const someValueImWatching = useSelector(s => s.someSlice.SomeProperty)

   const lazyFetch = async () => {
    // do something with some value I'm watching
    // do something with the data
    // implement the rest of the hook
   };
   return lazyFetch;
}
// mySlice.ts

const mySlice = createSlice({
  name: 'my-slice',
  initialState,
  reducers: {
    setMyData: (state, action) => {
      state.data = action.payload;
    }
  }
});

Upvotes: 1

phry
phry

Reputation: 44336

It depends. Of course, you can do all that without thunks. Getting the current state asynchronously would be a bit more dirty (in a thunk you can just call getState) but it is doable.

The question is: what do you lose?

And you lose mainly a concept. The strength of Redux itself is that it creates a data flow outside of your components. Your components just dispatch an event, and logic outside the component happens. Your component later gets a new state and displays it.

By pulling stuff like data fetching into your component, you lose that benefit. Instead of a store that does it's thing and components that do their thing, you now have components that need to know about the data layer and about store internals (in cases where many different actions should be dispatched in a given order).

Logic moved back into your components and you get a mess.

If you just use a thunk, all your component does is dispatch(userDetailsPageDisplayed({ userId: 5 })) and after a while, it gets all the data.


Going a little bit off-topic: you are writing a very outdated style of Redux here. Modern Redux does not use switch..case reducers, immutable reducer logic or ACTION_TYPES. Also you should usually not build dispatched actions in the component. I'd recommend you to read the official Redux tutorial. And then maybe give the Redux Style Guide a read, for more context and recommendations (we recommend event-type actions, not setter-style ones for example).

Upvotes: 5

Musaddik Rayat
Musaddik Rayat

Reputation: 127

Actually no. redux-thunk is just a convention of handling asynchronous tasks. You can easily do that with function or methods. Back in the days, when react only had class based components, it was not possible to use a functionality multiple times in our application. But this problem got solved with Stateful Function Components and Hooks. So, you really don't need thunk.

Upvotes: 0

Related Questions