DEV Community

Brett Bloxom
Brett Bloxom

Posted on • Updated on

React Hooks - useReducer

useReducer is usually preferable to useState, when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

Initialization

Similar to useState, when called, useReducer returns an array of two items. The first being our current state and the second being a dispatch method. We assign these two returned values to variables using array destructuring.

const [state, dispatch] = useReducer(reducer, initialState);
Enter fullscreen mode Exit fullscreen mode

useReducer takes two arguments and (and an optional 3rd which we will cover later). The first argument is a reducer function, and the second is our initial state value, similar to useState.

What is a Reducer?

Reducer functions are not specific to React. They are simply Javascript functions that take in two arguments: an initial value, and instructions for what to do to that value. The reducer applies some sort of logic to the value based on the instructions that you provided and returns an entirely new value.

const reducer = (value, instructions) => newValue
Enter fullscreen mode Exit fullscreen mode

An important thing to understand about reducers is that they will always only return one value. Reducers are pure functions that reduce the original input into a single return value without mutating the original value that was passed in and, given the same arguments, will always produce the same return value.

A good example of this pattern in Javascript is the .reduce() array method. As with useReducer, this method takes two arguments: a reducer function and the initial value from which to apply the reducer function against.

const nums = [1, 2, 3]  
const initialValue = 0  

const reducer = (accumulator, item) => accumulator + item 
const total = nums.reduce(reducer, initialValue)

console.log(nums) // [1, 2, 3]
console.log(total) // 6
Enter fullscreen mode Exit fullscreen mode

In this example, .reduce() loops through our nums array, and applies our reducer function for each iteration. Our initialValue is what we want the reducer to use as its starting point on the first iteration. The accumulator is the collected value returned in the last invocation that informs the function what the next value will be added to.

1st iteration: 0 + 1 => 1
2nd iteration: 1 + 2 => 3
3rd iteration: 3 + 3 => 6

The nums array was reduced into the single return value of 6.

How are Reducers Used in React?

In React, reducers are responsible for handling transitions from one state to the next state in your application. The initial value we provide to the reducer is our current state and the instructions we provide are called actions.

The current state and the action go in, the new state comes out the other side.

const reducer = (state, action) => newState
Enter fullscreen mode Exit fullscreen mode

Reducer functions handle state transitions by determining what to do based on information provided by the action.

Actions

Actions express unique events that happen throughout your application. From user interaction with the page, external interaction through network requests, and direct interaction with device APIs, these and more events can be described with actions.

Here are some general conventions for actions described by the Flux standard for action objects:

An action MUST

  • be a plain JavaScript object;
  • have a type property

An action MAY

  • have an error property.
  • have a payload property.
  • have a meta property.

An action MUST NOT include properties other than type, payload, error, and meta.

action.type

The type of an action identifies to the consumer the nature of the action that has occurred. type is a string constant. If two types are the same, they MUST be strictly equivalent (using ===).

// Action with type property
{
  type: 'ADD_TODO'
}
Enter fullscreen mode Exit fullscreen mode

action.payload

The optional payload property MAY be any type of value. It represents the payload of the action. Any information about the action that is not the type or status of the action should be part of the payload field.

// Action with type and payload properties
{
  type: 'ADD_TODO',
  payload: {
    todo,
    completed: false,
    id: id()  
  },
}
Enter fullscreen mode Exit fullscreen mode

action.error

The optional error property MAY be set to true if the action represents an error.

An action whose error is true is analogous to a rejected Promise. By convention, if error is true, the payload SHOULD be an error object. This is akin to rejecting a promise with an error object.

// Action representing an error. The error property is set to true, therefore the payload is an error object.
{
  type: 'ADD_TODO',
  payload: new Error(),
  error: true
}
Enter fullscreen mode Exit fullscreen mode

action.meta

The optional meta property MAY be any type of value. It is intended for any extra information that is not part of the payload.

Dispatching Actions

As I mentioned at the beginning, when initialized, useReducer returns an array of two items. The first being our current state and the second being a dispatch method.

