Skip to content

Sets up Redux boilerplate: actions, reducers and redux-saga for most common redux use cases

License

Notifications You must be signed in to change notification settings

Midnight-Coder/synthetic-redux

Repository files navigation

synthetic-redux

Sets up Redux boilerplate: actions, reducers and redux-saga for common API communication use cases

JavaScript Redux License: MIT semantic-release

Given an action type and reducer key the library generates:

  1. Redux Actions: Success, Error & Reset action types
  2. Reducers: Updates Store with loading, success and error states
  3. Redux-Saga: Generates saga-effect that communicates with API and handles success and error responses

Purpose

An application can have multiple API calls. Redux + Redux-Saga is a popular tool to handle these side effects. The redux bits become repetitive over the numerous API calls. This library The use-case the library solves for is -

  1. API call needs to be made
  2. Success response should be reflected in the Redux Store
  3. Error response should be reflected in the Redux Store
  4. Reset action must be provided to reset the Redux Store
  5. Differentiate between Success and Error responses
  6. Handle exceptions in communication (eg. network error)
  7. Reflect exception in the Redux Store

Synthetic Redux

Provides an abstraction over 3 bits of boilerplate-ish code:

  1. SyntheticAction - Access to success, error & reset actions and their generators
  2. SyntheticReducers - Access to loading, error and success States from Redux Store
  3. SyntheticSaga - Generates redux-saga effect that handles Success & Error API responses

API

Initialize =>

const synRedux = new SyntheticRedux(
  {type: <action>, url: <string>, payload: <any> }, 
  reducerKey
);

Cheatsheet

  1. Kick off synthetic action => synRedux.ignite()

  2. Access synthetic reducer => synRedux.reducer

  3. Access synthetic sagaEffect => synRedux.sagaEffect

  4. Set custom defaults for synthetic action(optional) =>

  synRedux.ignite = (customUrl) => synRedux.actions.ignite({
    url: customUrl,
    errorHandler: (err) => CustomLogger.error(err)
  });

Example Usage

const FETCH_TODOS = 'FETCH_TODOS';
const ADD_TODO = 'ADD_TODO';

export const listTodos = new SyntheticRedux({
  payload: {filter: { isDeleted: false }},
  type: FETCH_TODOS,
  url: '/api/v1/todo/list',
}, 'list');

export const addTodo = new SyntheticRedux({
  type: ADD_TODO,
  url: '/api/v1/todo',
}, 'newTodo');

addTodo.igniteWrapper = payload => addTodo.actions.ignite({ payload });

// RootReducer.js
const RootReducer = combineReducers({
  newTodo: addTodo.reducer,
  todos: listTodos.reducer,
});

// RootSaga.js
export default function* RootSaga() {
  const allSagas = [
    addTodo.sagaEffect,
    listTodos.sagaEffect,
  ]
  yield all(allSagas.map(fork));
}

// React Component
export default TodoList = () => {
  const listResponse = useSelector(state => state.todos.list);
  const addResponse = useSelector(state => state.todos.newTodo)
  const dispatch = useDispatch();
  const onAdd = (payload) => {
      dispatch(addTodo.igniteWrapper(payload));
  };
  
  React.useEffect(() => {
    dispatch(listTodos.ignite());
  }, [dispatch]);
  
  React.useEffect(() => {
      if(addResponse?.success) {
          dispatch(addResponse.reset())
      }
  }, [dispatch])
  
  if (listResponse[CATEGORY_NAMES].IGNITE) {
    return <LoadingState/>;
  } else if (listResponse.error) {
    return <ErrorHandler error={listResponse.error}/>;
  } else {
    return <ShowTodos list={listResponse.list} onAdd={onAdd}/>;
  }
};

// React Component

Customizing building blocks of Synthetic Redux:

Synthetic Actions

Redux requires actions as the basis of asynchronous communication. The modus operandi for redux actions is to define:

  1. an Action type (e.g. in Type.js)
  2. a function that generates an object using type: <action> and some payload

Synthetic Actions take in a Base Action Type and generate:

  1. <action>_SUCCESS <action>_ERROR <action>_RESET types
  2. functions that generate the desired Action Object

API

Kick off the action -> synAction.ignite();
Access base action string -> synAction.igniteName; // string Access suffix actions -> synAction.suffixNames; // [string] Access suffix action generators -> synAction.suffixSyntheticActions; // [ { type: string, generator: fn } ]

Example

const FETCH_TODOS = 'FETCH_TODOS';
export const synAction = new SyntheticAction({ 
  type: FETCH_TODOS, 
  url: '/api/v1/todo/list', 
});

Synthetic Reducer

Companion reducer piece to SyntheticActions. Takes in a SyntheticAction and reducer key & returns Redux State handler.

Example

const synReducer = SyntheticReducer(synAction, 'list')
const RootReducer = combineReducers({
  todos: synReducer
});

/* Usage in React Component */
const listResponse = useSelector(state => state.todos.list);
if (listResponse[CATEGORY_NAMES].IGNITE) { return <LoadingState />; }
else if (listResponse.error) { return <ErrorHandler error={listResponse.error} />; }
else { return <ShowTodos list={listResponse.list} />; }

Synthetic Redux-Saga Effect

Companion redux-saga with SyntheticActions and SyntheticReducer Given a SyntheticAction it returns a sagaEffect. The sagaEffect comes embedded with Error & Success Handling

Opinions

  1. Uses takeLatest to execute the effect
  2. Requires the API to respond with { error: <any> } to indicate error
  3. Expects action to be set with an url and payload(opt) for the API call

API

const sagaEffect = SyntheticSagaEffect(synAction);
const todoSaga = yield all([sagaEffect]);
export default function* RootSaga() {
  const allSagas = [todoSaga];
  yield all(allSagas.map(fork));
}

About

Sets up Redux boilerplate: actions, reducers and redux-saga for most common redux use cases

Resources

License

Stars

Watchers

Forks

Packages

No packages published