Redux Toolkit Basics. Learn Modern Redux Fast
Redux & Redux Toolkit
Redux-toolkit is the modern way to write redux. If you haven't used Redux Toolkit before, I suggest you go read the Getting Started Guide, and at minimum you should understand createSlice, createAction, createAsyncThunk from the API.
Mandatory documentation:
In order for you to fully grasp the power of Redux Toolkit, go checkout these docs:
1. Redux toolkit - Usage with Typescript. Read this twice
Usage With TypeScript | Redux Toolkit
2.createAsyncThunk:
createAsyncThunk | Redux Toolkit
3.createSlice:
Folder structure
We define an entity as a slice of state. For example, a user entity, a post entity, a comment entity, etc. Each entity will have its own slice of state, and its own reducer, actions, and selectors.
**Note:**The directory entity does not exist in the actual project, it is a generalisation in order to showcase the structure of files.
store / //directory where all state management logic is stored
  modules / //directory where all entities are stored
  entity /
  slice.ts.actions.ts // File where the slice and reducer are defined // File where all the actions are defined
selectors.ts // File where all the selectors are defined
index.ts // File for exports management
To avoid circular dependencies inside slices DON'T declare actions inside the slice file as unknown behaviour might occur. Even though this is not specified in the docs, once your project grows, you will encounter this problem if you declare everything in the same file.
Usage
Let's say that in the future, a new type of entity will be used: Monument and users can access, search and save monuments in their profile. Let's first define the type of a monument
enum ApiStatus {
  Idle = 'idle',
  Loading = 'loading',
  Success = 'success',
  Error = 'error',
}
interface Monument {
  id: number // primary ID property
  name: string
  apiStatus: ApiStatus // status of fetching entity from the server
}
1. Create the entity directory
First off we create a new directory named monuments in the app/store/modules directory.
2. Create a new slice for the entity
Than we create a slice.ts :
import {createSlice} from '@reduxjs/toolkit';
import {isErrorPayload} from '@utils/typeGuards';
import {ApiStatus} from 'types';
/**
* Type for dictionary where we keep fetched monuments
* for fast lookup by Id.
* In this dictionary the key is the id and the value is
* the monument object with that id. Example: Get monument
* with id 5 => localDictionary[5] (the monument object or undefined)
*/
type LocalDictionary = Record<number, Monument | undefined>
interface MonumentState {
 byId: number[] // an array to store monuments by Id
 local: LocalDictionary
 apiStatus: ApiStatus // status for fetching many monuments at once
 error?: string //optional error to display in case of fetching errors
}
const initialState: MonumentState = {
	byId: []
  local: {}
  apiStatus: ApiStatus.Idle,
};
const monumentSlice = createSlice({
  name: 'monument',
  initialState, // defined on top
  reducers: {},
  extraReducers: (builder) => {
    // This is where we will define most reducers for extra type safety
  },
});
export default monumentSlice.reducer;
3. Define an action
Now that we have the slice. Let's define anaction that fetches a monument from the server and stores it in the slice. We will usecreateAsyncThunk to do that. We now create an actions.ts file inside the monuments directory. If the code below seems confusing please read the link from above on createAsyncThunk:
import * as monumentAPI from '@api/monument' // functions for fetching monument data
import { createAsyncThunk } from '@reduxjs/toolkit'
import { ThunkApi } from 'types'
/* Async action to fetch a monument from the server */
export const fetchMonument = createAsyncThunk<Monument, number, ThunkApi>(
  'monuments/fetchOne',
  async (monumentId, thunkAPI) => {
    try {
      // fetch from the server
      const monument = await monumentAPI.getById(monumentId)
      // return with status success
      return {
        ...monument,
        apiStatus: ApiStatus.Success,
      }
    } catch (error) {
      return thunkAPI.rejectWithValue(error)
    }
  }
)
4. Handle action inside slice
We defined the action but we haven't specified how should redux handle this action. If you stop here and call dispatch(fetchMonument(14)) the theoretical Api Call will launch but the returned data would not be stored in the global state. Let's define that behaviour back in slice.ts :
import * as actions from './actions'
import {addNewValuesToLocalState} from '@utils/redux';
...
const monumentSlice = createSlice({
  name: 'monument',
  initialState, // defined on top
  reducers: {},
  // Use extraReducers, not reducers. Why?
  // With this style of adding reducers the (state, action) pair
  // is already typed with the definitions from the action file.
  // This is faster AND safer than declaring how to handle the action in the
  // reducers prop as their you need to define the TS types yourself.
  extraReducers: (builder) => {
    // handle what the state should be when the monument starts fetching
		builder.addCase(actions.fetchMonument.pending, (state, action) => {
      //mark the individual monument as pending
      // get the id that was passed as parameter to action
      const {arg: monumentId} = state.meta
      // retrieve existent monument object or create new one
      const monument: Partial<Monument> = {
        ...(state.local[monummentId] ?? {},
        apiStatus: ApiStatus.Pending
      }
      //save value with pending to state
      addNewValuesToLocalState(state.local, [monument as Monument])
    }
  },
});
...
So now when we dispatch fetchMonument , our local reducer will mark that particular monument as being in a loading state. Next let's define what happens when the monument has been fetched:
...
 extraReducers: (builder) => {
    // handle what the state should be when the monument starts fetching
		builder.addCase(actions.fetchMonument.pending, (state, action) => {
    ...
    }
    // handle what the state should be when the monument has been fetched
    builder.addCase(actions.fetchMonument.success, (state, action) => {
       const fetchedMonument = action.payload; // already typed because of builder function
       // we already set apiStatus to ApiStatus.Success when we
       // returned from the action
       addNewValuesToLocalState(state.local, [fetchedMonument])
    }
}
Great! Now we have a functional action that fetches a resource and stores it in the global state to be used by components.
But what happens if an error occurs?!
Let's define that case too:
import {isPayloadError} from '@utils/typeGuards';
...
 extraReducers: (builder) => {
    // handle what the state should be when the monument starts fetching
		builder.addCase(actions.fetchMonument.pending, (state, action) => {...}
    // handle what the state should be when the monument has been fetched
    builder.addCase(actions.fetchMonument.success, (state, action) => {...}
    // handle what the state should be when the api call failed
    builder.addCasse(actions.fetchMonument.rejected, (state, action) => {
      const {payload} = action;
      // type guard to determine if there is actually an error message
      if (isPayloadError(payload) {
	      state.error = payload.message
      }
       // get the id that was passed as parameter to action
      const {arg: monumentId} = state.meta
      // retrieve existent monument object or create new one
      const failedMonuent: Partial<Monument> = {
        ...(state.local[monummentId] ?? {}),
        apiStatus: ApiStatus.Error // mark fetching failed
      }
      //save value with pending to state
      addNewValuesToLocalState(state.local, [failedMonuent as Monument])
    })
}
Awesome! We have a fully usable redux action to fetch individual monuments.
5. Getting data from redux
Now that we have our action let's use it in a MonumentScreen . What we want to do:
- Get the monument from the redux store
 - If the monument is not fetched, fetch it
 - Display monument information
 
Let's start:
import * as React from 'react'
import {useSelector, useDispatch} from 'react-redux'
import * as monumentActions from '@redux/modules/monuments/actions'
interface ScreenProps {
  monumentId: number
}
function MonumentScreen(props: ScreenProps) {
    const {monumentId} = props;
    // Get the current value from the store
    const monument = useSelector<StoreState>(state =>
          state.monuments.local[monumentId] ?? {apiStatus: ApiStatus.Idle})
    // Helpful status indicators
    const isIdle = monument.apiStatus === ApiStatus.Idle;
    const isLoading = monument.apiStatus === ApiStatus.Loading;
    const isError = monument.apiStatus === ApiStatus.Error;
    const isSuccess = monument.apiStatus === ApiStatus.Success;
    // redux dispatch prop
    const dispatch = useDispatch();
    // If the monument is not fetched dispatch the action to fetch it
    React.useEffect(() => {
      if (isIdle) {
          dispatch(monumentActions.fetchMonument(monumentId))
        }
    }, [isIdle, dispatch]
    // Placeholder for loading monument
    if (isLoading) {
       return <LoadingMonument />
    }
    // Feedback in case of error
    if (isError) {
       return <Text>Something went wrong...</Text>
    }
    // fetch was succesfull!
    return <View>
             <Text>{monument.name}</Text>
           </View>
}
Let's evaluate what happens in the code above.
- In the first moment we try to extract from redux a monument that doesn't exist so we get back 
{apiStatus: ApiStatus.Idle} - In the 
useEffecthook we check if the status is idle we dispatch the fetch action - The 
pendingpart of action triggers so now our data extracted from redux is{apiStatus: ApiStatus.Loading} - The api finishes loading from the server and the 
successpart offetchMonumentis triggered in redux. ThereforeapiStatusis equal toApiStatus.Successmaking theisSuccessvalue be true and display the final data to the screen. Yaaay! 
Great! Everything works ok. But we have a problem with code reusability and the fact that we can just extract all that logic into a useMonument hook. Let's do that:
export function useMonument(monumentId: number) {
    // Get the current value from the store
    const monument = useSelector<StoreState>(state =>
          state.monuments.local[monumentId] ?? {apiStatus: ApiStatus.Idle})
    // redux dispatch prop
    const dispatch = useDispatch();
    // Helpful status indicators
    const isIdle = monument.apiStatus === ApiStatus.Idle;
    const isLoading = monument.apiStatus === ApiStatus.Loading;
    const isError = monument.apiStatus === ApiStatus.Error;
    const isSuccess = monument.apiStatus === ApiStatus.Success;
    // If the monument is not fetched dispatch the action to fetch it
    React.useEffect(() => {
      if (isIdle) {
        dispatch(monumentActions.fetchMonument(monumentId))
      }
    }, [isIdle, dispatch]
    return {
      monument,
      isIdle,
      isError,
      isSuccess
    }
}
As you can see, no code has been rewritten, just copy and paste into a hook value.
And now our MonumentScreen becomes:
import {useMonument} from '@hooks/monument'
...
function MonumentScreen(props: ScreenProps) {
  const {monumentId} = props;
  const {monument, isLoading, isError, isIdle, isSuccess} = useMonument(
    monumentId,
  );
  // Placeholder for loading monument
  if (isLoading || isIdle) {
    return <LoadingMonument />;
  }
  // Feedback in case of error
  if (isError) {
    return <Text>Something went wrong...</Text>;
  }
  // fetch finished successfully!
  if (isSuccess) {
    return (
      <View>
        <Text>Hello from {monument.name}</Text>
      </View>
    );
  }
  // (Optional) Safety throw if none of the above cases match
  throw new Error('This part of the function should not be reachable');
}
Looks way better now. And now we have a reusable hook to use throughout the application and by other developers to further speed up development. Great!
Keep this structure in mind for other async redux actions you might be building as it is easier to read and highly reusable.