const [todos, dispatch] = useReducer(reducer, [])
Enter fullscreen mode Exit fullscreen mode

When invoked, this dispatch method is responsible for passing an action to our reducer function.

Actions are dispatched when specific events take place. In following with the todo app example used thus far, these events could be represented by actions such as:

  • Adding a todo
  • Deleting a todo
  • Toggling whether a todo item is completed or not.

Let's create some action types for these events.

const ADD_TODO = 'ADD_TODO'
const DELETE_TODO = 'DELETE_TODO'
const TOGGLE_COMPLETED = 'TOGGLE_COMPLETED'
Enter fullscreen mode Exit fullscreen mode

We could use strings throughout our application when using these action types, but by assigning them to variables, we avoid the issue of misspelling the string, which would not throw an error, leading to wasted time spent tracking down the bug. If we misspell the variable name, we will get a useful error message telling us what we did wrong.

Now lets add some handler functions that will call dispatch, passing it an action object. These handlers will be triggered when certain events take place.

// calls dispatch, passing it an action object with a type property of ADD_TODO, 
// and a payload property containing the todo text that was passed in,
// a default value of false for the completed property, and a unique id.
const addTodo = todo => {
  dispatch({
    type: ADD_TODO,
    payload: {
      todo,
      completed: false,
      id: id()
    }
  });
};

// calls dispatch, passing it an action object with a type property of DELETE_TODO,
// and accepts an id which is the only property in our payload. 
const deleteTodo = id => {
  dispatch({
    type: DELETE_TODO,
    payload: {
      id
    }
  });
};

// calls dispatch, passing it an action object with a type property of TOGGLE_COMPLETED,
// and accepts an id which is the only property in our payload. 
const completeTodo = id => {
  dispatch({
    type: TOGGLE_COMPLETED,
    payload: {
      id
    }
  });
};
Enter fullscreen mode Exit fullscreen mode

Each action, when dispatched, will be handled differently by our reducer. A common pattern you will see with reducers is the use of switch statements. This isn't a requirement and any conditional logic will do so long as we are optimizing for readability. For the sake of showing something other than a switch statement, here is what a reducer for handling our todo app might look like with an if-else statement.

const todoReducer = (state, action) => {
  if (action.type === ADD_TODO) {
    return [action.payload, ...state]
  }
  if (action.type === DELETE_TODO) {
    return state.filter(todo => todo.id !== action.payload.id)
  }
  if (action.type === TOGGLE_COMPLETED) {
    return state.map(todo => {
      if (todo.id !== action.payload.id) return todo
      return {...todo, completed: !todo.completed}
    })
  }
  return state
}
Enter fullscreen mode Exit fullscreen mode

The above reducer knows what to do when given each type of action.

If the dispatched action has a type property of ADD_TODO:

  • Return a copy of the current state, adding the new todo to the beginning of the array.

If the dispatched action has a type property of DELETE_TODO:

  • Filter our list of todos, returning a new list of all todos whose id does not match the id passed with our action's payload, therefore removing the todo item from the list.

If the dispatched action has a type property of TOGGLE_COMPLETED:

  • Loop through our list of todos, looking for the todo whose id property matches the id from the action's payload. If they do not match, return the todo item as is. If a match is found, copy the todo item's properties, replacing the completed property with the opposite of what it was.

If none of those are true and we receive an unrecognized action, return the current state as is.

Putting It All Together

We have covered the basic components of how to use the reducer hook for managing more complex state. Let's look at a more practical example of using useReducer for managing state in a typical contact form component.

Let's start by building out the very basic structure of our form component.

import React, { useReducer } from  'react'

const Form = () => {

  // for now, we will just prevent the default 
  // behaviour upon submission
  handleSubmit = e => {
    e.preventDefault()
  }

  return (
    <>
      <h1>Send a Message</h1>
      <form onSubmit={handleSubmit}>
        <label htmlFor='name'>
          Name
          <input id='name' name='name' type='text' />
        </label>
        <label htmlFor='email'>
          Email
          <input id='email' name='email' type='email' />
        </label>
        <label htmlFor='subject'>
          Subject
          <input id='subject' name='subject' type='text' />
        </label>
        <label htmlFor='body'>
          Body
          <textarea id='body' name='body' />
        </label>
        <button type='submit'>
          Send
        </button>
      </form>
    </>
  )
}

