1.2.0 • Published 3 years ago

vdr-reduxtoolkit-observable v1.2.0

Weekly downloads
1
License
ISC
Repository
github
Last release
3 years ago

vdr-reduxtoolkit-observable

This library facilitates the use of redux-observable with redux-toolkit.

Installation

  • npm
    • npm install vdr-reduxtoolkit-observable
  • yarn
    • yarn add vdr-reduxtoolkit-observable

prerequisite

To know more about redux-toolkit and redux-observable, please visit:

https://redux-toolkit.js.org/

https://redux-observable.js.org/

Why to use this library

Basically, when we interact with an api, for each api call, we have 2 steps:

  • call the api
  • manage the api call result (success or error)

With redux-observable, we can convert these steps to 3 actions: Redux action | Description --- | --- fetch |this action will trigger the api call fetchSuccess |manage a successfull api call fetchError |manage a failed api call

These steps exist for each interaction with the api. If we have a read and an update api call, we will have 6 actions ( 3 for the read api call, 3 for the update api call)

  1. read_fetch ==> call the api
  2. read_fetchSuccess ==> manage the sucess call
  3. read_fetchError ==> manage the error call
  4. update_fetch ==> call the api
  5. update_fetchSuccess ==> manage the sucess call
  6. update_fetchError ==> manage the error call

The library will create and manage for you these steps. All you have to do is to create the reducers for these actions.

More information

AbstractEpicReducer

This class represents your action reducers (fetch, fetchSuccess, fetchError) + the api call. By extending the class you will implement 4 methods:

MethodDescription
fetchApiCallcall the api
fetchreducer for the fetch action
fetchSuccessreducer for the fetch sucess action
fetchErrorreducer for the fetch error action

AbstractSingleReducer

This class represents the reducer for a single action.

You can of course create an action which does not interact with redux-observable. You will use this class in order to manage a single action. By extending the class you will implement one method:

MethodDescription
consumeActionreducer for your action

AbstractStateSlice

This class represents your state slice. It will generate the actions and the epics. By extending the class you will implement two methods:

MethodDescription
getSliceNamedefine the reduxtoolkit slice name
getInitialStateget the inital state

The constructor will take 2 parameters.

constructor(
  (epicReducers: Array<AbstractEpicReducer<State, any, any>> | null),
  (singleReducers: Array<AbstractSingleReducer<State, any>> | null),
);

Use case

1. Api information

In our example, we will work with the rest point https://jsonplaceholder.typicode.com/posts/{postId}

This request will return a json with 4 information ( userId, id, title, body)

/*  https://jsonplaceholder.typicode.com/posts/1 */
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

2. App requirements and behaviours

a) the user will search a post by encoding a digit

b) In addition to displaying the post information, we want to inform the user of the request status. The request status can be:

Request statusDescription
pendingrequest on going
successrequest succeded
errorrequest failed

c) If an error occured, we want to display the error message.

d) Via a reset button the user will be able to clear the screen

3. Actions

Regarding the app requirements and behaviours, we will have four actions:

Action TypeAction PayloadDescription
post/read_fetchpost id to searchthis action will trigger the request call
post/read_fetchSuccesscontains the post informationthis action will be triggered if the request succeds
post/read_fetchErrorrequest has failedthis action will be triggered if the request fails
post/resetnullthis action will be triggered when the user wants to clear the post information

Action types are generated by the library.

4. Designing the state

In addition of the post information, we will add:

  • the request status information.
  • the error message if the request has failed
State fieldDescription
userIdpost information
idpost information
titlepost information
bodypost information
errorMessageerror message to display if the request fails
fetchStatusrequest status
/* represents the request possibles status */
enum EFetchStatus {
  NONE = 'NONE',
  PENDING = 'PENDING',
  SUCCESS = 'SUCCESS',
  ERROR = 'ERROR',
}

/* represents the request success result */
interface PostInfos {
  userId: Number;
  id: Number;
  title: string;
  body: string;
}

/* represents our slice state */
interface PostState extends PostInfos {
  errorMessage: string;
  fetchStatus: EFetchStatus;
}

5. Manage the api call

When the user searches a post

1 an action post/read_fetch is triggered 2,7 the reducer consumes the action 3,8 the reducer updates the state 4 check if a side effect exists for the action 5 make the api call 6a Request succeded -> trigger a post/read_fetchSuccess action 6b Request failed -> trigger a post/read_fetchError action

alt text

The redux-observable part is managed by the library.

To manage our api call, we will use the class AbstractEpicReducer. By using this class we will implement four methods:

MethodDescriptionschema step
fetchApiCallcall the api5
fetchreducer for the fetch action3
fetchSuccessreducer for the fetch sucess action8
fetchErrorreducer for the fetch error action8
 extends AbstractEpicReducer<A,B,C>
State fieldDescription
Astate type
Bfetch action payload type
Cfetch action success payload type
// represents the post/read_fetch action payload */
interface ReadPostFetchPayload {
  postId: number;
}

// represents the post/read_fetchSuccess action payload */
interface ReadPostFetchSuccessPayload extends PostInfos {}

// /!\ This interface is provided by the library
// represents the post/read_fetchError action payload
interface FetchErrorPayload {
  errorCode: string;
  errorMsg: string;
  errorData: any;
}

export class ReadPostReducer extends AbstractEpicReducer<PostState, ReadPostFetchPayload, ReadPostFetchSuccessPayload> {
  // Schema step 5 - make the api call
  fetchApiCall = (payload: ReadPostFetchPayload) => findPostById(payload.postId);

  // Schema step 3 - reducer for the post/read_fetch action
  fetch = (state: PostState, payload: ReadPostFetchPayload) => ({
    ...POST_STATE_INTITIAL_STATE,
    fetchStatus: EFetchStatus.PENDING,
  });

  // Schema step 8 - reducer for the post/read_fetchSuccess action
  fetchSuccess = (state: PostState, payload: ReadPostFetchSuccessPayload): PostState => ({
    ...payload,
    errorMessage: '',
    fetchStatus: EFetchStatus.SUCCESS,
  });

  // Schema step 8 - reducer for the post/read_fetchError action
  // /!\ FetchErrorPayload is provided by the library
  fetchError = (state: PostState, payload: FetchErrorPayload) => {
    return {
      ...POST_STATE_INTITIAL_STATE,
      errorMessage: payload.errorMsg,
      fetchStatus: EFetchStatus.ERROR,
    };
  };
}

6. Manage the reset button with AbstractSingleReducer

When the user clicks on the reset button.

1 an action post/reset is triggered 2 the reducer consumes the action 3 the reducer updates the state

alt text

To manage this action, we will use the class AbstractSingleReducer. By using this class we will implement one method:

MethodDescriptionschema step
consumeAction(state, actionPayload)reducer for the action3
 extends AbstractSingleReducer<A,B>
State fieldDescription
Astate type
Baction payload type
export class ResetPostReducer extends AbstractSingleReducer<PostState, null> {
  consumeAction = (state: PostState, payload: null): PostState => POST_STATE_INTITIAL_STATE;
}

7.Create your state slice

Now that we have our reducers, we want to glue them with our store. We will use the AbstractStateSlice

const SLICE_NAME = 'post';
class PostStateSlice extends AbstractStateSlice<PostState> {
  // all our actions will be prefixed by this value
  getSliceName = () => SLICE_NAME;
  // provide the initial state
  getInitialState = () => POST_STATE_INTITIAL_STATE;
}

const postSlice = new PostStateSlice(
  [
    //provide a list of AbstractEpicReducer
    // the library will generate and manage three actions  post/read_fetch, post/read_fetchSuccess, post/read_fetchError,
    new ReadPostReducer(SLICE_NAME, 'read'),
  ],
  [
    //provide a list of  AbstractSingleReducer
    new ResetPostReducer('reset'), // the library will generate an action 'post/reset'
  ],
);

//get the reducers in order to add them to the state
export const postReducers = postSlice.slice.reducer;

//get the epics in order to add them to the state epic
export const postEpic = postSlice.epic;

//action to trigger to load a post
export const getActionFindPostById = (x: ReadPostFetchPayload) => postSlice.slice.actions.read_fetch(x);

//action to trigger to reset the state
export const getActionResetPost = () => postSlice.slice.actions.reset(null);
 extends AbstractStateSlice<A>
State fieldDescription
Astate type

8. Configure our store (app.store.ts)

We configure our store with the epic and reducer generated by the class PostStateSlice

const epicMiddleware = createEpicMiddleware();

const appStore = configureStore({
  devTools: true,
  reducer: combineReducers({
    post: postReducers,
  }),
  middleware: [epicMiddleware],
});

epicMiddleware.run(combineEpics(postEpic));

export default appStore;
  1. Add the store to our application (index.tsx)
ReactDOM.render(
  <Provider store={appStore}>
    <App />
  </Provider>,
  document.getElementById('root'),
);

9. Screenshots

alt text

alt text

alt text

10. Code

Full code is here: https://github.com/valentino-drappa/vdr-reduxtoolkit-epic-example

#react #redux-toolkit #axios #redux-observable #rxjs