Understanding React and Redux with tests - 02

Exploring the react-redux package.

Organizing reducers

In my previous post I’ve learned, with tests, some of Redux ropes and now I would like to move that knowledge forward, again with tests, in regards to how I can use Redux in my Local addon.
I’m still working on the same one, big test file and what I want to do now is try and migrate the reducers from their current state of functions defined in the global scope to an object.
Given the current state of the reducers:

const status = function ( state = 'inactive', action ) {
    switch ( action.type ) {
        case TOGGLE_STATUS:
            return state === 'inactive' ? 'active' : 'inactive'
        default:
            return state
    }
}

const buttonText = function ( state = 'Deactivate', action ) {
    switch ( action.type ) {
        case TOGGLE_STATUS:
            return state === 'Deactivate' ? 'Activate' : 'Deactivate'
        default:
            return state
    }
}

function reducer( state = {}, action ) {
    return {
        status: status( state.status, action ),
        buttonText: buttonText( state.buttonText, action ),
    }
}

I rewrite the code to this (I’ve added the toggleStatus function at the top to show the constant reference change):

const toggleStatus = function () {
    return {
        type: Reducers.TOGGLE_STATUS,
    }
}

class Reducers {
    static TOGGLE_STATUS = 'TOGGLE_STATUS'

    static status( state = 'inactive', action ) {
        switch ( action.type ) {
            case Reducers.TOGGLE_STATUS:
                return state === 'inactive' ? 'active' : 'inactive'
            default:
                return state
        }
    }

    static buttonText( state = 'Deactivate', action ) {
        switch ( action.type ) {
            case Reducers.TOGGLE_STATUS:
                return state === 'Deactivate' ? 'Activate' : 'Deactivate'
            default:
                return state
        }
    }
}

function reducer( state = {}, action ) {
    return {
        status: Reducers.status( state.status, action ),
        buttonText: Reducers.buttonText( state.buttonText, action ),
    }
}

Without changing any other line of code I run the tests again and see that Redux is fine with my change:

To note here is that all the Reducers class methods are declared static; this makes sense as, by definition, reducer functions will get the whole context to process, in the form of the state and the action, as an input.
Having a class, Reducers in this case, define the methods that will be used as reducers could look like merely using a class as a namespace but there is potential value to be had in writing stateful reducers.
I write a little test for this last consideration.

Stateful reducers

While worrying, and developing, stateful reducers are probably out of scope for the addon code the idea is intriguing enough to be worth exploration.
Say that I’m writing a client application for an API with a request per minute limit; while I could write the client to simply issue a request whenever it needs to, and deal with the potential 429 response, I might want to avoid the warnings and alarms going off whenever that happens.
Many API providers will punish abuse, or too many requests, with timeouts and throttlings and it makes sense to use that information, if available, to avoid that on the client.

class Server {
    static REQUEST_SENT = 'REQUEST_SENT'
    static REQUESTS_ENABLED = 'REQUESTS_ENABLED'

    constructor( requestMinInterval = 5000 ) {
        this.requestMinInterval = requestMinInterval
        this.lastRequestAt = 0
    }

    setStore( store ) {
        this.store = store
    }

    blocked( state, action ) {
        switch ( action.type ) {
            case Server.REQUEST_SENT:
                this.lastRequestAt = action.requestedAt
                return this.lastRequestAt + this.requestMinInterval > Date.now()
            case Server.REQUESTS_ENABLED:
                return false
            default:
                return this.lastRequestAt + this.requestMinInterval > Date.now()
        }
    }

    request() {
        // ... make the request ...

        // after a time re-enable requests
        this.store.dispatch( {type: Server.REQUEST_SENT, requestedAt: Date.now()} )

        setTimeout( function () {
            this.store.dispatch( {type: Server.REQUESTS_ENABLED} )
        }.bind(this), this.requestMinInterval )
    }
}

const ClientComponent = function ( props ) {
    return (
        <button disabled={props.blocked} onClick={props.makeRequest}>Request something</button>
    )
}

class ClientWrapper extends React.Component {
    constructor( props ) {
        super( props )
        this.store = props.store
        this.store.subscribe( this.handleStatusChange.bind( this ) )

        this.server = props.server

        this.state = {
            blocked: this.store.getState().blocked,
        }
    }

    handleStatusChange() {
        this.setState( {
            blocked: this.store.getState().blocked,
        } )
    }

    render() {
        return (
            <div>
                <ClientComponent blocked={this.state.blocked} makeRequest={this.server.request.bind( this.server )}/>
            </div>
        )
    }
}

describe( 'Stateful reducers', function () {
    it( 'should correctly block requests', function () {
        const server = new Server( 500 )
        const initialState = {blocked: false}
        const store = createStore( function ( state, action ) {
            return {
                blocked: server.blocked( state, action ),
            }
        }, initialState )
        server.setStore( store )

        const wrapper = mount( <ClientWrapper server={server} store={store}/> )

        expect( wrapper.find( 'button' ).prop( 'disabled' ) ).to.be.false

        wrapper.find( 'button' ).simulate( 'click' )

        wrapper.update()

        expect( wrapper.find( 'button' ).prop( 'disabled' ) ).to.be.true

        // simulate the server timeout update
        store.dispatch( {type: Server.REQUESTS_ENABLED} )

        wrapper.update()

        expect( wrapper.find( 'button' ).prop( 'disabled' ) ).to.be.false
    } )
} )

The test passes showing that stateful reducers, as in having reduction operations handled by a stateful object that will use its state to produce the new store state, are indeed possible.

This seems like a detour but it shows an interesting way to test components that might update after “something”, a Redux store state update precisely, happens.
In the Server::request method a timeout is set to re-enable requests after a time:

request() {
    // ... make the request ...

    // after a time re-enable requests
    this.store.dispatch( {type: Server.REQUEST_SENT, requestedAt: Date.now()} )

    setTimeout( function () {
        this.store.dispatch( {type: Server.REQUESTS_ENABLED} )
    }.bind(this), this.requestMinInterval )
}

In the tests I cannot wait for the timeout to run so I simply dispatch the same action on the store object thus triggering a state change on the store and then verify the component rendered correctly:

// simulate the server timeout update
store.dispatch( {type: Server.REQUESTS_ENABLED} )

If I really had to test this client/server (idiotic) application I would test the timeout behavior in a test dedicated to the Server class.
While the whole “stateful reducers” idea works the question is: does it make sense?
In this example I could drop the need for a state by simply storing the information about the last request in the store state itself.
While I wanted to explore the possibility it’s really difficult for me to think about cases where “stateful reducers " could provide value the store would not provide by… keeping track of a state.

Putting the pieces together

So far I’ve “manually wired” the store into the components by writing code like this:

const wrapper = mount( <ClientWrapper server={server} store={store}/> )

And passed that store down to each component that either needs it itself or uses a component that needs it.
I’ve solved only partially the problem I’ve set out to solve by having something, the store object, that will work like an event bus; the remaining problem is how to avoid having to pass the reference to that event bus to each component.
Redux offers a convenient way to avoid that and it’s time to test it.
I’m defining the following hierarchy of React components for my first test:

<SudoApplication>
    <LabelAndButton>
        <Label/>
        <Button/>
    </LabelAndButton>
</SudoApplication>

The component tree is deeper than what I’ve used before to underline how inconvenient it would be to have to pass that store object up and down.
The idea behind this proto application is that the label will display the current value and the button will toggle it; the toggle should trigger a change in the label text too. First of all I define the React components I will need:

const Label = function ( {text} ) {
    return (
        <p className='label'>{text}</p>
    )
}

const Button = function ( {onButtonClick} ) {
    return (
        <button onClick={onButtonClick} className='button'>Toggle</button>
    )
}

const LabelAndButton = function ( props ) {
    return (
        <div>
            <Label text={props.labelText}/>
            <Button onButtonClick={props.onButtonClick}/>
        </div>
    )
}

The LabelAndButton component represents, and handles, the logical relation between the Label and the Button component; borrowing a term from this article about different React components the LabelAndButton component is a container component.
As a container component its task will be to use the store to set up the Label and the Button components passing the correct properties to them on each re-render.
This container component needs to update and inject, on each re-render, two properties:

  • the labelText property into the Label component
  • the onButtonClick property into the Button component

Here the first step of the Redux and React connection kicks in: before I would have written the LabelAndButton component to require the injection of a store object and use it to set the two properties, like this:

class LabelAndButton extends React.Component{
    constructor(props){
        super(props)
        this.store = props.store
        this.store.subscribe(this.handleStateChange.bind(this))
    }

    handleStateChange(){
        storeState = this.store.getState()
        if(storeState.current === this.state.current){
            return
        }

        this.setState({
            current: storeState.current
            labelText: storeState.label
        })
    }

    render(){
        const storeState = this.store.getState()

        return (
            <div>
                <Label text={this.state.labelText}/>
                <Button onButtonClick={this.state.onButtonClick}/>
            </div>
        )
    }
}

This is code I’ve already shown and, by now, pretty standard stuff.
The reason I am, instead, writing the LabelAndButton component like I did, as a functional component in place of a class, is because I will leverage the benefits offered by the React and Redux connector.
The first thing I need to do is to require the connector as a project dependency:

npm install --save react-redux

and then write the connecting logic.

From Redux store state to React properties

The part that took longer to understand is that the Redux and React connection consists in translating a Redux store state in an object specifying React properties passed to a component.
In my case I need to go from the Redux store state to properties needed to initialize the LabelAndButton component; I’ve listed them before as labelText and onButtonClick.
But what is going to pass those properties to the LabelAndButton component? A wrapper.
Do I have to write that wrapper? Yes and no.
Yes: I will have to write “something” to create the wrapper; no: I will not need to write a React component or a JavaScript class; what I will write is the code below:

const connect = require( 'react-redux' ).connect

const InteractiveLabelAndButton = connect( mapStateToProps, mapDispatchToProps )( LabelAndButton )

That InteractiveLabelAndButton variable will contain a React component, the wrapper, the Redux and React package took care to create in an efficient and optimized way; that “some kind of wrapping” is happening is clear from the fact that the function created by

connect( mapStateToProps, mapDispatchToProps )

is then “fed” the LabelAndButton component:

connect( mapStateToProps, mapDispatchToProps )( LabelAndButton )

But what are the two mapStateToProps and mapDispatchToProps variables?
Before I said:

The part that took me longer to understand is that the Redux and React connection consists in translating a Redux store state in an object specifying React properties passed to a component.

“Mapping”, in developer terms, means knowing how to translate a source object into a destination one.
Those two variables do exactly that: translate the store state into properties for the wrapped object filling the properties that will then be passed to the LabelAndButton::render method:

const mapStateToProps = function ( state ) {
    return {
        labelText: state.label,
    }
}

const mapDispatchToProps = function ( dispatch ) {
    return {
        onButtonClick: function () {
            dispatch( {type: TOGGLE} )
        },
    }
}

The first function, mapStateToProps, says that props.labelText should be assigned the same value as store.label.
The second function, mapDispatchToProps, says that props.onButtonClick should be a function that dispatches an action of the TOGGLE type.
Here the difference ends, the way the store object is built and its state reduced did not change from previous examples:

const TOGGLE = 'TOGGLE'

const getLabel = function ( state, action ) {
    if ( action.type === TOGGLE ) {
        return state.current === 'foo' ? 'Bar' : 'Foo'
    }
    return state.label
}

const getCurrent = function ( state, action ) {
    if ( action.type === TOGGLE ) {
        return state.current === 'foo' ? 'bar' : 'foo'
    }
    return state.current
}

const labelAndButtonApplicationReducers = function ( state, action ) {
    return {
        label: getLabel( state, action ),
        current: getCurrent( state, action ),
    }
}

const connect = require( 'react-redux' ).connect

const InteractiveLabelAndButton = connect( mapStateToProps, mapDispatchToProps )( LabelAndButton )

It’s time, now, to define the application component:

const SudoApplication = function () {
    return (
        <div>
            <InteractiveLabelAndButton/>
        </div>
    )
}

There is nothing fancy here: the application will merely render the InteractiveLabelAndButton component, the one created connecting Redux to the LabelAndButton component.
What is not apparent from the code is where and how the store object is passed, or injected, into the InteractiveLabelAndButton component; this is the second piece of how Redux and React connect and will use a component defined, again, in the react-redux package:

const Provider = require( 'react-redux' ).Provider

const applicationStore = createStore( labelAndButtonApplicationReducers, {label: 'Foo', current: 'foo'} )

const ProviderApplication = function () {
    return (
        <Provider store={applicationStore}>
            <SudoApplication/>
        </Provider>
    )
}

The Provider class will take care to inject the store in any component by means of the React context.
All the pieces are now in place and it’s now time to write a couple of tests to make sure the wiring is correct.

Testing the Redux and React connection

I’m still adding all of the code in the same file for sake of simplicity.
Mocha does allow multiple define entries to be defined in the same file and I can use Mocha grep flag to run only these two tests:

describe( 'Redux connect', function () {
    it( 'should render the application correctly', function () {
        const wrapper = mount( <ProviderApplication/> )

        expect( wrapper.find( '.label' ).text() ).to.be.equal( 'Foo' )
    } )

    it('should update the label when the button is clicked', function(){
        const wrapper = mount( <ProviderApplication/> )

        wrapper.find('.button').simulate('click')

        expect( wrapper.find( '.label' ).text() ).to.be.equal( 'Bar' )

        wrapper.find('.button').simulate('click')

        expect( wrapper.find( '.label' ).text() ).to.be.equal( 'Foo' )
    })
} )

I run the tests with:

mocha --grep "Redux connect"

And see them pass, confirming my wiring of the application is correct:

Next

I feel like I have now all the connecting pieces needed to rewrite the Local addon to a more acceptable standard and that will be my next step.
If, in the process of doing that, I have more interesting and not obvious (well, not for me) epiphanies I will write a post to document that; if not then I will move to the new features.