DEV Community

loading...
Cover image for How Redux works ? (Only HTML & Pure JS)

How Redux works ? (Only HTML & Pure JS)

papercoding22 profile image Trung Nguyen Updated on ・3 min read

This is a code example of Redux with only HTML & pure JavaScript. Code sandbox

<!DOCTYPE html>
<html>
  <head>
    <title>Redux basic example</title>
    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
  </head>
  <body>
    <div>
      <p>
        Clicked: <span id="value">0</span> times
        <button id="increment">+</button>
        <button id="decrement">-</button>
        <button id="incrementIfOdd">Increment if odd</button>
        <button id="incrementAsync">Increment async</button>
      </p>
    </div>
    <script>
      function counter(state, action) {
        if (typeof state === 'undefined') {
          return 0
        }

        switch (action.type) {
          case 'INCREMENT':
            return state + 1
          case 'DECREMENT':
            return state - 1
          default:
            return state
        }
      }

      var store = Redux.createStore(counter)
      var valueEl = document.getElementById('value')

      function render() {
        valueEl.innerHTML = store.getState().toString()
      }

      render()
      store.subscribe(render)

      document.getElementById('increment')
        .addEventListener('click', function () {
          store.dispatch({ type: 'INCREMENT' })
        })

      document.getElementById('decrement')
        .addEventListener('click', function () {
          store.dispatch({ type: 'DECREMENT' })
        })

      document.getElementById('incrementIfOdd')
        .addEventListener('click', function () {
          if (store.getState() % 2 !== 0) {
            store.dispatch({ type: 'INCREMENT' })
          }
        })

      document.getElementById('incrementAsync')
        .addEventListener('click', function () {
          setTimeout(function () {
            store.dispatch({ type: 'INCREMENT' })
          }, 1000)
        })
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The webpage looks like this
Alt Text

  1. createStore & counterReducer
// Counter reducer
function counterReducer(state, action) {
    if (typeof state === 'undefined') {
        return 0;
    }
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}
// Create store
var store = Redux.createStore(counterReducer);
Enter fullscreen mode Exit fullscreen mode
  • createStore receives a counterReducer function as a param and return an object called store.
  • This is the diagram of createStore function with mental model as a class. Alt Text

Here is simplified version of createStore in redux source code:

function createStore(reducer, initialState) {
  var currentReducer = reducer;
  var currentState = initialState;
  var listeners = [];
  var isDispatching = false;

  function getState() {
    return currentState;
  }

  function subscribe(listener) {
    listeners.push(listener);

    return function unsubscribe() {
      var index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  function dispatch(action) {
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.');
    }

    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    listeners.slice().forEach(listener => listener());
    return action;
  }

  function replaceReducer(nextReducer) {
    currentReducer = nextReducer;
    dispatch({ type: '@@redux/INIT' });
  }

  dispatch({ type: '@@redux/INIT' });

  return { dispatch, subscribe, getState, replaceReducer };
}
Enter fullscreen mode Exit fullscreen mode
  • currentReducer = counterReducer
  • currentState = preloadedSate
  • When store is created, it initially dispatch with action type is '@@redux/INIT' so that every reducer returns their initial state. In case counterReducer, it returns 0

What happens inside dispatch function ?

// Dispatch function inside Redux store
function dispatch(action: A) {    
    currentState = currentReducer(currentState, action)
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
    }
    return action
}
Enter fullscreen mode Exit fullscreen mode
  • The function currentReducer is called which is counterReducer
  • Because action type is @@redux/INIT and currentState is undefined, so counterReducer returns 0 as default value which is the initial state of the store.
  • Now, currentState is 0
  • After updating the state with initial value, it calls all listeners that is subscribing the store to notify.
var valueEl = document.getElementById('value')

function render() {
  valueEl.innerHTML = store.getState().toString()
}

render()
store.subscribe(render)
Enter fullscreen mode Exit fullscreen mode
  • In this case, we have render() function, it is called back and update the DOM element with the initial value.
  • Now in the browser, we will se the number 0 shown.

Updating state when action is sent

document.getElementById('increment')
    .addEventListener('click', function () {
      store.dispatch({ type: 'INCREMENT' })
    })
Enter fullscreen mode Exit fullscreen mode
  • When users click on the button "+", store dispatches the action with type 'INCREMENT' to the reducer of the store and the flow is the same as explanation above.
  • Function currentReducer is called with state is 0 and action's type is 'INCREMENT'.
  • Because 'INCREMENT' is a case inside counterReducer function, so the new state now is equal to 0 + 1 and returned to the state of the store.
  • Next, again it notifies listeners to let them know state is updated successfully.
  • Now, in the screen we will see Clicked: 1 times
  • The flow is similar to other action types

So this is basically how Redux works under the hood. In real life project, Redux store may have multiple reducers and midleware, and 3rd-party libraries enhance Redux workflow. But at very its core that's how it works basically !

Discussion (3)

Collapse
ahmetcengiz06 profile image
ahmetcengiz06

I use index.js file that contains in script tags codes. However, getting error Redux.create is not a function.

Collapse
papercoding22 profile image
Collapse
ahmetcengiz06 profile image
ahmetcengiz06

I want to create a another js file for script tag but getting error. I don't want to any script code in index.html but unpkg.com/redux@latest/dist/redux.... isn't reached other js file

Forem Open with the Forem app