This is the first day of my participation in the Gwen Challenge in November. Check out the details: the last Gwen Challenge in 2021

UseReducer is an alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method. UseReducer can be more useful than useState in some situations, such as when the state logic is complex and contains multiple subvalues, or when the next state depends on the previous state.

What scenarios are more suitable for useReducer or useState? The following are some examples.

Single state management

useuseStateImplement custom change theme colorhook

  • Window.matchmedia (mediaQueryString) obtains media query results through JS features
  • parametermediaQueryStringforprefers-color-scheme:darkIs used to detect whether the user has set the theme color of the system to light or dark.Refer to the MDN
  • If the user preferences are set todarkMode, then passsetModeThe initialization mode isdark, or forlightAnd saved inlocalStorage;
    function useDarkMode() {
      const preferDarkQuery = '(prefers-color-scheme: dark)'
      const [mode, setMode] = React.useState(
        () = >
          window.localStorage.getItem('colorMode') | | (window.matchMedia(preferDarkQuery).matches ? 'dark' : 'light'),
      )

      React.useEffect(() = > {
        const mediaQuery = window.matchMedia(preferDarkQuery)
        const handleChange = () = > setMode(mediaQuery.matches ? 'dark' : 'light')
        mediaQuery.addListener(handleChange)
        return () = > mediaQuery.removeListener(handleChange)
      }, [])

      React.useEffect(() = > {
        window.localStorage.setItem('colorMode', mode)
      }, [mode])

      return [mode, setMode]
    }
Copy the code

useuseReducerImplement custom change theme colorhook

  • There are two obvious implementationsuseStateIt’s a little bit more straightforward
    const preferDarkQuery = '(prefers-color-scheme: dark)'

    function darkModeReducer(state, action) {
      switch (action.type) {
        case 'MEDIA_CHANGE': {
          return{... state,mode: action.mode}
        }
        case 'SET_MODE': {
          return{... state,mode: action.mode}
        }
        default: {
          throw new Error(`Unhandled action type: ${action.type}`)}}}function init() {
      return {
        mode:
          window.localStorage.getItem('colorMode') | | (window.matchMedia(preferDarkQuery).matches ? 'dark' : 'light'),}}function useDarkMode() {
      const [state, dispatch] = React.useReducer(
        darkModeReducer,
        {mode: 'light'},
        init,
      )
      const {mode} = state

      React.useEffect(() = > {
        const mediaQuery = window.matchMedia(preferDarkQuery)
        const handleChange = () = >
          dispatch({
            type: 'MEDIA_CHANGE'.mode: mediaQuery.matches ? 'dark' : 'light',
          })
        mediaQuery.addListener(handleChange)
        return () = > mediaQuery.removeListener(handleChange)
      }, [])

      React.useEffect(() = > {
        window.localStorage.setItem('colorMode', mode)
      }, [mode])

      const setMode = React.useCallback(
        newMode= > dispatch({type: 'SET_MODE'.mode: newMode}),
        [],
      )

      return [mode, setMode]
    }
Copy the code

Conclusion: In this scenario where you only need to manage one variable, it is better to use useState

Multiple states are managed, with one state dependent on another

useuseStateimplementationuseUndo

  • UseUndo is an example on gitHub
  • In this case,pastUpdate dependency ofpresentAnd thepresentUpdate dependency offutureThey are interdependent, so useuseStateWhen, a little attention is easy to appear the situation of the loop.
    function useUndo(initialPresent) {
      const [state, setState] = React.useState({
        past: [].present: initialPresent,
        future: [],})constcanUndo = state.past.length ! = =0
      constcanRedo = state.future.length ! = =0

      const undo = React.useCallback(() = > {
        setState(currentState= > {
          const {past, present, future} = currentState

          if (past.length === 0) return currentState

          const previous = past[past.length - 1]
          const newPast = past.slice(0, past.length - 1)

          return {
            past: newPast,
            present: previous,
            future: [present, ...future],
          }
        })
      }, [])

      const redo = React.useCallback(() = > {
        setState(currentState= > {
          const {past, present, future} = currentState

          if (future.length === 0) return currentState

          const next = future[0]
          const newFuture = future.slice(1)

          return {
            past: [...past, present],
            present: next,
            future: newFuture,
          }
        })
      }, [])

      const set = React.useCallback(newPresent= > {
        setState(currentState= > {
          const {present, past} = currentState
          if (newPresent === present) return currentState

          return {
            past: [...past, present],
            present: newPresent,
            future: [],}})}, [])const reset = React.useCallback(newPresent= > {
        setState(() = > ({
          past: [].present: newPresent,
          future: [],}))}, [])return [state, {set, reset, undo, redo, canUndo, canRedo}]
    }
Copy the code

useuseReducerimplementationuseUndo

    const UNDO = 'UNDO'
    const REDO = 'REDO'
    const SET = 'SET'
    const RESET = 'RESET'

    function undoReducer(state, action) {
      const {past, present, future} = state
      const {type, newPresent} = action

      switch (type) {
        case UNDO: {
          if (past.length === 0) return state

          const previous = past[past.length - 1]
          const newPast = past.slice(0, past.length - 1)

          return {
            past: newPast,
            present: previous,
            future: [present, ...future],
          }
        }

        case REDO: {
          if (future.length === 0) return state

          const next = future[0]
          const newFuture = future.slice(1)

          return {
            past: [...past, present],
            present: next,
            future: newFuture,
          }
        }

        case SET: {
          if (newPresent === present) return state

          return {
            past: [...past, present],
            present: newPresent,
            future: [].}}case RESET: {
          return {
            past: [].present: newPresent,
            future: [],}}}}function useUndo(initialPresent) {
      const [state, dispatch] = React.useReducer(undoReducer, {
        past: [].present: initialPresent,
        future: [],})constcanUndo = state.past.length ! = =0
      constcanRedo = state.future.length ! = =0
      const undo = React.useCallback(() = > dispatch({type: UNDO}), [])
      const redo = React.useCallback(() = > dispatch({type: REDO}), [])
      const set = React.useCallback(
        newPresent= > dispatch({type: SET, newPresent}),
        [],
      )
      const reset = React.useCallback(
        newPresent= > dispatch({type: RESET, newPresent}),
        [],
      )

      return [state, {set, reset, undo, redo, canUndo, canRedo}]
    }
Copy the code

In this case, the reducer is used to manage events separately. The code of useUndo itself is much simpler and the processing logic is mainly in the Reducer function.

Conclusion: It is better to use useReducer when your state is dependent on other states

Complex business logic scenario: click a buttonThe next page

Using useState

    // useStatesetSate(... page, currPage +1)
Copy the code

Using useReducer

    // action
    dispatch({ type: 'GO_TO_NEXT_PAGE' })
    // reducer
    case 'GO_TO_NEXT_PAGE':
    return { ...state, page: currPage + 1}
Copy the code

UseState looks simpler, but in the future if additional actions need to be added when the user clicks “Next page”, the reducer needs to be updated and the useReducer will make the logic clearer.

Conclusion: It is better to use useReducer for complex logic processing

conclusion

  • Single state management useuseStateSimple and clear
  • Used when one state depends on anotheruseReducer
  • Complex business logic scenariosuseReducerThe logic is clearer and easier to maintain

Other suitable useReducer scenarios:

  • JavaScript objects or arrays as states
  • The state needs to be updated deep in the component tree
  • More convenientdebug, better predictive and maintainable state

When to use useState and when to use useReducer is not black and white. In most cases, the two methods can be used interchangeably.

reference

MDN-matchMedia

Should I useState or useReducer?

JavaScript Ramblings

useReducer vs useState in React