Understanding React and Redux with tests - 02
October 21, 2017
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:
[](https://theaveragedev.com/wp-content/uploads/2017/10/refactoring-reducers-01.png)
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.
[](https://theaveragedev.com/wp-content/uploads/2017/10/stateful-reducers.png)
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 theLabel
component - the
onButtonClick
property into theButton
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 connect
ing 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:
[](https://theaveragedev.com/wp-content/uploads/2017/10/react-redux-tests.png)
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.