Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save marcelaraujo/fb6a16b3093d2b37ae32be8308b307d9 to your computer and use it in GitHub Desktop.
Save marcelaraujo/fb6a16b3093d2b37ae32be8308b307d9 to your computer and use it in GitHub Desktop.
redux-saga module example
// redux/user/actions.js
import * as types from './types';
export function fetchUserRequest(userId){
return {
type: types.FETCH_USER_REQUEST,
payload: userId
}
}
export function fetchUserSuccess(user){
return {
type: types.FETCH_USER_SUCCESS,
payload: user
}
}
export function fetchUserFailure(error){
return {
type: types.FETCH_USER_FAILURE,
payload: error
}
}
// redux/user/reducer.js
import * as types from './types';
const initialState = {
loading: false,
error: null,
};
export default function(state = initialState, { type, payload }) {
switch (type) {
case types.FETCH_USER_REQUEST:
return {
...state,
loading: true,
};
case types.FETCH_USER_SUCCESS:
return {
...state,
...payload,
loading: false,
};
case types.FETCH_USER_FAILURE:
return {
...state,
loading: false,
error: {
...payload,
},
};
default:
return state;
}
}

Gist contains example files for redux-saga user(GET) module. The structure of redux for included files in this gist:

redux
├── user                          # Module name
│   ├── __tests__                 # Directory for redux module tests
│   │   ├── actions.test.js       # Action tests
│   │   └── saga.test.js          # Saga tests
│   ├── actions.js                # Module actions (f/e.: fetchUserRequest, fetchUserSuccess)
│   ├── reducer.js                # Module reducer
│   ├── saga.js                   # Module saga
│   └── types.js                  # Declared module constants (f/e.: FETCH_USER_REQUEST)

Example of redux with nested module

redux
├── user                          # Module name
│   ├── add                       # Sub-module name
│   │   ├── __tests__
│   │   │   ├── actions.test.js
│   │   │   └── saga.test.js
│   │   ├── actions.js
│   │   ├── reducer.js
│   │   ├── saga.js
│   │   └── types.js
│   └── delete
│       ├── __tests__
│       │   ├── actions.test.js
│       │   └── saga.test.js
│       ├── actions.js
│       ├── reducer.js
│       ├── saga.js
│       └── types.js
|
├── user-cats
│   ├── __tests__
│   │   ├── actions.test.js
│   │   └── saga.test.js
│   ├── actions.js
│   ├── reducer.js
│   ├── saga.js
│   └── types.js
|
├── root-reducer.js             # Reducers from ALL modules are here
└── root-saga.js                # Sagas from ALL modules are here
// redux/root-reducer.js
import { combineReducers } from 'redux';
import user from './user/reducer';
import userCats from './user-cats/reducer';
const rootReducer = combineReducers({
user,
userCats,
});
export default rootReducer;
// redux/root-saga.js
import {all} from 'redux-saga/effects';
import { watchUser } from './user/saga';
import { watchUserCats } from './user-cats/saga';
export default function* rootSaga() {
yield all([
watchUser(),
watchUserCats(),
]);
}
// redux/user/saga.js
import {call, put, push, takeEvery} from 'redux-saga/effects';
import { setLocalstorage } from 'utils/localstore';
import { toast } from 'utils/toast';
import { routes } from 'routes';
import * as types from './types';
import * as actions from './actions';
export function* fetchUserSaga({ payload }) {
const { user, error } = yield call(api.fetchUser, payload);
if(error){
yield put(actions.fetchUserFailure(error));
yield call(toast, "error", error); // show error toast
} else {
yield put(actions.fetchUserSuccess(user));
yield call(toast, "success"); // show success toast
// yield push(routes.DASHBOARD); // if necessary redirect via push action provided by redux-saga
yield call(setLocalstorage, "userData", user); // save to localStorage
}
}
export function* watchUser() {
yield takeEvery(types.FETCH_USER_REQUEST, fetchUserSaga)
}
// redux/user/__tests__/saga.test.js
import {call, put, push, takeEvery} from 'redux-saga/effects';
import { setLocalstorage } from 'utils/localstore';
import { toast } from 'utils/toast';
import { routes } from 'routes';
import { fetchUserSaga, fetchUser } from '../saga';
import * as actions from '../actions';
describe('User saga', () => {
describe('Fetch user', () => {
const payload = {
username: 'Bob',
};
describe('when success', () => {
test('should dispatch success action', () => {
const saga = fetchUserSaga({ payload });
const response = {
username: 'Bob',
};
expect(saga.next().value).toEqual(call(fetchUser, payload));
expect(saga.next(response).value).toEqual(put(actions.fetchUserSuccess(response)));
});
test('should show success toast notification', () => {
const saga = fetchUserSaga({ payload });
const response = {
username: 'Bob',
};
expect(saga.next().value).toEqual(call(fetchUser, payload));
expect(saga.next(response).value).toEqual(put(actions.fetchUserSuccess(response)));
expect(saga.next().value).toEqual(call(toast, 'success'));
});
});
describe('when error', () =>{
test('should dispatch an error action', () =>{
const saga = fetchUserSaga({ payload });
const response = { error: {message: 'fake err'}};
expect(saga.next().value).toEqual(call(fetchUser, payload));
expect(saga.next(response).value).toEqual(put(actions.fetchUserFailure(response.error.message)))
})
});
})
});
// redux/user/types.js
export const FETCH_USER_REQUEST = 'FETCH_USER_REQUEST';
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS';
export const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment