Lab - Add a Redux Slice

Plan: B2B Developer

Lesson 20 of 25 · 20 min

Introduction

In this exercise, we’ll practice the use of Redux state in the Buyer Portal. The state we’ll be adding and testing with won’t be useful just yet, but we’ll make use of it in a future exercise.

In this lab, you will:

  • Create a custom Redux slice to store global state
  • Test setting and retrieving a state value

Setup

This exercise continues where you left off previously. Make sure the dev server is started in your Buyer Portal project:

yarn dev

If you need a fresh start, you can follow the instructions below to set up a new project complete with previous exercise code.

  1. Copy the project with the appropriate tag.
degit https://github.com/bigcommerce-edu/lab-b2b-buyer-portal.git#e-slice-pre <directory-name>
  1. Install dependencies.
cd <directory-name>
yarn install
  1. Run the dev server.
yarn dev

Remember!

Script Manager in your store control panel must contain appropriate header/footer scripts to load the Buyer Portal from your local environment on the appropriate port.

Revisit the initial setup lab for full details on setting up your environment.

Exercise Reference Copy

If it’s helpful to compare your own code as you go, you can clone or download the completed version of this labs as a reference.

Redux State Lab Snapshot

Global State - The CRM Token

In a future exercise, we’ll be simulating fetching support case data from an external CRM. What we’ll ultimately need in this scenario is an authentication token for that separate API, which is appropriate to store globally in the user’s session state, just like the B2B GraphQL token.

In these steps, we’ll set up a custom Redux slice to store this value, as well as test the appropriate entry points for setting it when the application is initialized and retrieving it in the right component.

Step 1: Create a Slice and Hook

Remember, all file paths referenced in these exercises are relative to apps/storefront/src in your Buyer Portal project.

  1. Open store/slices/crm.ts and add the appropriate interfaces and slice definition.
crm.ts
export interface CRMState {
crmToken: string;
}
const initialState: CRMState = {
crmToken: '',
};
const crmSlice = createSlice({
name: 'crm',
initialState,
reducers: {
setCrmToken: (state, { payload }: PayloadAction<string>) => {
state.crmToken = payload;
},
},
selectors: {
selectCrmToken: (state) => state.crmToken,
},
});

Our slice is very simple, containing only a single string value: crmToken.

  1. Add exports of the slice’s actions, selectors, and reducer.
crm.ts
const crmSlice = createSlice({
...
});
export const {
setCrmToken,
} = crmSlice.actions;
export const {
selectCrmToken,
} = crmSlice.selectors;
export default persistReducer({ key: 'crm', storage: storageSession }, crmSlice.reducer);

Breaking Down the Redux Slice

The code we’ve put in place is doing a few things:

  • Slice initialization: createSlice is used to create our unique slice with:
    • An initial state (containing an empty token value).
    • Reducer functions. We have only one, accepting a payload with a new token value and returning a new state.
    • Selector functions. Again, we have only selectCrmToken, used to retrieve the current state value.
  • Exports: The file exports important objects that outside code will need in order to manage the slice’s state. Notice that each of these values is found on a property of the slice that was initialized.
    • Actions: Functions that can be dispatched to update the slice’s state.
    • Selectors: The same selector functions defined in the slice initialization.
    • The reducer: A single reducer from the slice contains all the individual reducers, and this in turn will be combined with the reducers of the app’s other slices.
  • Persistence: The redux-persist library is used in the reducer export to persist this slice’s state to the browser’s session storage.

Immutable State

Recall that global state must be immutable, meaning that the state value cannot be changed directly. The syntax in the setCrmToken reducer appears to be doing just that, but this is part of what Redux does under the hood. Redux handles cloning a brand new state object with the new value, allowing us to use a more straightforward syntax in the reducer.

Including the Slice in the Store

Now we need to update a couple of global files to include our new slice with the rest of the app’s slices.

  1. Open store/index.ts and add an export for the crm slice. This will allow any code to import the slice’s actions and selectors directly from @/store.
store/index.ts
export * from './slices/storeInfo';
export * from './slices/crm';
export { reducer };
  1. Open store/reducer.ts and add crm to the combineReducers call.
store/reducer.ts
import crm from './slices/crm';
export const reducer = combineReducers({
...
crm,
});

Set Up a Hook

We can use our slice’s selector anywhere in our code, along with the useAppSelector hook from @/store, to retrieve the current value.

We’re going to take this a step further by encapsulating this in our own unique hook. Our code will then be able to use this hook without concerning itself with the details of how the state is retrieved.

  1. Open shared/service/crm-bff/useCrmToken.ts and implement the hook.
useCrmToken.ts
import { useAppSelector, selectCrmToken } from "../../../../../../store";
export default function useCrmToken() {
const crmToken = useAppSelector(selectCrmToken);
return crmToken;
}

Step 2: Get and Set the State Value

The details of fetching the CRM token will be handled in a future exercise, but for now we’ll use a static value in the appropriate entry points for setting and retrieving the state.

  1. Open App.tsx. This is the central entry point for the application and where we will ultimately initialize the CRM token.
  2. Add an import of the appropriate action.
App.tsx
import { setCrmToken } from '../../../../../../store';
  1. Just before the return, add a React side effect to set the test value.
App.tsx
export default function App() {
...
useEffect(() => {
storeDispatch(setCrmToken('test token'));
}, [storeDispatch]);
return (
<>
...
</>
);

Notice we’re using storeDispatch, the global store dispatch function, along with our unique action.

  1. Open pages/Overview/components/RecentOrders.tsx. This is the component where we’ll eventually be using the token.
  2. Add the hook call and a side effect to log the token value.
RecentOrders.tsx
export default function RecentOrders({
...
}: OrdersProps) {
const crmToken = useCrmToken();
...
useEffect(() => {
if (!startLoad || !loading) return;
...
}, [startLoad, loading]);
useEffect(() => {
if (!crmToken) return;
console.log('crmToken', crmToken);
}, [crmToken]);
const orderColumns = [
...
];
...
}

You should be able to observe the functionality with your browser developer tools, with the Overview page open:

  • View your console to see the test value being logged.
  • The persistence of the value should also be evident in the browser’s session storage. Look for the key “persist:crm”.

Full Exercise Code