How you can do unit and integration tests for a React/Redux

How you can do unit and integration tests for a React/Redux

How you can do unit and integration tests for a React/Redux.These series of posts will, hopefully, give you a good overview and examples on how you can do unit and integration tests for a react & redux app,

Unit vs. Integration Testing

Before we dive deep into the subject, let’s make sure we all understand the basics. There are many different types of app testing, but a 2018 survey shows that automated unit and integration tests are at the top of the list.

For a better comparison, I’m only picking the two main automated testing methods. Let’s look at the definitions and characteristics of unit and integration testing:

Test Preparations: Form Review

Before you start any job, you want to know all about it. You don’t want any surprises, and you do want the best results. This is also true for testing. Which is why it’s better to get all the available information on the form that should be tested, and its related conditions beforehand. And, of course, to make sure you know what exactly should be tested.

To show you how it goes, I chose a form that contains information on Property Evaluation. It’s a form that customers fill in to describe the property they would like to purchase. It’s quite simple — it doesn’t have any complex logic or mandatory fields, and a few fields to fill in.

Check out the image below:

The only logic you can’t see in the image are different fields that are set depending on the choice in the Property type field. For instance:

  • If a customer chooses ‘Apartment’, they get options like ‘floor’, ‘parking conditions’, etc.
  • If a customer chooses ‘House’, they get options like ‘floor area’, ‘building standard’, etc.

Next, let’s dive into the form’s code. The implementation of the form is divided into two parts:

  1. Template file — listing of all fields; we can also call it ‘view’ (Code Listing of PropertySelfOwnedForm.js on github)
  2. Container file — form logic, stored in one place (Code Listing of PropertySelfOwnedFormContainer.js on github)
Testing Forms Connected with Redux

Depending on the type of testing, I use different processes to test forms connected with Redux.

For unit tests, I use shallow rendering (as opposed to deep tree rendering) and the Redux-mock-store library. For integration tests, I use mount rendering (deep tree rendering) and an actual Redux store.

Unit Testing of Forms Connected with Redux

As I said above, for unit testing I use shallow rendering. It’s a one-level rendering that doesn’t take into account child components inside the component in question. On top of this, the tested component doesn’t indirectly affect its child components behavior.

Redux-mock-store is a library designed to test action logic, and provides a mocked Redux store. It’s easy to launch and to use, and doesn’t affect the Redux store itself.

Before you start testing, be sure to configure the form.

These are my imports:

  • Rendering method: Enzyme’s shallow renderer
  • Include mocked data required for form rendering. In the example below it is json file djangoParamsChoices, containing mocked data for select options. This data is being passed to context on the backend side and fetched on the frontend side through the custom function getDjangoParam.
  • Include form view itself
  • Import additional tools for store mock
  • Import additional libraries for test needs (mainly required when writing the special test case)
import { shallow } from ‘enzyme’;
import djangoParamsChoices from ‘tests/__mocks__/djangoParamsChoices’;
import PropertySelfOwnedForm from ‘../PropertySelfOwnedForm’;
import configureStore from ‘redux-mock-store’;
const snapshotDiff = require(‘snapshot-diff’);
  • Initialize mockstore with empty state:
const initialState = {};
  • Set default props (they vary from the tested form requirements):

The form view depends on the property type; that’s why I put in default props.

const defaultProps = {
    propertyType: 1
};
  • Mock store and render form before each test:

First, configure the mock store with the help of the redux-mock-store library.

const mockStore = configureStore();
  • Configure function for execution before each test run using the ‘beforeEach’ method.
let store, PropertySelfOwnedFormComponentWrapper, PropertySelfOwnedFormComponent, receivedNamesList;beforeEach(() => {
    store = mockStore(initialState);
    PropertySelfOwnedFormComponentWrapper = (props) => (
        <PropertySelfOwnedForm {...defaultProps} {...props} store={store} />
    );
    PropertySelfOwnedFormComponent = shallow(<PropertySelfOwnedFormComponentWrapper />).dive();
});

Inside the function, don’t forget to:

  1. Reset the store after every test: store = mockStore(initialState) returns an instance of the configured mock store.
  2. Make Wrapper HOC to pass store, defaultProps and custom props for the special test case .dive() method to receive the rendered form structure one level deeper.

Without the dive() method, ShallowWrapper looks like this:

<PropertySelfOwnedForm
     propertyType={1}
     onSubmit={[Function: mockConstructor]}
     onSubmitAndNavigate={[Function: mockConstructor]}
     onNavigate={[Function: mockConstructor]}
     store={{...}}
/>

Here’s what it looks like with the dive() method: ShallowWrapperWithDiveMethod.js