export default Form
Enter fullscreen mode Exit fullscreen mode

Next, let's declare our action types, an object representing our initial state, and our reducer function. You can declare these inside of your component or out, or write them in a separate file and import them where needed. For this example, I will be declaring them in the same file, but outside of our component to keep our <Form /> a bit less cluttered and easier to read.

We also need to initialize our useReducer hook, passing it our newly created reducer function and initial state object.

For variety, I will use a switch statement in our reducer.

import React, { useReducer } from  'react'

// action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'

// initial state
const INITIAL_STATE = {
  name: '',
  email: '',
  subject: '',
  body: '',
}

// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }
    default: 
      return INITIAL_STATE
}

// form component
const Form = () => {
  // initialize useReducer
  const [state, dispatch] = useReducer(formReducer, INITIAL_STATE)
  ...
Enter fullscreen mode Exit fullscreen mode

Now we need to give control of our inputs over to React so that we can store the input values in state.

First, let's set the value of each input to the respective value stored in state.

<input 
  id='name'
  name='name' 
  type='text' 
  value={state.name} 
/>
Enter fullscreen mode Exit fullscreen mode

Doing this alone will disable our input because we have hardcoded the value to an empty string with no instructions for how to handle the change event.

So, we also need to provide an onChange attribute to our input and pass to it a function so that we can update the values stored in state.

<input 
  id='name' 
  name='name' 
  type='text' 
  value={state.name} 
  onChange={e => updateFieldValue(e.target.name, e.target.value)}
/>
Enter fullscreen mode Exit fullscreen mode

And our updateFieldValue handler function:

const updateFieldValue = (field, value) => {
  dispatch({
    type: UPDATE_FIELD_VALUE,
    payload: {
      field,
      value,
    },
  })
}
Enter fullscreen mode Exit fullscreen mode

Now when a user types in our input field, the updateFieldValue function is triggered, which dispatches an action to our formReducer with a type of UPDATE_FIELD_VALUE, and a payload which includes the field that was updated, and the new value of that field.

Our formReducer knows what to do with this action type and returns a new state with the updated field values.

Here is what our Form component looks like thus far:

import React, { useReducer } from  'react'

// initial state values
const INITIAL_STATE = {
  name: '',
  email: '',
  subject: '',
  body: '',
}

// action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'

// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case  UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }
    default:
      return INITIAL_STATE
  }
} 

// Form component
const Form = () => {
  const [state,  dispatch] = useReducer(formReducer, INITIAL_STATE)

  // input change handler function
  const updateFieldValue = (field, value) => {
    dispatch({
      type: UPDATE_FIELD_VALUE,
      payload: {
        field,
        value,
      },
    })
  } 

  // submit handler
  const handleSubmit = event => {
    event.preventDefault()
  } 

  return (
    <>
      <h1>Send a Message</h1>
      <form onSubmit={handleSubmit}>
        <label htmlFor='name'>
          Name
          <input
            id='name'
            name='name'
            type='text'
            value={state.name}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='email'>
          Email
          <input
            id='email'
            name='email'
            type='email'
            value={state.email}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='subject'>
          Subject
          <input
            id='subject'
            name='subject'
            type='text'
            value={state.subject}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
          />
        </label>
        <label htmlFor='body'>
          Body
          <textarea
            id='body'
            name='body'
            type='text'
            value={state.body}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <button type='submit'>
          Send
        </button>
      </form>
    </>
  )
}

export  default  Form
Enter fullscreen mode Exit fullscreen mode

Our form is successfully using the reducer hook to update and keep track of our input values in state. Now we need to handle the various states associated with submitting the form and display those states to the user.

Adding Form States

At this point, we only have one type of action for updating the values of our various input fields in state. This alone is a valid use case for useReducer, but when thinking about all of the states involved with submitting a form, updating and storing the input values is only one small piece of the equation.

