Redux toolkit create slice with sync and async calls

By : FiveMinute  |  Updated On : 01 Mar, 2021

Redux toolkit create slice with sync and async calls

createSlice

createSlice is a funtion from redux toolkit which accepts a object parameter. As per my ovservation createSlice word is came from slices of cakes, where bunch of slices makes cake same way we create project where bunch of different widgets like header,footer,sidebar,main content etc.. which combines together and create a complete project. createSlice handles all your redux things like types, actions and reducers. In the object parameter it takes few things like initial state, reducer functions and it automatically create actions and types. 

Redux Toolkit really makes redux very easy to understand and implementation. 

Syntax for createSlice : 

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment : (state) {
      state.value++
    },
    decrement : (state) {
      state.value--
    },
    incrementByAmount : (state, action) {
      state.value += action.payload
    },
    extraReducers: {}
  },
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

Parameters Details : 

createSlice takes a single configuration object parameter, with the below options:

function createSlice({
    // A name, used in action types
    name: string,
    // The initial state for the reducer
    initialState: any,
    // An object of "case reducers". Key names will be used to generate actions.
    reducers: Object<string, ReducerFunction | ReducerAndPrepareObject>
    // A "builder callback" function used to add more reducers, or
    // an additional object of "case reducers", where the keys should be other
    // action types
    extraReducers?:
    | Object<string, ReducerFunction>
    | ((builder: ActionReducerMapBuilder<State>) => void)
})

name : A string name for any slice of state. The name value generate constant action type which is used as prefix of type names.

initialStateThis takes initial value (data)  when slice is created.

reducersReducer is a object which takes key value pair, where key generate action type which will show up in the Redux DevTools Extention when they are dispatched. In the entire application if exact same action type is dispatched than corresponding reducer is called and the value take a function which is send to createReducer function create reducer. When we export the actions it return action creator which is dispatched from the application.

export const { increment, decrement, incrementByAmount } = counterSlice.actions
//exported action creator

extraReducers : extraReducers are reducers which handle the external actions. The actions which is created by the createAction or createAsyncThunk. Those actions are not generated by the slice.actions. extraReducers are also known as case reducers.

If two fields from reducers and extraReducers happen to end up with the same action type string, the function from reducers will be used to handle that action type.

Understand some basic functionality of extraReducers.

import { createSlice, createAction } from '@reduxjs/toolkit'
import { createStore, combineReducers } from 'redux'

const incrementBy = createAction('incrementBy')
const decrementBy = createAction('decrementBy')

const counter = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: (state) => state + 1,
    decrement: (state) => state - 1,
    multiply: {
      reducer: (state, action) => state * action.payload,
      prepare: (value) => ({ payload: value || 2 }), // fallback if the payload is a falsy value
    },
  },
  // "builder callback API", recommended for TypeScript users
  extraReducers: (builder) => {
    builder.addCase(incrementBy, (state, action) => {
      return state + action.payload
    })
    builder.addCase(decrementBy, (state, action) => {
      return state - action.payload
    })
  },
})

const user = createSlice({
  name: 'user',
  initialState: { name: '', age: 20 },
  reducers: {
    setUserName: (state, action) => {
      state.name = action.payload // mutate the state all you want with immer
    },
  },
  // "map object API"
  extraReducers: {
    [counter.actions.increment]: (
      state,
      action /* action will be inferred as "any", as the map notation does not contain type information */
    ) => {
      state.age += 1
    },
  },
})

const reducer = combineReducers({
  counter: counter.reducer,
  user: user.reducer,
})

const store = createStore(reducer)

store.dispatch(counter.actions.increment())
// -> { counter: 1, user: {name : '', age: 21} }
store.dispatch(counter.actions.increment())
// -> { counter: 2, user: {name: '', age: 22} }
store.dispatch(counter.actions.multiply(3))
// -> { counter: 6, user: {name: '', age: 22} }
store.dispatch(counter.actions.multiply())
// -> { counter: 12, user: {name: '', age: 22} }
console.log(`${counter.actions.decrement}`)
// -> "counter/decrement"
store.dispatch(user.actions.setUserName('eric'))
// -> { counter: 6, user: { name: 'eric', age: 22} }

Now look how createSlice manages Asynchoronus operations. When we need some data from apis we use createAsyncThunk to handle the async operations.

create a function component Home from where dispatch to get the data.

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { getData } from './homeSlice';

function Home() {

    const dispatch = useDispatch();

    let data = useSelector(state => state.home.data);

    useEffect(() => {
        dispatch(getData()).then((data) => {
            //write code here which is processed after fetching data            
        });
    }, []);

    return (

        <div>
            {
                (data && data.items) ? (
                    data.items.map(function (val, i) {
                        return <Widget data={val} key={i}></Widget>;
                    })
                ) : ""
            }
        </div>
    );
}

export default Home;

Next we have created homeSlice to fetch async data via api.

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from "axios";

export const getData = createAsyncThunk('home/getData', async (params, thunkAPI) => {

    const url = 'https://www.example.com/api/data.json';
    const response = await axios.get(url);
    return response.data;
})

export const homeSlice = createSlice({
    name: 'home',
    initialState: {
        loading: false,
        error: false,
        data: []
    },
    reducers: {},
    extraReducers: {
        [getVideos.pending]: (state) => {
            state.loading = true;
        },
        [getVideos.rejected]: (state) => {
            state.loading = false;
            state.error = true;
        },
        [getVideos.fulfilled]: (state, action) => {
            state.loading = false;
            if (state.data.length < 1) {
                state.data = action.payload;
            } else {
                state.data.items.push(...action.payload.channel.items);
            }
        }
    }
});

export default homeSlice.reducer;

Like above code we can create asynchronous operations.

Also Read : Integrate SSR on react