Writing Tests for Unit Testing

Now, you’re ready to write the test itself. Follow my process to see how you should proceed.

Check the Form Component that’s being rendered:

it('render connected form component', () => {
    expect(PropertySelfOwnedFormComponent.length).toEqual(1);
});

Check the list of fields rendered correctly for property type ‘House’:

it('PropertySelfOwnedForm renders correctly with PropertyTypeHouse', () => {
    receivedNamesList = PropertySelfOwnedFormComponent.find('form').find('Col').children().map(node => node.props().name);
    const expectedNamesList = [
         'building_volume',
         'site_area',
         'building_volume_standard',
         'number_of_garages_house',
         'number_of_garages_separate_building',
         'number_of_parking_spots_covered',
         'number_of_parking_spots_uncovered'
    ];
    expect(receivedNamesList).toEqual(expect.arrayContaining(expectedNamesList));
});

Create a snapshot to check the user interface for property type ‘House’:

it('create snapshot for PropertySelfOwnedForm with PropertyTypeHouse fields', () => {
    expect(PropertySelfOwnedFormComponent).toMatchSnapshot();
});

At this point, you must be asking yourself, “Why do we need two tests for one property type, both snapshot and field existence?” Here’s why: the two tests help us check logic and UI.

  • According to the logic, we should receive an expected list of fields
  • According to the UI, we should obtain a defined order of fields with its own design.

This is what we get from the two tests:

  • No changes in field list / UI -> Two tests passed
  • No changes in field list / Changes in UI -> Snapshot test failed, i.e., the UI changed.
  • Changes in field list / Changes in UI -> Both tests failed, i.e., the logic failed (or both logic and UI), as the field list differs from what was expected.

Having gone through two tests, I see exactly what the problem was and where I should look for reasons for failure. I repeat the process with another property type — ‘Apartment’ and its expected array of fields. I follow the same steps:
Check the list of fields rendered correctly for property type ‘Apartment’:

it('PropertySelfOwnedForm renders correctly with PropertyTypeApartment', () => {
    const props = {
            propertyType: 10
        },
        PropertySelfOwnedFormComponent = shallow(<PropertySelfOwnedFormComponentWrapper {...props} />).dive();
    const receivedNamesList = PropertySelfOwnedFormComponent.find('form').find('Col').children().map(node => node.props().name);
    const expectedNamesList = [
        'number_of_apartments',
        'floor_number',
        'balcony_terrace_place',
        'apartments_number_of_outdoor_parking_spaces',
        'apartments_number_of_garages',
        'apartments_number_of_garages_individual'
    ];
    expect(receivedNamesList).toEqual(expect.arrayContaining(expectedNamesList));

Create a snapshot to check fields for property type ‘Apartment’:

it('create snapshot for PropertySelfOwnedForm with PropertyTypeApartment fields', () => {
    const props = {
            propertyType: 10
        },
        PropertySelfOwnedFormComponent = shallow(<PropertySelfOwnedFormComponentWrapper {...props} />).dive();
    expect(PropertySelfOwnedFormComponent).toMatchSnapshot();
});

The next test is experimental. I decided to investigate a diffing snapshot utility for Jest that one reader of my recent article suggested.

First of all, let’s see how it works. It receives two rendered components with different states or props, and outputs their difference as a string. In the example below, I created a snapshot showing the difference between the forms with different property types — ‘House’ and ‘Apartment’.

it('snapshot difference between 2 React forms state', () => {
    const props = {
            propertyType: 10
        },
        PropertySelfOwnedFormComponentApartment = shallow(<PropertySelfOwnedFormComponentWrapper {...props} />).dive();
    expect(
        snapshotDiff(
            PropertySelfOwnedFormComponent,
            PropertySelfOwnedFormComponentApartment
        )
    ).toMatchSnapshot();
});

This kind of test has its advantages. As you can see above, it covers two snapshots and minimizes code base — thus, instead of two snapshots, you can create just one showing the difference, and similarly, only write one test instead of two. It’s quite easy to use, and lets you cover different states with one test. But, looking at my case, I got a snapshot with 2841 rows, as shown on github. With a large amount of code like this, it’s too hard to see why the test had failed and where.

This only proves one thing: whatever you work with, use your tools and libraries wisely, and only in the places that really require it. This utility may be useful for testing differences in stateless components to find UI inconsistencies, and to define differences in simple functionality components that contain minimum logical conditions. But for testing large pieces of UI, it doesn’t seem to be appropriate.

Before we wrap up the part about unit testing of forms connected with Redux, there’s one more thing. There’s a reason why I didn’t include tests for events. Let’s look at the form structure PropertySelfOwnedForm.js that includes the ButtonsToolbar.js component.

import ButtonsToolbar from 'shared/widgets/buttons/ButtonsToolbar';
<ButtonsToolbar {...restProps} />

This component contains three buttons: ‘Save’, ‘Save and Next’ and ‘Next’, and is used in many forms. Shallow rendering doesn’t include child components, and at this point, I don’t care about their functionality. And rendered ButtonsToolbarcomponent looks like this:

<ButtonsToolbar
      onNavigate={[MockFunction]}
      onSubmit={[MockFunction]}
      onSubmitAndNavigate={[MockFunction]}
      store={
        Object {
          "clearActions": [Function],
          "dispatch": [Function],
          "getActions": [Function],
          "getState": [Function],
          "replaceReducer": [Function],
          "subscribe": [Function],
        }
      }
    />

The truth is, I don’t need to test it as a part of unit form tests. I will test the button events separately in ButtonsToolbar.test.js.

You can find the full tests listing here: PropertySelfOwnedFormUnit.test.js

Integration Testing of Forms Connected with Redux

For integration testing — testing components in a working environment — I use mount rendering. Mount rendering is a type of deep level rendering that includes all the child components by mounting them all into the DOM.

This kind of rendering is actually quite similar to the real DOM tree, as its components’ behavior is interconnected. And the goal of the integration testing is to check this connection. Thus, an actual Redux store is in this case a great choice.

An actual Redux store is one created with the help of a redux library. In this case, there’s no need to mock anything, as you can use the real store the same way as in the app.

Next, I’m configuring my form for testing.

Here’s the list of imports:

  • Rendering method: Enzyme’s mount renderer
  • Methods from Redux for creating a store and combining reducers into a single root reducer
  • Provider from react-redux library to make store available for nested components wrapped in the connect() function
  • Router from react-router-dom to provide React Router navigation
  • Redux-form for better managing the redux state of the form
  • propertyDetailsResource is an object with namespace and endpoint
  • Include json file djangoParamsChoices, containing mocked data passed from the backend
  • Include form view itself
import { mount } from 'enzyme';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { reduxForm, reducer as formReducer } from 'redux-form';import propertyDetailsResource from 'store/propertyDetailsResource';
import djangoParamsChoices from 'tests/__mocks__/djangoParamsChoices';
import PropertySelfOwnedForm from '../PropertySelfOwnedForm';

Then, I prepare data for testing. To do so, it’s important to keep in mind that:

  • There’s a configuration difference between defaultProps for unit and integration tests:
  • With integration tests, a resource with an actual endpoint is added to defaultProps
  • Mocked function handleSubmit is provided by the ‘redux-form’, because Redux-Form decorates the component with the handleSubmit prop
  • Three mocked functions for custom buttons submit events.
  • The store is created the same way as in the app
  • The imported form is decorated with reduxForm
  • The decorated form is wrapped by Router and Provider.

If it makes it easier for you, the sequence of data preparation for integration testing is the same as it is for actions during the form integration with Redux.

global.getDjangoParam = () => djangoParamsChoices;let PropertySelfOwnedFormComponent;
const history = {
        push: jest.fn(),
        location: {
            pathname: '/en/data-collection/property-valuation/'
        },
        listen: () => {}
    },
    defaultProps = {
        propertyType: 1,
        resource: propertyDetailsResource,
        handleSubmit: (fn) => fn,
        onSubmit: jest.fn(),
        onSubmitAndNavigate: jest.fn(),
        onNavigate: jest.fn()
    },
    store = createStore(combineReducers({ form: formReducer })),
    Decorated = reduxForm({
        form: 'property-details-form'
    })(PropertySelfOwnedForm),
    PropertySelfOwnedFormComponentWrapper = (props) => (
        <Provider store={store}>
            <Router history={history}>
                <Decorated {...defaultProps} {...props} />
            </Router>
        </Provider>
    );

Render form before each test:

beforeEach(() => {
    PropertySelfOwnedFormComponent = mount(
        <PropertySelfOwnedFormComponentWrapper />
    );
});
Writing Tests for Integration Testing

Now, let’s do the actual writing. The first step is to create snapshots of both property types. This means that, first, you create a snapshot to check the fields for property type House’:

it('PropertySelfOwnedForm renders correctly with PropertyTypeHouse', () => {
    expect(PropertySelfOwnedFormComponent).toMatchSnapshot();
});

Next, create a snapshot to check fields for property type ‘Apartment’:

it('PropertySelfOwnedForm renders correctly with PropertyTypeApartment', () => {
    const props = {
            propertyType: 10
        },
        PropertyTypeApartmentWrapper = mount(<PropertySelfOwnedFormComponentWrapper {...props} />);
    expect(PropertyTypeApartmentWrapper).toMatchSnapshot();
});

The form buttons are disabled if the form is a pristine or in submitting state. The following test checks if the ‘Save’ button reacts to form changes and becomes active after losing the pristine state:

it('check if `Save` button react to form changes', () => {
    expect(PropertySelfOwnedFormComponent.find('button.button--accent').props().disabled).toEqual(true);
    const streetNumberField = PropertySelfOwnedFormComponent.find('input[name="street_number"]');
    streetNumberField.simulate('change', { target: {value: '10'} });
    expect(PropertySelfOwnedFormComponent.find('button.button--accent').props().disabled).toEqual(false);
});

The last three tests check events that are called by clicking on the onSubmit, onSubmitAndNavigate, or the onNavigate button.

Check if an onSubmit event was called:

it('check event on `Save` button', () => {
    const streetNumberField = PropertySelfOwnedFormComponent.find('input[name="street_number"]');
    streetNumberField.simulate('change', { target: {value: '10'} });const propertySelfOwnedFormButton = PropertySelfOwnedFormComponent.find('button.button--accent');
    propertySelfOwnedFormButton.simulate('click');
    expect(defaultProps.onSubmit).toHaveBeenCalled();
});

Check if an onSubmitAndNavigate event was called:

it('check event on `Save & continue` button', () => {
    const streetNumberField = PropertySelfOwnedFormComponent.find('input[name="street_number"]');
    streetNumberField.simulate('change', { target: {value: '10'} });const propertySelfOwnedFormButton = PropertySelfOwnedFormComponent.find('button.button--secondary').at(0);
    propertySelfOwnedFormButton.simulate('click');
    expect(defaultProps.onSubmitAndNavigate).toHaveBeenCalled();
});

Check if an onNavigate event was called:

it('check event on `Next` button', () => {
    const propertySelfOwnedFormButton = PropertySelfOwnedFormComponent.find('button.button--secondary').at(1);
    propertySelfOwnedFormButton.simulate('click');
    expect(defaultProps.onNavigate).toHaveBeenCalled();
});

Full tests listing: PropertySelfOwnedFormIntegration.test.js

Now the form is fully tested, including the inside components being rendered.

To conclude, I’d like to say that both unit and integration testing are equally important. Each type of testing does its own work and has its own purpose. Dismissing one can lead to a lot of troubleshooting work in the future.

Unit testing mainly covers the user interface, while integration tests dig deeper into functionality. Some people find it redundant to do both, but I’d say both are necessary if you want your product to look good to the user, be user-friendly, and also work smoothly. Unit testing alone will never cover the most important part of the product — the interactions among components. Besides, it’s better to be safe than sorry.

When it comes to testing, forms require special attention, as forms are an essential part of many projects and a way to communicate with customers. This is why it’s vital to be properly prepared and carefully go through all the stages — imports, mocks preparation, store creation, form decoration with Redux, and correct wrapper creation. But the tests themselves are not complicated. In most cases, they follow form logic and reflect field changes and button simulations (in the case of integration testing).

Thank you for your time. We look forward to reading your feedback!

How to Implement Redux in 24 Lines of JavaScript?

How to Implement Redux in 24 Lines of JavaScript?

Redux is among the most important JavaScript libraries ever created. In this posr, you'll learn how to Implement Redux in 24 lines of JavaScript. Inspired by prior art like [Flux](https://facebook.github.io/flux/) and...

Redux is among the most important JavaScript libraries ever created. In this posr, you'll learn how to Implement Redux in 24 lines of JavaScript.

Inspired by prior art like Flux and Elm, Redux put JavaScript functional programming on the map by introducing a scalable architecture of three simple points.

If you're new to Redux, consider reading the official docs first.

Redux Is Mostly Convention

Consider this simple counter application that uses the Redux architecture. If you'd like to jump ahead check out the Github repo for it.

State lives in a single tree

The application's state looks like this.

const initialState = { count: 0 };

Actions declare state changes

By Redux convention, I do not directly modify (mutate) the state.

// DON'T do this in a Redux app
state.count = 1;

Instead I create all the actions the user may leverage in the application.

const actions = {
  increment: { type: 'INCREMENT' },
  decrement: { type: 'DECREMENT' }
};

Reducer interprets action and updates state

The last architectural piece calls for a reducer, a pure function that returns a new copy of your state based on the previous state and action.

  • If increment is fired, increment state.count.
  • If decrement is fired, decrement state.count.
const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

No Redux so far

Did you notice that we haven't touched the Redux library yet? We've just created some objects and a function. This is what I mean by "mostly convention", 90% of Redux doesn't require Redux!

Let's implement Redux

To put this architecture to use, we must plug it into a store. We'll implement just one function–createStore.

It's used like this.

import { createStore } from 'redux'

const store = createStore(countReducer);

store.subscribe(() => {
  console.log(store.getState());
});

store.dispatch(actions.increment);
// logs { count: 1 }

store.dispatch(actions.increment);
// logs { count: 2 }

store.dispatch(actions.decrement);
// logs { count: 1 }

And here's our initial boilerplate. We'll need a list of listeners and the initial state supplied by the reducer.

const createStore = (yourReducer) => {
    const listeners = [];
    let currentState = yourReducer(undefined, {});
}

Whenever someone subscribes to our store, they get added to the listeners array. The is important because every time someone dispatches an action, all the listeners must be notified in a loop.

Calling yourReducer with undefined and an empty object returns the initialState we installed up above. This gives us a proper value to return when we call store.getState(). Speaking of which, let's create that method.

store.getState()

This is a function that returns the latest state from the store. We'll need this to update our UI every time the user clicks a button.

const createStore = (yourReducer) => {
    const listeners = [];
    let currentState = yourReducer(undefined, {});

    return {
        getState: () => currentState
    };
}

store.dispatch(action)

This is a function that takes an action as a parameter. It feeds that action and the currentState to yourReducer to get a new state. Then dispatch notifies everyone subscribed to the store.

const createStore = (yourReducer) => {
  const listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = reducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    }
  };
};

store.subscribe(listener)

This is a function that lets you be notified when the store receives an action It's good to use store.getState() in here to get your latest state and update your UI.

const createStore = (yourReducer) => {
  const listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = reducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe: (newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l === newListener);
      };

      return unsubscribe;
    }
  };
};

subscribe returns a function called unsubscribe that you can call when you're no longer interested in listening to the store's updates.

All Together Now

Let's hook this up to our buttons and view the final source code.

// simplified createStore function
const createStore = (reducer) => {
  const listeners = [];
  let currentState = reducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = reducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe: (newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l === newListener);
      };

      return unsubscribe;
    }
  };
};

// Redux architecture pieces
const initialState = { count: 0 };

const actions = {
  increment: { type: 'INCREMENT' },
  decrement: { type: 'DECREMENT' }
};

const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

const store = createStore(countReducer);

// DOM elements
const incrementButton = document.querySelector('.increment');
const decrementButton = document.querySelector('.decrement');

// Wire click events to actions
incrementButton.addEventListener('click', () => {
  store.dispatch(actions.increment);
});

decrementButton.addEventListener('click', () => {
  store.dispatch(actions.decrement);
});

// Initialize UI display
const counterDisplay = document.querySelector('h1');
counterDisplay.innerHTML = parseInt(initialState.count);

