Skip to content

Use redux saga with useReducer hook to handle side effects without Redux

Notifications You must be signed in to change notification settings

ashishrudra/react-async-saga-reducer

Repository files navigation

react-async-saga-reducer

Use redux saga with React useReducer hook to handle side effects without Redux

install

npm i react-async-saga-reducer

dependencies

1. React
2. Redux-Saga

usage

DEMO

saga.js

import { put, takeEvery, delay } from 'redux-saga/effects';

function* sagaWorker() {
    let countDown = 5;
    while(countDown > 0) {
        yield put({type: 'UPDATE_TIMER', value: countDown});
        yield delay(1000);
        countDown -= 1;
    }
    yield put({type: 'INCREMENT'});
}

export function* mySaga() {
    yield takeEvery('ASYNC_INCREMENT', sagaWorker);
}

reducer.js

export function myReducer(state, action) {
    switch(action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 , timer: 0 }
        case 'UPDATE_TIMER':
            return { count: state.count, timer: action.value }
        default:
            return state
    }
}

app.js

import React from 'react';
import { useAsyncSagaReducer } from 'react-async-saga-reducer';
import { myReducer } from './reducer';
import { mySaga } from './saga';

const initialState = { count: 0 , timer: 0 };

export function App() {
    const [ state, dispatch ] = useAsyncSagaReducer(myReducer, mySaga, initialState);

    return (
        <div className="App">
            <p>Count: {state.count}</p>
            <br/>
            <button onClick={() => dispatch({type: 'ASYNC_INCREMENT'})} disabled={state.timer > 0}>Increment after 5 second</button>
            {state.timer > 0 && <p>...please wait {state.timer} sec...</p>}
        </div>
    );
}

TypeScript

Type annotations may be specified when calling useAsyncSagaReducer:

saga.ts

import { put, takeEvery, delay } from 'redux-saga/effects';

function* sagaWorker() {
    let countDown = 5;
    while(countDown > 0) {
        yield put({type: 'UPDATE_TIMER', value: countDown});
        yield delay(1000);
        countDown -= 1;
    }
    yield put({type: 'INCREMENT'});
}

export function* mySaga() {
    yield takeEvery('ASYNC_INCREMENT', sagaWorker);
}

action.ts

export interface Action<T> {
    type: string;
    value?: T
}

state.ts

export interface StateInterface {
    count: number;
    timer: number;
}

export const initialState = (): StateInterface => {
    return {
        count: 0,
        timer: 0
    };
}

reducer.ts

import { StateInterface, initialState } from './state';
import { Action } from './action';

export function myReducer(state: StateInterface, action: Action<number>): StateInterface {
    switch(action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 , timer: 0 }
        case 'UPDATE_TIMER':
            return { count: state.count, timer: action.value }
        default:
            return state
    }
}

app.tsx

import * as React from 'react';
import { useAsyncSagaReducer } from 'react-async-saga-reducer';
import { myReducer } from './reducer';
import { mySaga } from './saga';
import { StateInterface, initialState } from './state';
import { Action } from './action';

export function App() {
    //specify types to gain intelligent code completion and TypeScript type checking.
    const [ state, dispatch ] = useAsyncSagaReducer<StateInterface, Action<number>>(myReducer, mySaga, initialState());

    return (
        <div className="App">
            <p>Count: {state.count}</p>
            <br/>
            <button onClick={() => dispatch({type: 'ASYNC_INCREMENT'})} disabled={state.timer > 0}>Increment after 5 second</button>
            {state.timer > 0 && <p>...please wait {state.timer} sec...</p>}
        </div>
    );
}