Here are a few of the common states that our form could be in:

  • Idle: Our initial state. An empty form, ready to be filled out and submitted;
  • Pending: We submitted the form and are waiting to find out if the submission was successful or not;
  • Success: Our form was submitted successfully;
  • Error: Something went wrong while trying to send the form;

All of these form states need to be tracked and communicated to the user. Each status will be represented by a different UI.

Let's add a new action type for representing these state changes:

// action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'
const UPDATE_STATUS = 'UPDATE_STATUS'
Enter fullscreen mode Exit fullscreen mode

Similar to our action types, I'm going to declare a few new variables for our current form states to avoid the issue I mentioned earlier with using strings instead of variables. We want useful error messages if we end up making a spelling mistake.

// form status variables
const IDLE = 'UPDATE_FIELD_VALUE'
const PENDING = 'PENDING'
const SUCCESS = 'SUCCESS'
const ERROR = 'ERROR'
Enter fullscreen mode Exit fullscreen mode

Also add a new status property to our initial state with default value of IDLE

// initial state
const INITIAL_STATE = {
  name: '',
  email: '',
  subject: '',
  body: '',
  status: IDLE,
}
Enter fullscreen mode Exit fullscreen mode

We now need to add a new case for dealing with an action type of UPDATE_STATUS. If an action is dispatched with a type of UPDATE_STATUS, we return a copy of the state as is, replacing the value of our status property with the new value from our actions's payload.

// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }  
    case UPDATE_STATUS:
      return { ...state, status: action.payload.status }
    default: 
      return INITIAL_STATE
}
Enter fullscreen mode Exit fullscreen mode

Inside of our Form component, let's add a new handler function for communicating that an UPDATE_STATUS event has occurred. We will call this handler updateStatus.

// Form component
const Form = () => {
  const [state,  dispatch] = useReducer(formReducer, INITIAL_STATE)

  // handler functions
  const updateFieldValue = (field, value) => {
    dispatch({
      type: UPDATE_FIELD_VALUE,
      payload: {
        field,
        value,
      },
    })
  } 

  const updateStatus = status => {
    dispatch({
      type: UPDATE_STATUS,
      payload: {
        status,
      },
    })
  }
  ...
Enter fullscreen mode Exit fullscreen mode

We can now give our handleSubmit function the logic for updating the status property in state. Typically, you would send a POST request to some sort of API responsible for handling incoming messages in a useEffect hook. This API would then communicate whether or not this was successful by providing an error response or a success response. For now, we will mock this functionality by initially setting our status to PENDING, then after two seconds, setting its value to SUCCESS.

  ...
  // submit handler
  const handleSubmit = event => {
    event.preventDefault()
    updateStatus(PENDING) 

    setTimeout(()  =>  {
      updateStatus(SUCCESS)
    },  2000)
  } 
  ...
Enter fullscreen mode Exit fullscreen mode

Now in our form, we can add some markup for displaying IDLE, PENDING, SUCCESS, and ERROR states to the user.

...
  // Success state
  if (state.status === SUCCESS) {
    return <p>Your message was sent successfully.</p>   
  }  

  // Error state
  if  (state.status === ERROR) {
    return <p>Oops! Something went wrong...</p>
  } 

  // Default State
  return (
    <>
      <h1>Send a Message</h1>
      <form onSubmit={handleSubmit}>
        <label htmlFor='name'>
          Name
          <input
            id='name'
            name='name'
            type='text'
            value={state.name}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='email'>
          Email
          <input
            id='email'
            name='email'
            type='email'
            value={state.email}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='subject'>
          Subject
          <input
            id='subject'
            name='subject'
            type='text'
            value={state.subject}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
          />
        </label>
        <label htmlFor='body'>
          Body
          <textarea
            id='body'
            name='body'
            type='text'
            value={state.body}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <button type='submit' disabled={state.status === PENDING}>
          {state.status !== PENDING ? 'Send' : 'Sending...'}
        </button>
      </form>
    </>
  )
}

