npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

use-redux-saga

v1.1.17

Published

use-redux-saga is a library to apply saga with react hooks API in a simple way

Downloads

47

Readme

useReduxSaga

A extension library for react hook + react redux + saga

use-redux-saga is a library to integrate redux and its side effect with react hook API with more advanced features.

The provided Hooks are trying to make reducer and saga aligning with the normal usage of hooks.

npm version npm downloads

Installation

To use useReduxSaga with your React app, install it as a dependency:

# If you use npm:
npm install use-redux-saga

# Or if you use Yarn:
yarn add use-redux-saga

Setup

Reducers

use-redux-saga required to init with the static reducers before combinedReducers. The way to insert reducer is based on react redux document - code splitting.

P.S. createReducerManager and setRunSaga have zero dependencies. If you are aiming to use only one of them. you could simply ingore another init method.

// Your reducers object before combinedReducers
const reducers = { todosReducer }

// ...
// Store.js / ts
// init with createReducerManager
createReducerManager(reducers)

Redux Saga

const sagaMiddleware = createSagaMiddleware({
  sagaMonitor
});

// code to run sagaMiddleware after store creation
sagaMiddleware.run(mySaga);
// setRunSaga with sagaMiddleware.run
setRunSaga(sagaMiddleware.run);

Usage Example

useReduxReducer

useReduxReducer<S = any, A = any>(reducer: Reducer<S, A>, key: string, cleanUp: boolean = false): [S]

|params|Description| |----|----| |reducer|Reducer Type of react redux| |key|The reducer key for combinedReducers. The reducer will keep alive if cleanUp = false and cannot recreated.| |cleanUp|Default false, automatically remove reducer when FC destoryed if true.| |Return|Description| |S|The state of reducer|

const SomeScreen: FC = () => {
    const [state] = useReduxReducer((state = {
        value: 0
    }, action : any) => {
        switch (action.type) {
            case 'add':
                return { 
                    ...state,
                    value: state.value + 1
                };
        }
        return state;
    }, "UniqueKey");
    return <Text>{state.value}</Text>
}

or

const SomeScreen: FC = () => {
    // someReducer from normal reducer file.
    const [state] = useReduxReducer(someReducer, "UniqueKey");
    return <Text>{state.value}</Text>
}

useReduxReducerLocal<S = any, A = any>(reducer: Reducer<S, A>): [S]

|params|Description| |----|----| |reducer|Reducer Type of react redux| |Return|Description| |S|The state of reducer|

Same as useReduxReducer with auto generated key. Reducer will be removed if FC object is destoryed.

useSaga

useSaga will always be destroyed when FC is destoryed. Use useContext Provider to make every events in single location.

Attentions

The 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. To resolve it, it could pass the latest useState value when dispatch by useDispatch. useSagaSimple provided useStateVariables variables to pass those values in useStateVariables with code hints. Please 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.

useSaga(rootSaga: (sages: Type) => Generator, saga: Type): () => void

|params|Description| |----|----| |rootSaga|the root Saga| |saga|sub sagas that will pass to rootSaga as parameter| |Return|Description| |()=>void|Call to cancel this saga immediately|

// sample to run saga in run time
    const cancelSaga = useSaga(function*(params: any) {
        yield takeLatest("TEST_1", params.add)
    }, {
        add: function* () {
            yield delay(1000)
            yield put({
                type: 'provider_add'
            })
        },
    })
    
    // ... in some cases, call cancelSaga() to stop the saga actions manually
    // cancelSaga is not necessary is most use case.

or

// demoSaga is normal saga file with exported default. No params required in this case.
useSaga(demoSaga, {})

useSagaSimple(saga: (action: {type: string, payload: any, useStateVariables: Type}) => Generator, useStateVariables: Type, effect: any = takeLatest): [((payload: any) => void), () => void]

useSagaSimple is a simple saga implementation with only one generator function. Effect will affect the behaviour when triggered. Always use dispatchPayload to trigger this saga.

|params|Description| |----|----| |saga|saga generator method to run| |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 {}| |effect|any saga effect that take actions by different behaviour| |Return|Description| |((payload: any) => void)|Dispatch method with payload parameter| |()=>void|Call to cancel this saga immediately|

    const [dispatchPayload, cancelSaga]: [(payload: any) => void, () => void] = useSagaSimple(function* (action) {
        yield delay(1000)
        yield put({
            type: 'provider_add'
        })
    }, {});

    // ...
    return <View onPress={() => {
        dispatchPayload({
            value: 0
        })
    }} />

useRedux

useRedux is a more advanced hook to generic action in redux

useRedux<StateType, Actions extends { [id: string]: (state: StateType, payload: any) => StateType }>(initState: StateType, actions: Actions): [StateType, { [key in keyof Actions]: (payload: any) => void }]