// Update UI when an action fires
store.subscribe(() => {
  const state = store.getState();

  counterDisplay.innerHTML = parseInt(state.count);
});

And once again here's our final UI.

If you're interested in the HTML/CSS I used, here's the GitHub repo again!

Thanks for reading

Dumb decorators for redux & redux-observable & react-redux

Dumb decorators for redux & redux-observable & react-redux

redux-epics-decorator Dumb decorators for redux & redux-observable & react-redux & redux-actions.

redux-epics-decorator

A Dumb wrapper for redux redux-observable react-redux redux-actions injection-js

Features
  • 🚀 Less boilerplate codes
  • 🦄 No magic string Action Types
  • 💚 Type Safe, typecheck in Payload
  • ⛏ Go to definition, go to your Reducer/Epics with one click
  • 🖇 Easy to intergrate into existed redux-observable or other redux middlewares

but which more important is:

Dependeicies
  • yarn add redux redux-observable rxjs redux-actions react-redux
  • yarn add redux-epics-decorator
Full example project

fixtures

Use yarn && yarn start to play with it.

Usage
// module.ts
import { Action } from 'redux-actions'
import { ActionsObservable } from 'redux-observable'
import { Observable } from 'rxjs'
import { exhaustMap, takeUntil } from 'rxjs/operators'

import { generateMsg, Msg } from '../service'
import { EffectModule, Module, Effect, Reducer, ModuleActionProps, DefineAction } from 'redux-epics-decorator'

export interface StateProps {
  currentMsgId: string | null
  allMsgs: Msg[]
}

@Module('your_module_name')
export class Module1 extends EffectModule<StateProps> {
  readonly defaltState: StateProps = {
    currentMsgId: null,
    allMsgs: []
  }

  @DefineAction('dispose') dispose: Observable<void>

  @Effect({
    success: (state: StateProps, { payload }: Action<Msg>) => {
      const { allMsgs } = state
      return { ...state, allMsgs: allMsgs.concat([payload!]) }
    }
  })
  getMsg(action$: Observable<void>) {
    return action$.pipe(
      exhaustMap(() => generateMsg().pipe(
        takeUntil(this.dispose),
        map(this.createAction('success')), // up in Effect Decorator
        // dispatch a normal Redux Action
        // intergrate to your existed redux system
        endWith(this.markAsGlobal({
          type: 'notification',
          payload: {
            type: 'success',
            msg: '✨ Get message success!'
          }
        })),
      ))
    )
  }

  @Reducer('select_msg')
  selectMsg(state: StateProps, { payload }: Action<string>) {
    return { ...state, currentMsgId: payload }
  }
}

export type DispatchProps = ModuleActionProps<Module1>
// container.tsx
import { Module1, StateProps, DispatchProps } from './module'

interface OtherProps {
  price: number
  count: number
}
type Props = StateProps & OtherProps & DispatchProps

const mapStateToProps = (state: GlobalState): StateProps => ({
  ...state.yourcomponent,
  price: otherModule.price,
  count: otherModule.count,
})

class YourComponent extends React.PureComponent<Props> {
  // your codes ...

  render() {
    // this is same to this.props.dispatch({ type: 'Module1/getMsg' })
    this.props.getMsg() // () => Action<void>, type safe here
    return (
      <div />
    )
  }
}

export connect(Module1)(mapStateToProps)(YourComponent)
// store
import { combineModuleEpics, combineModuleReducers, createEpicMiddleware } from 'redux-epics-decorator'

import { StateProps as YourComponentStateProps, Module1 } from './yourcomponent/module'

interface GlobalState {
  yourcomponent: YourComponentStateProps
}

const rootEpic = combineEpics(
  combineModuleEpics(
    Module1,
    Module2,
    Module3,
  ),
  // other normal epics from redux-observable
  epic1,
  epic2,
  epic3,
)

const rootReducer = combineReducers({
  ...combineModuleReducers({
    module1: Module1,
    module2: Module2,
    module3: Module3,
  }),
  // other normal reducers from redux-actions
  other1: otherReducers1,
  other2: otherReducers2,
})

const epicMiddleware = createEpicMiddleware()

export default store = createStore<GlobalState>(rootReducer, compose<any>(
  applyMiddleware(
    epicMiddleware
  )
))

epicMiddleware.run(rootEpic)
Download Details:

Author: Brooooooklyn

Official Website: https://github.com/Brooooooklyn/redux-epics-decorator

Redux Basics

Redux Basics

In this post, the simplest React Redux tutorial I wish I had when I started learning. ... Having seen the basics let's now talk about what problem Redux tries to solve.

Throughout last week, I had my first taste of Redux. During this time, we implemented Redux with React but, it does not need to be used exclusively with React. However, this has been my only experience with it thus far, so I will explain it the way it is used with React.

Upon introduction to Redux, you may be left feeling instantly confused. Initially learning React, most days are spent getting comfortable with the idea of passing props from one component, to another, to another... to another.... to.... another.

While this is an easy concept to understand, it's not necessarily the most efficient. There are a variety of state management systems used within React, but I want to discuss Redux and what has helped me wrap my mind around it!

You may also like: Angular vs React vs Vue: Which one will be popular in 2020.

What is Redux?

Redux has one main advantage, and that's the efficiency it provides. Redux allows you to store your state in what is called a "Redux Store" and uses actions to call reducers, which in turn manipulate your state however you see fit.

Let's keep this simple and straight to the point. Redux is Uber Eats.

I know what you may be thinking... What are you are talking about? Let me explain.

In traditional prop passing, relate each component to a neighbor. If you needed something from the grocery store, imagine that you have to ask neighbor E, to ask neighbor D, to ask neighbor C, to ask neighbor B, to ask neighbor A, if you can use some of their bread. It works... but, it's pretty inconvenient

What if there was a way to just have the bread delivered straight to you?!

AH, this is where Redux shines. With the use of the Redux store, that bread (AKA state), is always available whenever you need it. No passing props, no talking to neighbors, just simply call up the store and get what you need!

The Redux Store

The Redux Store takes about 3.87 seconds to build, and is one of the easiest things to do in React. After installing Redux with your package manager of choice, simply import the function into your main component (usually index.js).

import { createStore } from 'redux'

Boom! Now you have the power, just create a store really quick! Be sure to export your reducer from it's proper file, and import it into your index.js file.