export  default  Form
Enter fullscreen mode Exit fullscreen mode

With this in place, upon submission of our form, the status is set to PENDING for two seconds, which disables the submit button and changes the button text to Sending... instead of Send.

After two seconds, the status is set to SUCCESS which renders the message Your message was sent successfully. instead of our form.

To see the ERROR message right now, you can hardcode the status to ERROR in the INITIAL_STATE, which will display the message Oops! Something went wrong... instead of our form.

At this point, we have the base functionality in place for managing state in most forms. You will still need to swap out our submit handler with real functionality and also write your styles for helping communicate the various form states.

The only missing piece is a reset button for allowing the user to send another message upon a successful or unsuccessful submit attempt. For this, we will utilize the optional third parameter to useReducer that I mentioned at the beginning of this article.

Lazy Initialization

useReducer also gives us the ability to create the initial state lazily. To do this, you can pass an init function as the optional third argument.

The initial state will be set to init(initialState).

const [todos, dispatch] = useReducer(reducer, initialState, init);
Enter fullscreen mode Exit fullscreen mode

The init function lets you extract the logic for calculating the initial state outside of the reducer. This is also handy for resetting the state to its initial values in response to an action.

In our case, this action will have a type of RESET, so lets add another action type for this:

//action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'
const UPDATE_STATUS = 'UPDATE_STATUS'
const RESET = 'RESET'
Enter fullscreen mode Exit fullscreen mode

Declare our init function:

// init function passed as optional 3rd argument for lazy initialization
const init = initialState => initialState
Enter fullscreen mode Exit fullscreen mode

Add a new case for for handling the new action type

// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case  UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }
    case UPDATE_STATUS:
      return { ...state, status: action.payload.status }
    case RESET:
      return init(INITIAL_STATE)
    default:
      return INITIAL_STATE
  }
} 
Enter fullscreen mode Exit fullscreen mode

Pass our init function as the third argument to useReducer:

// Form component
...
const Form = () => {
  const [state,  dispatch] = useReducer(formReducer, INITIAL_STATE, init)
...  
Enter fullscreen mode Exit fullscreen mode

Add a new handler function:

...
const resetForm = () => {
  dispatch({ type: RESET })
}
...
Enter fullscreen mode Exit fullscreen mode

And lastly, update our SUCCESS and ERROR UI's to include a button that triggers our resetForm handler function, setting the form back to its original state and displaying that to the user.

...
// Success state
if (state.status === SUCCESS)  {
  return (
    <>
      <p>Your message was sent successfully.</p>
      <button type='button' onClick={resetForm}>
        Send Another Message
      </button>
    </>
  )
}  

// Error state
if (state.status === ERROR)  {
  return (
    <>
      <p>Something went wrong...</p>
      <button type='button' onClick={resetForm}>
        Try Again
      </button>
    </>
  )
}
... 
Enter fullscreen mode Exit fullscreen mode

Our Finished Form Component

import React, { useReducer } from 'react'

// form status variables
const IDLE = 'UPDATE_FIELD_VALUE'
const PENDING = 'PENDING'
const SUCCESS = 'SUCCESS'
const ERROR = 'ERROR'

// initial state values
const INITIAL_STATE = {
  name: '',
  email: '',
  subject: '',
  body: '',
  status: IDLE,
}

// action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'
const UPDATE_STATUS = 'UPDATE_STATUS'
const RESET = 'RESET'

// 3rd parameter for lazy initialization
const init = initialState => initialState

// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case  UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }
    case UPDATE_STATUS:
      return { ...state, status: action.payload.status }
    case RESET:
      return init(INITIAL_STATE)
    default:
      return INITIAL_STATE
  }
} 

