Skip to content

Instantly share code, notes, and snippets.

@tmkelly28
Last active July 6, 2022 15:35
Show Gist options
  • Save tmkelly28/0518885e20c300c3609c6591f2913368 to your computer and use it in GitHub Desktop.
Save tmkelly28/0518885e20c300c3609c6591f2913368 to your computer and use it in GitHub Desktop.

Part 4. Usage with React

Now that we know how createStore works, and we know how to write reducer functions, we can use it to manage state in our applications!

We want to write a React application that uses Redux for state management. However, Redux doesn't know anything about React. (There is a library called react-redux, which provides a special method that we can use to make React and Redux talk to each other in an intuitive way, but we won't use it for now. We'll see how it works fairly soon, though.)

Let's say we have a React component that renders a simple counter. Something like this:

import React from 'react';
class Counter extends React.Component {
  
  constructor () {
    super();
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick () {
    this.setState({ counter: this.state.counter + 1 })
  }
  
  render () {
    return (
      <div>
        <h3>The count is: { this.state.count }</h3>
        <button onClick={this.handleClick}>Increase</button>
      </div>
    )
  }
}

Right now, this component's state is managed by the React component. However, we want it to be in the Redux store instead. To start writing our redux store, we'll ask ourselves three questions:

Question One: What actions can the user take in our app?

We want to create a store. To create a store, we need to create a reducer function. To create our reducer function, we need to think about the actions that a user can take in our application (either actively, by clicking a button, or passively, by loading the page).

What actions can be taken in our React component above?

...

Just the one - clicking the button. Clicking the button causes the counter to increment. Let's imagine this action as a string constant, the same way we imagined the actions the user could take with the remote control car.

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

We've written an action type. Thinking about our application in terms of action types will help make sure that our state is focused on actual user interactions. This will help us keep our state simple and lean.

Question 2: How can we represent the state of our app?

In both React and Redux, we represent the state of our application as a JavaScript object. Ideally, we should represent it as a JavaScript object with all of the default values of our state filled in.

What would this look like for our counter application?

...

It would look very similar to the way it does in our React component. We'll put it in a variable called "initialState":

const initialState = {
  count: 0
}

The only thing that changes over time in our application is the number of the count. Therefore, we'll represent state as an object with a key-value pair of count, which we initialize at 0.

Question 3: How does our state change in response to our actions?

When someone performs an action, we want our state to change in response. This is what the reducer is for! Remember what a reducer function looks like:

const initialState = { counter: 0 };

function reducer (prevState = initialState, action) {

  const newState = Object.assign({}, prevState);
  
  switch (action.type) {
  
    // switch on our action types here...
  
  }
  
  return newState;

}

Our only action type is INCREMENT_COUNTER, so we simply need to describe how the state changes in that case.

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

const initialState = { counter: 0 };

function reducer (state = initialState, action) {

  const newState = Object.assign({}, state);
  
  switch (action.type) {
  
    case INCREMENT_COUNTER:
      newState.counter += 1;
      break;
      
  }
  
  return newState;

}

Remember, the reducer needs to return a new object each time - it must not mutate the previous state object directly. This is why we create a copy of the previous start first, and then apply the changes to that.

Now that we have our reducer, we can create our store:

import { createStore } from 'redux';

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

const initialState = { counter: 0 };

function reducer (state = initialState, action) {

  const newState = Object.assign({}, state);
  
  switch (action.type) {
  
    case INCREMENT_COUNTER:
      newState.counter += 1;
      break;
      
  }
  
  return newState;

}

const store = createStore(reducer);

export default store;

Now that we have our store, let's examine the initial state that it contains:

store.getState(); // { counter: 0}

It's equal to the default argument from our reducer function, just like we expected.

Remember that to change the state, we need to dispatch an action with a type of INCREMENT_COUNTER.

store.dispatch({ type: 'INCREMENT_COUNTER' });
store.getState(); // { counter: 1 }

Now we have a store that's ready to be used with a real (albeit simple) React component.

@tmkelly28
Copy link
Author

Thanks for pointing out the typo, @Takeyourvitaminsbro - fixed

@jason-jbell
Copy link

jason-jbell commented Dec 2, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment