{"id":27553031,"url":"https://github.com/springwong/use-redux-saga","last_synced_at":"2025-04-19T11:43:12.086Z","repository":{"id":48782125,"uuid":"384775028","full_name":"springwong/use-redux-saga","owner":"springwong","description":"use-redux-saga is a library to integrate redux and its side effect with react hook API with more advanced features.","archived":false,"fork":false,"pushed_at":"2021-07-14T17:32:50.000Z","size":422,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-08T16:47:54.314Z","etag":null,"topics":["generators","hooks-library","react","reacthook","redux","redux-saga","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/springwong.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-07-10T19:29:24.000Z","updated_at":"2024-12-16T21:04:37.000Z","dependencies_parsed_at":"2022-08-31T05:40:41.969Z","dependency_job_id":null,"html_url":"https://github.com/springwong/use-redux-saga","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/springwong%2Fuse-redux-saga","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/springwong%2Fuse-redux-saga/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/springwong%2Fuse-redux-saga/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/springwong%2Fuse-redux-saga/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/springwong","download_url":"https://codeload.github.com/springwong/use-redux-saga/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249685436,"owners_count":21310602,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["generators","hooks-library","react","reacthook","redux","redux-saga","typescript"],"created_at":"2025-04-19T11:43:11.471Z","updated_at":"2025-04-19T11:43:12.076Z","avatar_url":"https://github.com/springwong.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"useReduxSaga\n=========================\n\nA extension library for react hook + [react redux](https://github.com/reduxjs/react-redux) + [saga](https://redux-saga.js.org/)\n\n`use-redux-saga` is a library to integrate redux and its side effect with react hook API with more advanced features.\n\nThe provided Hooks are trying to make reducer and saga aligning with the normal usage of hooks.\n\n[![npm version](https://img.shields.io/npm/v/use-redux-saga.svg?style=flat-square)](https://www.npmjs.com/package/use-redux-saga)\n[![npm downloads](https://img.shields.io/npm/dm/use-redux-saga.svg?style=flat-square)](https://www.npmjs.com/package/use-redux-saga)\n\n## Installation\n\nTo use useReduxSaga with your React app, install it as a dependency:\n\n```bash\n# If you use npm:\nnpm install use-redux-saga\n\n# Or if you use Yarn:\nyarn add use-redux-saga\n```\n## Setup\n\n### Reducers\n`use-redux-saga` required to init with the static reducers before combinedReducers.\nThe way to insert reducer is based on [react redux document - code splitting](https://redux.js.org/usage/code-splitting).\n\nP.S. createReducerManager and setRunSaga have zero dependencies. If you are aiming to use only one of them. you could simply ingore another init method.\n\n```javascript\n// Your reducers object before combinedReducers\nconst reducers = { todosReducer }\n\n// ...\n// Store.js / ts\n// init with createReducerManager\ncreateReducerManager(reducers)\n```\n### Redux Saga\n```javascript\nconst sagaMiddleware = createSagaMiddleware({\n  sagaMonitor\n});\n\n// code to run sagaMiddleware after store creation\nsagaMiddleware.run(mySaga);\n// setRunSaga with sagaMiddleware.run\nsetRunSaga(sagaMiddleware.run);\n```\n\n## Usage Example\n### useReduxReducer\n\n#### useReduxReducer\u003cS = any, A = any\u003e(reducer: Reducer\u003cS, A\u003e, key: string, cleanUp: boolean = false): [S]\n|params|Description|\n|----|----|\n|reducer|Reducer Type of react redux|\n|key|The reducer key for combinedReducers. The reducer will keep alive if cleanUp = false and cannot recreated.|\n|cleanUp|Default false, automatically remove reducer when FC destoryed if true.|\n|Return|Description|\n|S|The state of reducer|\n\n```javascript\nconst SomeScreen: FC = () =\u003e {\n    const [state] = useReduxReducer((state = {\n        value: 0\n    }, action : any) =\u003e {\n        switch (action.type) {\n            case 'add':\n                return { \n                    ...state,\n                    value: state.value + 1\n                };\n        }\n        return state;\n    }, \"UniqueKey\");\n    return \u003cText\u003e{state.value}\u003c/Text\u003e\n}\n```\nor\n```javascript\nconst SomeScreen: FC = () =\u003e {\n    // someReducer from normal reducer file.\n    const [state] = useReduxReducer(someReducer, \"UniqueKey\");\n    return \u003cText\u003e{state.value}\u003c/Text\u003e\n}\n```\n\n#### useReduxReducerLocal\u003cS = any, A = any\u003e(reducer: Reducer\u003cS, A\u003e): [S]\n|params|Description|\n|----|----|\n|reducer|Reducer Type of react redux|\n|Return|Description|\n|S|The state of reducer|\n\nSame as useReduxReducer with auto generated key. Reducer will be removed if FC object is destoryed.\n\n### useSaga\nuseSaga will always be destroyed when FC is destoryed. Use useContext Provider to make every events in single location.\n\n#### Attentions\nThe useState variable inside generator will not be updated when everytime called. The problem is that if you use useState variable directly inside, you may get unexpected value and result.\nTo resolve it, it could pass the latest useState value when dispatch by useDispatch.\nuseSagaSimple provided useStateVariables variables to pass those values in useStateVariables with code hints.\nPlease note that the values of useState variable is locked when passed to generator function. If that value is changed in the middle of saga, the value cannot be reflected. For example, after an API call, and useState value is changed during the call.\n\n#### useSaga\u003cType\u003e(rootSaga: (sages: Type) =\u003e Generator, saga: Type): () =\u003e void\n|params|Description|\n|----|----|\n|rootSaga|the root Saga|\n|saga|sub sagas that will pass to rootSaga as parameter|\n|Return|Description|\n|()=\u003evoid|Call to cancel this saga immediately|\n\n```javascript\n// sample to run saga in run time\n    const cancelSaga = useSaga(function*(params: any) {\n        yield takeLatest(\"TEST_1\", params.add)\n    }, {\n        add: function* () {\n            yield delay(1000)\n            yield put({\n                type: 'provider_add'\n            })\n        },\n    })\n    \n    // ... in some cases, call cancelSaga() to stop the saga actions manually\n    // cancelSaga is not necessary is most use case.\n```\n\nor \n```javascript\n// demoSaga is normal saga file with exported default. No params required in this case.\nuseSaga(demoSaga, {})\n```\n\n#### useSagaSimple\u003cType\u003e(saga: (action: {type: string, payload: any, useStateVariables: Type}) =\u003e Generator, useStateVariables: Type, effect: any = takeLatest): [((payload: any) =\u003e void), () =\u003e void]\nuseSagaSimple is a simple saga implementation with only one generator function. Effect will affect the behaviour when triggered.\nAlways use dispatchPayload to trigger this saga.\n\n|params|Description|\n|----|----|\n|saga|saga generator method to run|\n|useStateVariables|useState variable that passed to function, function* will not keep variable update from useState. So, to make sure the value is most updated in saga call, the value is passed in action.useStateVariables, no default but could pass {}|\n|effect|any saga effect that take actions by different behaviour|\n|Return|Description|\n|((payload: any) =\u003e void)|Dispatch method with payload parameter|\n|()=\u003evoid|Call to cancel this saga immediately|\n\n```javascript\n    const [dispatchPayload, cancelSaga]: [(payload: any) =\u003e void, () =\u003e void] = useSagaSimple(function* (action) {\n        yield delay(1000)\n        yield put({\n            type: 'provider_add'\n        })\n    }, {});\n\n    // ...\n    return \u003cView onPress={() =\u003e {\n        dispatchPayload({\n            value: 0\n        })\n    }} /\u003e\n```\n\n### useRedux\nuseRedux is a more advanced hook to generic action in redux\n\n### useRedux\u003cStateType, Actions extends { [id: string]: (state: StateType, payload: any) =\u003e StateType }\u003e(initState: StateType, actions: Actions): [StateType, { [key in keyof Actions]: (payload: any) =\u003e void }]\n\n|params|Description|\n|----|----|\n|state|init State|\n|actions|An object to process each return like normal reducer, 2nd param is payload but not action, action.type is hidden here which is object's key|\n|Return|Description|\n|state: StateType|Current state of useRedux|\n|dispatches|The object that could trigger reducer, it's 1-to-1 mapping with input param actions|\n\n```javascript\n    const [state, dispatches] = useRedux({\n        value: 0\n    }, {\n        add: (state, payload) =\u003e {\n            return {\n                ...state,\n                value: state.value + 1\n            }\n        },\n        minus: (state, payload) =\u003e {\n            return {\n                ...state,\n                value: state.value - 1\n            }\n        },\n    });\n\n    // ...\n    return \u003cView onPress={() =\u003e {\n        dispatches.add({\n            // maybe some value\n        })\n    }} \u003e\u003cText\u003e{state.value}\u003c/Text\u003e \u003c/View\u003e\n```\n\n### useStateRef\n\n```javascript\n    const [refState, setRefState] = useStateRef(0);\n\n    useEffect(() =\u003e {\n        // get value with state.current which is same as useRef\n        console.log(`refState.current changed: ${refState.current}`)\n    }, [refState.current])\n\n    setRefState(1);\n```\n\n### reducerManager\nNormally, don't need to get reducerManager object, but sometimes, you may need to remove reducer with removeReducer() method.\n```javascript\ninterface {\n    getReducerMap: () =\u003e any,\n    addReducer: (key : string, reducer : Reducer\u003cany, any\u003e, store: Store) =\u003e void,\n    removeReducer: (key : string, store: Store) =\u003e void,\n}\n```\n\n### State accross screens\n#### useContext Provider\n```javascript\n// container screen\nexport const UseReduxProvider = React.createContext({\n    state: {value: 0},\n    dispatches: {multiple: (payload: any) =\u003e {}},\n})\n\nconst Container: FC = () =\u003e {\n    const [state, dispatches] = useRedux({\n        value: 2\n    }, {\n        multiple: (state, payload) =\u003e {\n            return {\n                ...state,\n                value: state.value * 2,\n            }\n        }\n    });\n    return \u003cUseReduxProvider.Provider value={{\n        state,\n        dispatches,\n    }} \u003e\n       {\n           //... Other Components\n       }\n    \u003c/UseReduxProvider.Provider\u003e\n}\n\n// another screen, useContext\nconst reduxConsumer = useContext(UseReduxProvider);\n```\n\n#### useReduxReducer with cleanUp = false\nReducer will not be duplicated if cleanUp = false.\nAlways can use useSelector to get the store value. Or, dispatch action with useDispatch in `react-redux` library.\n\nuseReduxReducer with same key will serve the 1st called function only.\n\nSuggest to use reducer file approach in this case.\n```javascript\nconst state = useSelector(\"YOUR_KEY\")\n```\n\n## Limitation\nFor inline generator function in useSaga / useSagaSimple, there are some limitations on mixed up with react hook APIs.\nThis limitation is not due to library itself but internal property of generator function and react hook.\n\n### useSaga inline function\nConsider a implementation below in functional component.\n```javascript\nconst Component: FC = () =\u003e {\n    const [index, setIndex] = useState(0);\n    const [testCall] = useSagaSimple(function* (action) {\n        // it always return initState = 0\n        console.log(`index directly from : ${index}`)\n        // value change by click\n        console.log(`index pass from dispatch: ${action.useStateVariables.index}`)\n    }, {index});\n    return \u003cTouchableOpacity onPress={\n            () =\u003e {\n                // add a for index from useState\n                setIndex(index + 1);\n                testCall({});\n            }\n        } style={style.button}\u003e\n            \u003cText\u003e{\"Press to trigger saga\"}\u003c/Text\u003e\n    \u003c/TouchableOpacity\u003e\n}\n```\nIndex haven't change by clicks because useState update value with different ref. And generator function will create a snapshot of those values when first time creation.\n\nTo simplify the usage, useSagaSimple allow the object to pass through dispatch.\n```\n{index} as last parameter\nget() with action.useStateVariables.index\n```\nHowever, unlike 'yield select' in normal redux saga, the value is decided when payload is dispatched. So, if value is changed during the call. The value will not be most updated values.\nConsider the saga file in normal practice, 'yield select' is the more 'saga' way to fetch the latest state of reducer values.\n\nAnother examples to show alternative:\n```javascript\nconst Component: FC = () =\u003e {\n    const index = useRef(0);\n    const [state, dispatches, reducerKey] = useRedux({value: 0}, {\n        add: (state, payload) =\u003e { return { ...state, value: state.value + 1}},\n    })\n    const [testCall] = useSagaSimple(function* (action) {\n        // it has most updated value when index.current change.\n        console.log(`index directly from : ${index.current}`)\n        // or, for any reducer\n        const selectorValue = (yield select(state =\u003e {\n            return state[reducerKey]['value']\n        })) as number;\n        // it has most updated value also from redux state\n        console.log(`index directly from : ${selectorValue}`)\n    })\n\n    return \u003cTouchableOpacity onPress={\n            () =\u003e {\n                // add 1 for both ref and redux reducer\n                dispatches.add({})\n                index.current = index.current + 1;\n                testCall({});\n            }\n        } style={style.button}\u003e\n            \u003cText\u003e{\"Press to trigger saga\"}\u003c/Text\u003e\n    \u003c/TouchableOpacity\u003e\n}\n```\nuseRef results in consistent value because it have consistent ref object in its ref. And ref itself do not change in FC lifecycle.\nyield select is correct and normal way to retrieve reducer value which is safe to use whatever in saga file or inline function.\n\nuseReduxSaga provided an alternative hook `useStateRef`. It could listen with useEffect and get the latest value within generator method.\n\n### Summary\n|source|Expection|\n|----|----|\n|const [state] = useState()|State ref will be locked when create generator function. Value will not be changed inside generator function with setState function|\n|const [state] = useReducer()|Same as useState, state ref can't be changed once created generator function|\n|const state = useSelector()|Same as useState. The reason here is output of useSelector will have ref change when value updated. However, that ref change cannot be updated inside generator function.|\n|const dispatch = useDispatch();dispatch({type:'xxx', payload:'state from useState'});|Dispatched state will be a copy of state when action dispatched. The state change after action dispatch will not affect the state value in saga parameter. useSagaSimple is supported this way.|\n|const value = yield select(s =\u003e s['name'].value;|The saga way to retrieve latest state value. The most updated value will be returned.|\n|const ref = useRef(0); const state = ref.current;|The value will be updated if get from ref.current. However, useRef value change will not trigger screen rendering which is mentioned in react hook documentation. Not suggested to rely on useRef with saga logic as normal practice.|\n|const [ref, setRef] = useStateRef(0);|This value will be updated in generator as it is useRef based and no ref change during setRef method.|\n\n## License\nCopyright (c) 2021 Spring Wong.\n\nLicensed under The MIT License (MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspringwong%2Fuse-redux-saga","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspringwong%2Fuse-redux-saga","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspringwong%2Fuse-redux-saga/lists"}