/ react

Using Redux and ES6 without Reactjs

Attention: I ill not explain how redux works here! Just how to use with vanillajs and es6.

I really love to work with ReactJS and Redux, but somethings react is a bazooka shot if you need just something simple.

Is not easy to find posts talking about redux and pure es6 and because of that i ill to keep this post live.

So lets try todo do something simple now, lets create a famous Counter component with redux and es6 (everybody use a counter to show how redux works, so lets do it too but with es6 and without react)

1. Installation

First of all, lets create a simple project with redux lib and babel. And for it we can use the parceljs.

    $ npm install parcel-bundler --save-dev
    $ npm install redux --save

2. Project structure

And now lets create the structure of our project, lets now create our index.html and a empty index.js at same folder, in my sample i ill put all my files inside a /src/ folder

<html>
  <head>
    <title>Simple Counter with Redux and ES6</title>
  </head>
  <body>
    <section id="counter">
      <button id="add">+</button>
      <span id="result">0</span>
      <button id="subtract">-</button>
      <br />
      <span id="last-command"></span>
    </section>
    <!-- put the path for yout index.js file here -->
    <script src="./index.js"></script>
  </body>
</html>

Your folder structure should looks like this:
files-1

And if you run your project using parceljs you will see something like this running on your browser:

parcel ./src/index.html

running

We have now our button to add, the value of our counter and a button to subtract.

Using this structure we ill create a class to manage these elements and it all ill work as component.

3. Creating the redux layers

First, lets create the actions of our component, i did it at the following path /src/actions/CounterActions.js.

And what kind of actions we ill have? We ill have 2 actions, an action to add the counter and other to subtract the counter.

CounterActions.js

export const ActionsTypes = { // here we create our action types
  ADD: 'ADD',
  SUBTRACT: 'SUBTRACT',
};

export const add = () => ({ // and then we create our add action
  type: ActionsTypes.ADD,
});

export const subtract = () => ({ // and the subtract action
  type: ActionsTypes.SUBTRACT,
});

Second step is create our reducer to 'listen' our actions and return the right data to next layer: the store.
I created at: /src/reducers/CounterReducer.js

CounterReducer.js

import { combineReducers } from 'redux';

import { ActionsTypes as CounterActionTypes } from '../actions/CounterActions';

const result = (state = 0, action) => {
  switch (action.type) {
    case CounterActionTypes.ADD:
      return state + 1;
    case CounterActionTypes.SUBTRACT:
      return state - 1;
    default:
      return state;
  }
};

const lastCommand = (state = '', action) => {
  switch (action.type) {
    case CounterActionTypes.ADD:
      return 'added';
    case CounterActionTypes.SUBTRACT:
      return 'subtracted';
    default:
      return state;
  }
};

export default combineReducers({
  result,
  lastCommand,
});

I created 2 reducers, the first reducer result is the calc of our counter and the second reducer lastCommand is to save and show what was our last command.

FINALLY

Create a file called CounterComponent.js and in this file we ill code a class to represent our component and connect the actions and store. I created this file inside a path /src/components/CounterComponent.js.

Now create our class with a constructor and inside this constructor we ill map the elements on page to props of our class.

CounterComponent.js

// Lets import the actions to use on dispatch
import { add, subtract } from '../actions/CounterActions';

class CounterComponent {
  constructor(store) {
    this.store = store;
    this.counter = document.querySelector('#counter');

    this.add = this.counter.querySelector('#add');
    this.subtract = this.counter.querySelector('#subtract');
    this.result = this.counter.querySelector('#result');
    this.lastCommand = this.counter.querySelector('#last-command');

    this.add.addEventListener('click', this.handleAdd.bind(this));
    this.subtract.addEventListener('click', this.handleSubtract.bind(this));
  }

  // This is the handler to dispatch the add action on button click
  handleAdd() {
    this.store.dispatch(add());
  }

  // This is the handler to dispatch the subtract action on button click
  handleSubtract() {
    this.store.dispatch(subtract());
  }

  // this method ill 'listen' the changes of state
  handleUpdate() {
    this.store.subscribe(() => {
      const state = this.store.getState();
      this.result.innerHTML = state.result;
      this.lastCommand.innerHTML = state.lastCommand;
    });
  }

  // the render method ill call the update
  render() {
    this.handleUpdate();
  }
}

export default CounterComponent;

Lets explain what is happening in this file:

In constructor we are mapping the html elements to internal properties of our class and adding the listeners to elements.
When we click on a button it ill trigger the internal method handleAdd or handleSubtract

Also we are saving the store to a internal property because the store ill provide the listener and dispatcher for us;

constructor(store) {
    this.store = store;
    this.counter = document.querySelector('#counter');

    this.add = this.counter.querySelector('#add');
    this.subtract = this.counter.querySelector('#subtract');
    this.result = this.counter.querySelector('#result');
    this.lastCommand = this.counter.querySelector('#last-command');

    this.add.addEventListener('click', this.handleAdd.bind(this));
    this.subtract.addEventListener('click', this.handleSubtract.bind(this));
  }

Our handle methods ill dispatch the actions using the saved store from property

  handleAdd() {
    this.store.dispatch(add());
  }

  handleSubtract() {
    this.store.dispatch(subtract());
  }

After the dispatch, our reducer ill do their job manipulating the data and returning the NEW data to store and we ill 'listen' this change on our handleUpdate.

We do not render any kind of elements, because in this sample our elements already is in our html, so our render method ill just call the handleUpdate to update data on our view.

handleUpdate() {
    this.store.subscribe(() => {
      const state = this.store.getState();
      this.result.innerHTML = state.result;
      this.lastCommand.innerHTML = state.lastCommand;
    });
  }

  render() {
    this.handleUpdate();
  }

4. Connection everything

Now, the final step is code our index.js to load our component when page is loaded and it is very simple:

// Import the createStore
import { createStore } from 'redux';

// Import your reducer and component
import CounterReducer from './reducers/CounterReducer';
import CounterComponent from './component/CounterComponent';

// Create the store with your reducer
const store = createStore(CounterReducer);

// And then the magic happens, we ill call our component class 
// on load of page and we ill call the render method to update 
// the page for the first time
document.addEventListener('DOMContentLoaded', () => {
  console.log('loaded');
  const counter = new CounterComponent(store);
  counter.render();
});

And it ill now works

recording--2-

You can check the working code on github
https://github.com/renanborgez/sample-redux-es6-vanillajs