nomadus
nomadus

Reputation: 909

How to migrate Redux to React hooks

I am currently trying to migrate Redux files to React hooks (useReducer) with minimal changes. I am reusing Redux reducers and the initial states. e.g.

export const INITIAL_STATE = {
    locale: "en"
};

export const reducer = (state = INITIAL_STATE, action) => {
    switch (action.type) {
        case "CHANGE_LOCALE":
            return {
                ...state,
                locale: action.payload
            };
        default:
            return state;
    }
};

I'd like to reuse Redux actions as well and need some help, currently, I have.

export const changeLocale = (payload) => {
    return (dispatch) => {
        Cookies.set("lc", payload);

        dispatch({
            type: "CHANGE_LOCALE",
            payload
        });
    };
};

But dispatch here and dispatch in useReducer are different functions, what are the ways to migrate such action to be used in useReducer hook?

Upvotes: 0

Views: 403

Answers (2)

Brainpick.co.uk
Brainpick.co.uk

Reputation: 11

Here is what we use. Add a utility function:

const wrapAsync = (dispatch) => (action) => {
    if (typeof action === "function") {
        return action(dispatch);
    }
    return dispatch(action);
};

Then in your code:

const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
const asyncDispatch = React.useMemo(() => wrapAsync(dispatch), [dispatch]);

Pass the new asyncDispatch down to child components:

<LocaleContext.Provider
    value={{...state, asyncDispatch}}>
        {children}
</LocaleContext.Provider>

Then in your child component, you can do as follows:

import {changeLocale} from "~/actions/locales";

let {locale, asyncDispatch} = useContext(LocaleContext);
    
<Button onclick={({e}) => asyncDispatch(changeLocale(e.target.value))}/>

Upvotes: 1

apokryfos
apokryfos

Reputation: 40653

You can't achieve this with plain a useReducer though it's straight-forward to create a custom hook that can deal with the "thunk" (or whatever you want to call the actions which are functions) as well. Your base case reducer is:

const [ state, dispatch ] = React.useReducer(reducer, INITIAL_STATE);

However you need to modify your dispatch to allow it to make it have "middleware" like behaviour:

const useReducerWithThunk = (reducer, initialState) => {
    const [ state, innerDispatch ] = React.useReducer(reducer, initialState);
    const myDispatch = async (action) => {
         if (typeof action === 'function') {
            // Wrap the result in a promise if it's not one already
            return await Promise.resolve(action(myDispatch, () => state));
         } else {
            return innerDispatch(action);
         }
    };

    return [ state, myDispatch ]; 
}

Then your code will do:

const [ state, dispatch ] = useReducerWithThunk(reducer, initialState);

This should be enough for a simple drop-in replacement of redux with thunks, however be mindful that dispatch will become async which is not the default behaviour.

Upvotes: 0

Related Questions