|params|Description| |----|----| |state|init State| |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| |Return|Description| |state: StateType|Current state of useRedux| |dispatches|The object that could trigger reducer, it's 1-to-1 mapping with input param actions|

    const [state, dispatches] = useRedux({
        value: 0
    }, {
        add: (state, payload) => {
            return {
                ...state,
                value: state.value + 1
            }
        },
        minus: (state, payload) => {
            return {
                ...state,
                value: state.value - 1
            }
        },
    });

    // ...
    return <View onPress={() => {
        dispatches.add({
            // maybe some value
        })
    }} ><Text>{state.value}</Text> </View>

useStateRef

    const [refState, setRefState] = useStateRef(0);

    useEffect(() => {
        // get value with state.current which is same as useRef
        console.log(`refState.current changed: ${refState.current}`)
    }, [refState.current])

    setRefState(1);

reducerManager

Normally, don't need to get reducerManager object, but sometimes, you may need to remove reducer with removeReducer() method.

interface {
    getReducerMap: () => any,
    addReducer: (key : string, reducer : Reducer<any, any>, store: Store) => void,
    removeReducer: (key : string, store: Store) => void,
}

State accross screens

useContext Provider

// container screen
export const UseReduxProvider = React.createContext({
    state: {value: 0},
    dispatches: {multiple: (payload: any) => {}},
})

const Container: FC = () => {
    const [state, dispatches] = useRedux({
        value: 2
    }, {
        multiple: (state, payload) => {
            return {
                ...state,
                value: state.value * 2,
            }
        }
    });
    return <UseReduxProvider.Provider value={{
        state,
        dispatches,
    }} >
       {
           //... Other Components
       }
    </UseReduxProvider.Provider>
}

// another screen, useContext
const reduxConsumer = useContext(UseReduxProvider);

useReduxReducer with cleanUp = false

Reducer will not be duplicated if cleanUp = false. Always can use useSelector to get the store value. Or, dispatch action with useDispatch in react-redux library.

useReduxReducer with same key will serve the 1st called function only.

Suggest to use reducer file approach in this case.

const state = useSelector("YOUR_KEY")

Limitation

For inline generator function in useSaga / useSagaSimple, there are some limitations on mixed up with react hook APIs. This limitation is not due to library itself but internal property of generator function and react hook.

useSaga inline function

Consider a implementation below in functional component.

const Component: FC = () => {
    const [index, setIndex] = useState(0);
    const [testCall] = useSagaSimple(function* (action) {
        // it always return initState = 0
        console.log(`index directly from : ${index}`)
        // value change by click
        console.log(`index pass from dispatch: ${action.useStateVariables.index}`)
    }, {index});
    return <TouchableOpacity onPress={
            () => {
                // add a for index from useState
                setIndex(index + 1);
                testCall({});
            }
        } style={style.button}>
            <Text>{"Press to trigger saga"}</Text>
    </TouchableOpacity>
}

Index 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.

To simplify the usage, useSagaSimple allow the object to pass through dispatch.

{index} as last parameter
get() with action.useStateVariables.index

However, 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. Consider the saga file in normal practice, 'yield select' is the more 'saga' way to fetch the latest state of reducer values.

Another examples to show alternative:

const Component: FC = () => {
    const index = useRef(0);
    const [state, dispatches, reducerKey] = useRedux({value: 0}, {
        add: (state, payload) => { return { ...state, value: state.value + 1}},
    })
    const [testCall] = useSagaSimple(function* (action) {
        // it has most updated value when index.current change.
        console.log(`index directly from : ${index.current}`)
        // or, for any reducer
        const selectorValue = (yield select(state => {
            return state[reducerKey]['value']
        })) as number;
        // it has most updated value also from redux state
        console.log(`index directly from : ${selectorValue}`)
    })

    return <TouchableOpacity onPress={
            () => {
                // add 1 for both ref and redux reducer
                dispatches.add({})
                index.current = index.current + 1;
                testCall({});
            }
        } style={style.button}>
            <Text>{"Press to trigger saga"}</Text>
    </TouchableOpacity>
}

useRef results in consistent value because it have consistent ref object in its ref. And ref itself do not change in FC lifecycle. yield select is correct and normal way to retrieve reducer value which is safe to use whatever in saga file or inline function.

useReduxSaga provided an alternative hook useStateRef. It could listen with useEffect and get the latest value within generator method.

Summary

|source|Expection| |----|----| |const [state] = useState()|State ref will be locked when create generator function. Value will not be changed inside generator function with setState function| |const [state] = useReducer()|Same as useState, state ref can't be changed once created generator function| |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.| |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.| |const value = yield select(s => s['name'].value;|The saga way to retrieve latest state value. The most updated value will be returned.| |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.| |const [ref, setRef] = useStateRef(0);|This value will be updated in generator as it is useRef based and no ref change during setRef method.|

License

Copyright (c) 2021 Spring Wong.

Licensed under The MIT License (MIT).