const store = createStore(yourReducerGoesHere)

Simple enough? Now your store exists in a variable called store. It takes in a reducer as well.(This is how it will manipulate the state that's held within the store. Now, let's talk about the Provider.

Providing state to your components

Provider is simple enough to remember, because it provides access the state from the store to your components. I say access, because it doesn't necessarily give your components the state just yet (this is what we have connect() for).

In that same component, you'll want to import Provider.

import { Provider } from 'react-redux' Booyah!

After that, you want to wrap your App component in that provider. Think of this as granting your application the ability to use the store. It typically looks something like this:

ReactDOM.render(
<Provider store={store}>
    <App />
</Provider>
, document.getElementById("root"));

See that sneaky little prop pass, right there? It almost forms a sentence! In the Provider we passed in the store. It can almost be read as, "Providing the store to the component". Well, that's how I read it at least! :)

Awesome, now we created a store, passed the store to the provider, which is providing that store to our application. Before seeing how we grab the state, we need to have state first! On to the reducer!

Reducing The Stress

Reducers! This is one of the powerful aspects of Redux. Essentially, I call them the execution guidelines. The reducer file will typically consist of two things: the initial state, and the reducer itself.

For example, for simplicity sake, let's say our initial state has an array of names.

const initialState = {
   names: ['Bob', 'Susan']
}

Woo! They are looking great. Now the reducer comes into play. This section can get messy, so we'll keep it extremely simple. Reducers are functions full of if...else conditions. The easier way to write this is with switch cases. To prevent confusion, I'll provide an example of both, if...else and a switch case, if you happen to be familiar with both!

Our case that modifies state will be called, 'Add Name'. However, in Redux cases, it's common practice to use all capital letters for this (kind of similar to just screaming at the reducer to do its job), so it would look like 'ADD_NAME'.

If none of the cases do match, you want to be sure to return the initialState. I know this is a lot of words, so let's see an example!

export const reducer = (state = initialState, action) {
    if (action.type === 'ADD_NAME') {
        return {
            ...state,
            names: [...names, action.payload]
        }
    } else {
        return state
    }
}

What's happening here is the reducer takes in state, and an action. State will be undefined if you don't provide it an initial state, so in this example, we assign state to initialState. The action will be an object containing a type and sometimes a payload property. For example, this action object for this example may look like:

{ type: 'ADD_NAME', payload: newNameGoesHere }

The type specifies what reducer case to trigger, like instructions! The payload is just data, it can be called anything. In this case, we have a new name we want to add to the users array. So we spread the whole state object first, then spread the users array into a new array, and add the new name on to the end, this name is being referenced by the action.payload.

So back to my point, reducers are the execution guidelines. They take instruction from the action, and perform based on what action.type is called. This will make more sense in a second when we discuss actions. The payload property is just a common way of passing in the data you want to incorporate into state, it can be called anything - beanChili if you want! :D

Like I said, reducers are typically written in a switch case format, so they may look like this when you come across them:

export const reducer = (state = initialState, action) {
    switch(action.type){
        case 'ADD_NAME':
            return {
                ...state,
                names: [...names, action.payload]
            }
        default:
            return state
    }
}

This achieves the same result, just tends to be less words, the longer your code gets!

Okay, so we've covered the store, the provider, initial state, and the reducer. Now let's take a peek at actions!

Lights, Camera, ACTIONS

As I stated earlier, actions are the instructions for the reducer. Action creators are functions, that return actions. These actions are objects similar to the one I referenced above, with a type and a payload property.

The way these work, is your action creator function is called within your component, which returns an object of "instructions". In this case, you call the action, and it will return an object that looks like:

{ type: 'ADD_NAME', payload: newName }

This function could be represented by:

export const addName = (newName) => {
   return { type: 'ADD_NAME', payload: newName }
}

In this case, when the addName function is invoked, we will pass in the name we want to add, as newName!

Now, this returned object gets passed into the reducer. Can you tell what's going to happen?

The reducer enters the switch case, and checks the action.type. OH! The type is 'ADD_NAME', so hop into that return statement.

Okay, so it is returning state, and then attaching action.payload onto the enter of the array... what is action.payload?

Well, referencing our object above, we see action.payload is the newName. Let's say that we passed in the name 'Chris' as the newName argument. What happens now, is Chris is tacked onto the end of the array. Now our users array in state looks like:

['Bob', 'Susan', 'Chris'] Awesome!

So essentially we just called a function (an action creator), which said, "Hey Reducer... add a new name, the new name is Chris!"

The reducer responds, "Cool, added the name, here's your new state!"

Simple enough, right? They definitely get more complex as more functionality is incorporated into your application, but these are the basics.

However, there is one final question:

How do the components actually access this state?

Simple! By connect! Let's take a look.

Connecting the links

Connecting the store state to our components becomes a bit of extra work, but essentially we have our state, and provide access to the main component (App.js). However, now we need to accept access, via the connect() method.

Connect is a higher-order component, which is a different topic itself, but essentially this gets invoked twice in a row. It is called during the export of your component.

First, let's import connect into our component:

import { connect } from 'react-redux';

Say we have a <List /> component being rendered in App.js, and we want to connect List.js. In that component, on the export line we could do something like:

export default connect(null, {})(List);

The first invocation takes in two items, the state you're receiving, and the actions you want to use (in that order). Let's touch on the state.

Remember, connecting only accepts access, it doesn't actually provide the state, that's what we have mapStateToProps for. :D

mapStateToProps says, "Oh, you connected your component? You granted access? Well here is the state you asked for!"

Okay... Maybe the component doesn't talk, but if they did, they'd probably say something along those lines.

This mapStateToProps example, is a function that receives the state, and is then passed into the connect method. Like this:

const mapStateToProps = state => {
   return {
      names: state.names 
   }
}

This function takes in state, which is the entire state object from the reducer. In this case, our state object only has one array inside of it, but these state objects are typically 10x as long, so we have to specify what information we want!

In this return line, we say, "Return an object with a names property." How do we know what names is? Well, we access it off of the state object, by state.names.

Our returned property doesn't need to be called names, we could do something like:

const mapStateToProps = state => {
   return {
      gummyBears: state.names
   }
}

But, that's not very semantic is it? We want to understand that names is an array of names. So it's common practice to keep the same property name, in your returned state object!

We're almost finished, so hang in there! Let's recap where we're at.

We have our component accessing state from the store, through mapStateToProps. The state exists in the component now, but the component can't access it just yet.

First, we need to pass it to the connect function. The connect functions says, "Access to the store granted! Now... what state am I granting access to?"

So we pass in the function returning state, mapStateToProps, like this:

export default connect(mapStateToProps, {})(List) Radical!

We're almost there!

Now the component is capable of receiving that state as props, like it traditionally would from a parent component. Maybe we are mapping over it, and displaying each name on the screen in a div. Here's what this may look like!

const List = props => {
    return (
        <div>
            {
                props.names.map(name => {
                    return <div>{name}</div>
                })
            }
        </div>
    )
}

Awesome! But there is one final problem... Where does the action get called?

Typically there would be an input, so you could input a new name, and add it to the array - but, for simplicity sake, let's just add a button that adds the name Chris, when clicked! (Not very functional, but you see my point! :D)

We need to access that action creator function. Well, earlier we exported that function so we could import it where we need it, like in our List.js component!

import { addName } from "../actions"

The file location will depend on your directory structure, but it is common to have all actions exported from an index.js file in your actions directory, and then import from that directory. Don't worry too much about that now though!

Great, we have our function, but we can't just pass this function as props to our component just yet. This action is related to Redux, and with Redux we need to connect the action through the connect higher-order component, so when we return our action object, our reducer can accept it and perform accordingly!

Remember that extra space in the connect at the bottom of our List.js component? Let's fill that in with our addName function.

export default connect(mapStateToProps, {addName})(List);

Now, we can pass in our function as props (similar to our state), and use the function as we need!

const List = props => {
    return (
        <div>
            <button onClick={() => props.addName('Chris')}></button>
            {
                props.names.map(name => {
                    return <div>{name}</div>
                })
            }
        </div>
    )
}

I simply created a button, and added an onClick event listener, which triggers the addName function, and passing in 'Chris', like we set out to achieve!

Geez! that was a mission... but we made it! So, let's recap what is happening exactly.

The Redux Recap

We started with creating our store, and passed access to it through the provider, which wrapped our application. Then we created our initial state to use, and formed our reducer which manipulates the state. We built an action creator, addName which is a function that returns instructions for the reducer. These specific instructions said, "We want to add the name Chris to the names array!"

The reducer then takes that information and adds the name to the state. Our component accesses the state through connect, and receives the state through the mapStateToPropsfunction. We also import our action creator, addName, and pass it to connect as well.

The result? We can access our action creator, and our state, as props! However, we aren't passing this information through any other components, just pulling it directly from the store. Delivery straight to your door! Uber eats roc- I mean, Redux rocks!

I understand there is so much more to Redux, and many other things you can change to make everything easier and simpler to use, I just wanted to cover some of the basic foundations of it, and what has helped me understand it a bit better!

I would love to hear your thoughts/opinions on Redux. Thank you for reading !