// Form component
const Form = () => {
  const [state, dispatch] = useReducer(formReducer, INITIAL_STATE, init)

  // handler functions
  const updateFieldValue = (field, value) => {
    dispatch({
      type: UPDATE_FIELD_VALUE,
      payload: {
        field,
        value,
      },
    })
  } 

  const updateStatus = status => {
    dispatch({
      type: UPDATE_STATUS,
      payload: {
        status,
      },
    })
  }

  const resetForm = () => {
    dispatch({ type: RESET })
  }

  // MOCK submit handler
  const handleSubmit = event => {
    event.preventDefault()
    updateStatus(PENDING) 

    setTimeout(()  =>  {
      updateStatus(SUCCESS)
    },  2000)
  } 

  // Success state UI
  if (state.status === SUCCESS)  {
    return (
      <>
        <p>Your message was sent successfully.</p>
        <button type='button' onClick={resetForm}>
          Send Another Message
        </button>
      </>
    )
  }  

  // Error state UI
  if (state.status === ERROR)  {
    return (
      <>
        <p>Something went wrong...</p>
        <button type='button' onClick={resetForm}>
          Try Again
        </button>
      </>
    )
  } 

  // Default state UI
  return (
    <>
      <h1>Send a Message</h1>
      <form onSubmit={handleSubmit}>
        <label htmlFor='name'>
          Name
          <input
            id='name'
            name='name'
            type='text'
            value={state.name}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='email'>
          Email
          <input
            id='email'
            name='email'
            type='email'
            value={state.email}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='subject'>
          Subject
          <input
            id='subject'
            name='subject'
            type='text'
            value={state.subject}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
          />
        </label>
        <label htmlFor='body'>
          Body
          <textarea
            id='body'
            name='body'
            type='text'
            value={state.body}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <button type='submit' disabled={state.status === PENDING}>
          {state.status !== PENDING ? 'Send' : 'Sending...'}
        </button>
      </form>
    </>
  )
}

export  default  Form
Enter fullscreen mode Exit fullscreen mode

Recap

  • useReducer is preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one;
  • When called, useReducer returns an array of two items: the current state, and a dispatch method;
  • useReducer accepts three arguments: A reducer function, the initial state, and the optional init function for lazy initialization of state;
  • In React, reducers are responsible for handling transitions from one state to the next state in your application. Reducers take in the current state and an action and return an entirely new state;
  • Actions express unique events that happen throughout your application.
  • A few general conventions for actions have been described by the Flux standard for action objects;
  • Actions are dispatched to our reducer when specific events take place;

Thanks for reading!

Top comments (10)

Collapse
 
dagolinuxoid profile image
Artur Haurylkevich • Edited

It's a good post!! The only thing I'm not agree about is the naming of action creaters you've used on updateStatus and reset functions. They are more like dispatch callers/invokers. An action creater would be something like this let fnAC = ()=>({type: 'valueOfACObject'})

Collapse
 
brettblox profile image
Brett Bloxom

Ah, thank you! That is a really good point. I completely agree. My "action creators" are not just creating actions, but also calling the dispatch function so using that term is not accurate. I am going to revise this. Thinking I will just keep the handler functions and remove the concept of "action creators" altogether as I like having these handlers much more.

Collapse
 
olivierjm profile image
Olivier JM Maniraho

This is probably the best article on useReducer, it should be linked in the official docs.
Well explained and demoed.

Collapse
 
brettblox profile image
Brett Bloxom

Wow, thank you so much, Olivier. That means a lot. I'm glad you got something out of it.

Collapse
 
olivierjm profile image
Olivier JM Maniraho

Thanks for sharing.

Collapse
 
dance2die profile image
Sung M. Kim

Nice article and thank you for hsaring, Brett~

Also loved the summary in the beginning of the article.

Collapse
 
brettblox profile image
Brett Bloxom

Thank you, Sung! I really appreciate that.

Collapse
 
dance2die profile image
Sung M. Kim

You're welcome and thank you for sharing~

Collapse
 
quantumk9 profile image
QuantumK9

Indeed one of the best explanations so far!! Thanks a lot for putting this together!
The 3rd argument (init) was giving me a hard time , but your explanation was simple and to the point. Cheers!

Collapse
 
brettblox profile image
Brett Bloxom

Hey QuantumK9! Thanks so much for that response. I'm glad you were able to get some value out of this post.