Persisting State and Displaying State

Learning Goals

  • Persist changes to state
  • Display changes to the state in the view

Instructions

An HTML page, index.html, is provided and already linked to src/reducer.js. Open index.html in browser to access the functions in reducer.js and follow along.

Building our Counter Application

Previously, we've had a reducer that updates state. In the example we used, we defined a switch statement with one case and a default:

function changeState(state, action) {
  switch (action.type) {
    case "counter/increment":
      return { count: state.count + 1 };
    default:
      return state;
  }
}

let state = { count: 0 };
let action = { type: "counter/increment" };

state = changeState(state, action);
// => {count: 1}

Persisting State

Any time we want to preserve the return value of our reducer function, we need to store it in some variable, like so:

state = changeState(state, { type: "counter/increment" });
//  => {count: 1}
state = changeState(state, { type: "counter/increment" });
//  => {count: 2}

Here, we're reassigning state to the return value of our reducer. This way, each time changeState is called, it is using the updated state in its arguments.

Let's encapsulate this procedure in a function so that we can just call that method and it will persist our changes. We'll name that function dispatch.

let state = { count: 0 };

function changeState(state, action) {
  switch (action.type) {
    case "counter/increment":
      return { count: state.count + 1 };
    default:
      return state;
  }
}

function dispatch(action) {
  state = changeState(state, action);
  return state;
}

dispatch({ type: "counter/increment" });
// => {count: 1}
dispatch({ type: "counter/increment" });
// => {count: 2}
dispatch({ type: "counter/increment" });
// => {count: 3}

Nice! Now we just call our dispatch function, and pass through our action, and our state is preserved. Let's walk through it.

We declare our state variable and assign it a value of {count: 0}. Then, we define our reducer and our new function dispatch. At the bottom, we call the dispatch function and pass through our action, {type: 'counter/increment'}. When we call dispatch, this calls our changeState reducer, and passes the action object to the reducer. When called, the changeState reducer also takes in state, which has been declared up above.

state is assigned the return value of changeState. Since the counter/increment type was used, the returned value of changeState contains a count equal to the previous state's count plus one.

Thus, our state is updated. Each time dispatch is called, the current version of state is passed into changeState, and then state is assigned a new value based on what changeState returns.

Rendering Our State

Ignoring React for a bit, how would we display something like this on a page? And how would we make sure that our HTML updates every time we change our state? Well, to render this on the page we can write a render function that changes our HTML:

function render() {
  const app = document.querySelector("#app");
  app.textContent = state.count;
}

Now if we want to call our render function, we'll see our state on the page:

let state = { count: 0 };

function changeState(state, action) {
  switch (action.type) {
    case "counter/increment":
      return { count: state.count + 1 };
    default:
      return state;
  }
}

function dispatch(action) {
  state = changeState(state, action);
  return state;
}

function render() {
  const app = document.querySelector("#app");
  app.textContent = state.count;
}

// call the render function
render();

And presto! Our number is displayed on the page. However, we want to call render every time our state changes. And it's safe to say our state will not change unless we call the dispatch function. So we can ensure that the render function runs every time that our dispatch function is called by changing our dispatch function to the following.

function render() {
  const app = document.querySelector("#app");
  app.textContent = state.count;
}

function dispatch(action) {
  state = changeState(state, action);
  render();
}

dispatch({ type: "counter/increment" });
dispatch({ type: "counter/increment" });

Summary

Just to show everything together finally:

let state = { count: 0 };

function changeState(state, action) {
  switch (action.type) {
    case "counter/increment":
      return { count: state.count + 1 };
    default:
      return state;
  }
}

function render() {
  const app = document.querySelector("#app");
  app.textContent = state.count;
}

function dispatch(action) {
  state = changeState(state, action);
  render();
}

render();

If you copy the code above into src/reducer.js and open index.html in your browser, in the console, you can confirm everything is working by running dispatch({type: 'counter/increment'}) and watch as the displayed count increases!

With just this set of functions, we could actually apply our own Redux pattern to a regular ol' JavaScript and HTML webpage!

In the future, we'll also see how to dispatch actions in response to user events.

Conclusion

In this lesson, we've introduced a new function called the dispatch function. Our dispatch function solved two problems for us.

First, it persisted changes to our state. This is because we called the dispatch function, the dispatch function called our reducer, and then we took the return value from the reducer and assigned it to be our new state.

Second, it ensured that each time our state updates, our HTML updates to reflect these changes. It does this by simply calling the render function. Each time we call dispatch it's as if we are then calling render. Don't worry about re-rendering too much. Remember that when we integrate with React, React will only be virtually re-rendering the DOM, and then updating the DOM with the smallest number of changes to ensure a performant application.