React and Redux

(Pssst! All the code referred to in this post can be found here)

Well, it's time for a post I've been waiting a while to do. Despite the fact that it's backed by a giant mega-corporation of questionable ethics, React is my current fave JavaScript front-end framework, and the React/Redux combo allows me to write some pretty sweet functional front-end code.

What is React? Well, for me, it's a framework that allows me to think of markup as something a function returns.

Redux (React's partner in crime) is a framework for managing application state all in one place with pure functions. I like to think of redux as allowing me to write functions that take some input, previous application state, and output new application state, which triggers another call to my react code (the thing that actually renders the app).

Ok, let's start with some examples. Here's the simplest one I can think of:

const  
    R = require('ramda'),
    React = require('react'),
    ReactDOM = require('react-dom')

//Simple hello world
ReactDOM.render(  
    <p>Yo yo whattup?</p>,
    document.getElementById('hello'))

This renders to:

I need React and ReactDOM to make this work, and I'm requiring ramda because I just know I'll be using it soon. (Love ramda). I also need the babelify react preset since I'm using babel to compile the <p>Yo yo whattup?</p> bit up there.

<p>Yo yo whattup?</p> is just some markup (it's called JSX in React-land). I'm not using a function to produce it but I will in later examples. Think of JSX as a value, just like objects, numbers, strings and so on are values.

document.getElementById('hello') gets me a div with the id of hello. This is where ReactDOM.render will render the app. Speaking of which...

ReactDOM.render takes your top level component/value/whatever (your JSX) and a reference to the HTML element you want to render it to, and renders it.

Pretty simple so far right?

Here's another example:

//stateless functional components
const  
    SomeComponent = () =>
        <div>This is a stateless functional component</div>,
    AnotherComponent = ({argument}) =>
        <div>
            The argument of this component is {argument}
        </div>

ReactDOM.render(  
    <div>
        <SomeComponent/>
        <AnotherComponent argument='bananas'/>
    </div>,
    document.getElementById('simple'))

And here's what that gets us

Here we've got two components, which are functions. In react you can use classes to make components but, I really, really, don't want to do that. (Object-oriented boo!!) Functions are the best!

In any event, in this call to ReactDOM.render I wrap everything with a top-level component, since it will only take one element as its first argument, this top-level element is usually a div by common practice.

SomeComponent just returns a div with some hard-coded text, but AnotherComponent takes an object as it's argument and expects that object to have an argument property.

We create an instance of AnotherComponent by writing <AnotherComponent argument='bananas'/>. This calls the AnotherComponent function with an object including the key-value pair argument and bananas.

Cool right? Our function-component (or what React calls a "stateless functional component") gets the properties it needs by us adding property-value pairs in the JSX.

Ok, check this out:

// map example
const  
    Row = ({text}) => <tr><td>{text}</td></tr>,
    Table = ({list}) =>
        <table>
            <tbody>
                {list.map((text, i) =>
                    <Row key={i} text={text}/>)}
            </tbody>
        </table>

ReactDOM.render(  
    <Table list={['stuff', 'and', 'things']}/>,
    document.getElementById('map'))

This gives the following markup:

<table>  
    <tbody>
        <tr>
            <td>stuff</td>
        </tr>
        <tr>
            <td>and</td>
        </tr>
        <tr>
            <td>things</td>
        </tr>
    </tbody>
</table>  

And here's what it looks like:

In React, we're able to insert any old JavaScript we want into our JSX by wrapping it in curly braces. I can use all my favorites, map, filter, reduce, whatever.

I know there are some purists out there who hate this, but I really like mixing my markup and JavaScript together like this! It's really convenient.

(By the way, if I don't add a unique key to lists like this, React complains. So that's the only reason that's there. No big deal.)

Speaking of mixing it up, you can also add style to JSX by using plain old JavaScript objects:

// style example
const  
    Row = ({text, style={}}) =>
        <tr><td style={style}>{text}</td></tr>,
    Table = ({list, style={}}) =>
        <table style={style}>
            <tbody>
                {list.map((text, i) =>
                    <Row
                        style={style}
                        key={i}
                        text={text}/>)}
            </tbody>
        </table>

const tableStyle = {border: '1px solid black'}  
ReactDOM.render(  
    <Table
        style={tableStyle}
        list={'each word will be a row'.split(' ')}/>,
    document.getElementById('stylemap'))

This is just the same code as the first map example, but I've added a style property, which is just a plain old JavaScript object containing styling information.

Looks just like CSS right? Only now that's it's an object, we can use JavaScript to create these objects, giving us more flexiblity.

Here's what it renders to:

Ok, now it's time to introduce Redux.

Check it ooooooout:

const  
    {createStore} = require('redux'),
    reducer = (state = '', action) => {
        switch(action.type) {
            case 'ADD_TEXT':
                return action.text
            default:
                return state
        }
    },
    store = createStore(reducer),
    render = () =>
        ReactDOM.render(
            <div>
                <p>{store.getState()}</p>
                <input
                    type="text"
                    onChange={
                        ({target:{value: text}}) => 
                            store.dispatch({
                                type: 'ADD_TEXT',
                                text
                            })}/>
            </div>,
            document.getElementById('textfield'))

store.subscribe(render)  
render()  

The result of all this is an app that takes whatever you type in a text-field and displays it in a paragraph element:

I know this code might look like a lot but it's really pretty simple.

store is where all application state lives. It can be any JavaScript value you want. This example is a string, but it could be a number, an array, or an object, whatever.

store.getState() gets us a copy of application state so we can use it to render our app.

When we create a store we give it a pure function called a reducer to manage state transformation. A reducer takes any action our app sends the store, the current app state, and returns new app state.

store.subscribe(render) tells store what to do when state changes (after calling a the reducer). Usually that means re-rendering the app.

Re-rendering the entire app every time state changes is no big deal in React because it optimizes things under the hood. In reality, React will only re-render what's actually changed and leave everything else alone.

Sending an action from any part of our app to Redux is easy. Just call store.dispatch with an object that at least has a type property. You can give it other stuff for the reducer to use, but the type property is mandatory.

So far so good? The store keeps all application state, actions are sent to the store to modify state, which is done by a pure reducer function. Any time state changes, the app re-renders.

The reducer function is usually a switch statement that switches on the type of whatever action is coming in. Then whatever case handles that action takes other information from the action object, current app state, and smushes them together to make and return new app state.

Our reducer in this example only has one case, ADD_TEXT, but I hope you can see that a reducer can have any number of cases.

Just like in my post about RxJs, I made a bigger, more fully featured, app example. It's a simpler implementation of a Trello-style or kanban app.

Here's what it looks like:
My Kanban app

We've got three columns, 'to do', 'doing', 'done'. The plus buttons at the top add items to that column. Each item is a title and a description that you can edit at any time. Each item has a 'remove' button that removes the item and arrow buttons that can move them up and down their own column or to other columns.

Here's what initial state looks like:

    kanbanInitialState = {
        nextKey: 1,
        columns: [
            {
                key: 1,
                heading: 'to do',
                items: []
            },
            {
                key: 2,
                heading: 'doing',
                items: []
            },
            {
                key: 3,
                heading: 'done',
                items: []
            }
        ]
    },

Each item has its own unique key, and we've got a field that keeps track of the next unique key in the sequence. The columns are hard-coded, but since it's just an array, it'd be easy enough to make that part of the app dynamic too.

The reducer is pretty large but let me show you some choice bits:

        switch(action.type) {
            case 'CHANGE_ITEM_TITLE':
                return changeItem(
                    {title: action.title},
                    action.id,
                    state)
            case 'CHANGE_ITEM_DESCRIPTION':
                return changeItem(
                    {description: action.description}, 
                    action.id,
                    state)
            case 'ADD_ITEM':
                return {
                    ...state,
                    nextKey: state.nextKey + 1,
                    columns: state.columns.map(col =>
                        col.key === action.id
                            ? ({
                                ...col,
                                items: [{
                                    key: state.nextKey,
                                    title: '',
                                    description: ''
                                }]
                                    .concat(col.items)
                            })
                            : col)
                }

This is a bit of the reducer. The first two cases cover changing the title or description of an item using the id/key of that item. ADD_ITEM handles adding an item to a given column based on the id/key of the column.

A lot of the transformations look like the code for ADD_ITEM. I use map to apply a transformation function to all columns, but use an id check to leave all but one column, the column to contain a new or modified item, unchanged.

For example, the code for moving an item right or left first maps over app state to remove an item, then maps again to add it to the new column.

...
const  
    colIndex = getItemIndex(action.id, state.columns),
    item = getItem(colIndex, action.id, state.columns)
...
R.pipe(  
    R.map(
        R.over(
            R.lensProp('items'),
            R.reject(R.propEq('key', action.id)))),
    R.adjust(
        R.over(
            R.lensProp('items'),
            R.append(item)),
        colIndex - 1))
            (state.columns)

Moving an item up and down consist of using a reduce call to re-arrange the order of items

col.items.reduce(  
    (items, item) =>
        item.key === action.id
            ? R.dropLast(1, items)
                .concat(item)
                .concat(R.last(items))
                .filter(x => x) //sometimes last is undefined
            : items.concat([item]),

If you look at the code you'll see that all application logic lives in the reducer. You'll also see that the reducer is a pure function, which is much easier to understand as you scale.

And here's the code to actually render the app:

....
Column = ({id, heading, items}) =>  
    <div style={colStyle}>
        <h3 style={headerStyle}>{heading}</h3>
        <button
            type="button"
            onClick={() => kanbanStore.dispatch({
                type: 'ADD_ITEM',
                id
            })}
            style={addButtonStyle}>
            +
        </button>
        {items.map(({key, title, description}) =>
            <Item
                key={key}
                id={key}
                title={title}
                description={description}/>)}
    </div>,
....
ReactDOM.render(  
    <div style={kanbanStyle}>
        {kanbanStore.getState().columns.map(
            column =>
                <Column
                    key={column.key}
                    id={column.key}
                    heading={column.heading}
                    items={column.items}/>)}
    </div>,
    document.getElementById('kanban'))

I won't include Item since I hope you get the idea.

There's a ton more to learn, but these are the broad strokes. If you'd like to learn more, check out egghead.io's excellent tutorials on React and Redux. (Their Redux tutorial is especially good). There are also the React and Redux Docs to check out.

Looking for a software developer?