React - Putting it all together

Presented by

Guyllaume Cardinal

and

Guyllaume Cardinal

Partner at Majisti, brand creator and Frontend expert

Majisti 3 years, Web dev 7 years

React for more than a year

Elaborated all of Majisti's frontend stack

  • Quality, scalability, service, maintanability
  • Custom Web application and platforms development
  • Consulting and team coaching

We also blog (sometimes)!

http://booborguru.com/

contact@majisti.com

514-316-9092

Today's schedule

  1. Disclaimers (nothing too bad, don't worry!)
  2. Libraries used
  3. An explanation of how we wire it all together
  4. Conclusion

Before We Begin

A few disclaimers

  • My examples use TypeScript
  • This talk assumes you already know React
  • This is just the setup I'm using right now, nothing more
  • I don't have the absolute truth (though I wish I did!)

Before We Begin

Missing stuff from this talk

  • Testing won't be covered
  • I won't explain Webpack (there's already a ton of really good resources)
  • Continuous Integration/Continuous Delivery
  • Choice of backend
  • Docker setup

One more thing

The usual React warnings

It's kind of a React tradition at this point.

  • It's a view layer, nothing more
  • It does not care where data comes from, how it's sent or transformed within the app
  • It could be swapped out and replaced with any other view layer out there
  • You will need to figure out your "framework"

Everything in this talk is the result of:

  • Our needs as a business
  • What our clients expects out of our products
  • Bias towards our own opinions and the way we work

One more More thing

It is but Majisti's Framework

It should still give you a good idea of where to start when working with React!

Oh, last thing, I promise!

Sample "project"

Now for the fun part

What do we use?

  • React (obviously)
  • Redux
  • React Router (and react-router-redux)
  • Reselect
  • InversifyJS
  • react-css-modules

Real World App

Rocket LEague Custom Training

Filter training codes for the Rocket League game

by Guyllaume Cardinal and Steven Rosato

Real World App

b2bup

Connecting businesses together at events.

by Majisti

A view layer. It helps you build user interfaces in a reusable way

 

I highly recommend Rami Sayar's talk: https://vimeo.com/189677855

REact

Why we love it

  • Pretty small size
  • Thriving community
  • Backed by Facebook
  • Tiny API footprint: Coding for React is simple
  • Tooling is amazing (hot reloading, so sexy <3)
  • Never having to code a date picker again? Count me in!

REact

Alternatives and competition

  • Meteor's Blaze templating
  • Angular's directives
  • vue.js
  • Polymer
  • Mithril
  • Preact

handle application state and how it changes with user interaction

Redux

The store

Central point of your app.

  • Any and all state change goes through the store via store.dispatch()
  • The current state can be fetched with store.getState()
import {createStore} from 'redux'
import RootReducer from 'RootReducer'

const initialState = {}
const store = createStore(RootReducer, initialState)

Redux

The State

POJO (Plain old JavaScript object). Describes your application's current state.

{
    user: {
        firstName: "Guyllaume",
        lastName: "Cardinal",
    },
    noteItems: {
        "92ns812m04": {
            content: "Do HTML5mtl talk",
            done: false,
            category: "91n236bws6",
        }
    }
    noteCategories: {
        "91n236bws6": {
            name: "Reminders",
            color: "f7f2d5",
        }
    }
}

Redux

The State

You should aim to keep it as flat as possible

  • Updating a deep nested object will make it difficult to keep the state's immutability
  • Updating a low level value will mean a deeply nested property will be considered as changed, causing useless renders
  • Accessing data will get ugly after a while
  • Refactoring will get even uglier

Redux

Connected React Components

Using redux's connect function, we can access the state and inject props into our React components

export class UserSummary extends React.Component {
    public render() {
        return (
            <div>
                <p>{this.props.firstName}</p>
                <p>{this.props.lastName}</p>
            </div>
        )
    }
}

export function mapStateToProps(state) {
    return {
        firstName: state.user.firstName,
        lastName: state.user.lastName,
    }
}

export default connect(mapStateToProps)(UserSummary)

Redux

Actions

  • POJO
  • Describes what happened, not how
  • They have no required format, the only thing that Redux requires is that they have a type property.
{
    type: "USER_LOGGED_OUT",
}

Redux

Actions

There is a standard that emerged: flux standard actions

{
    type: "ADD_TODO",
    payload: {
        content: 'Do something',
        completed: false,
        title: null,
    },
    error: false,
    meta: {
        text: "Intended for any extra information that is not part of the payload"
    }
}

Redux

Action creators

They create and return the action, maybe doing some work beforehand

export default class UserActionCreator {
    public requestNewUserProfileSuccess(user: UserResponse) {
        return {
            payload: {
                firstName: user.profile.firstName,
                id: user.id,
                lastName: user.profile.lastName,
            },
            type: "REQUEST_NEW_USER_PROFILE_SUCCESS",
        }
    }
}

Redux

Reducers

Pure functions. They wait for actions and return a new state, with the action's payload merged

export default function UserReducer(state = null, action) {
    switch (action.type) {
        case "REQUEST_NEW_USER_PROFILE_SUCCESS":
            state[action.payload.id] = action.payload
            return Object.assign({}, state)

        default:
            return state
    }
}
  • Reducers must always return something, never undefined
  • They must be pure function

Redux

Reducers

import {combineReducers} from 'redux'

export function idReducer(state = null, action) {
    switch (action.type) {
        /** Decide if we reducer or not **/
    }
}

export function profileReducer(state = null, action) {
    switch (action.type) {
        /** Decide if we reducer or not **/
    }
}

export default combineReducers({
    id: userIdReducer,
    profile: profileReducer,
})
{
    id: '123',
    profile: {
        firstName: 'Guyllaume',
        lastName: 'Cardinal',
    },
}

Redux

Reducers

// RootReducer.ts
import UserReducer from '...'
import NoteItemsReducer from '...'
import NoteCategoriesReducer from '...'

export default combineReducers({
    user: UserReducer,
    noteItems: NoteItemsReducer,
    noteCategories: NoteCategoriesReducer,
})
// UserReducer.ts
export function idReducer(state, action) {/* Some code */}
export function profileReducer(state, action) {/* Some code */}

export default combineReducers({
    id: idReducer,
    profile: profileReducer,
})
// Resulting state
{
    user: {
        id: '123',
        profile: {
            firstName: 'foo',
            lastName: 'bar',
        }
    },
    noteItems: {},
    noteCategories: {},
}

Composed to create a root reducer

Redux

Binding it all together

Now we have:

  • A store, built with a root reducer and an initial state
  • Action creators that can return new actions
  • Connected React components that fetch the current application's state using connect()
  • Reducers that will react to certain action types and return a new state accordingly

Redux

Binding it all together

Introducing dispatch(). That's how you send your actions to the store so reducers can act on them.

const initialState = {}
const store = createStore(RootReducer, initialState)
store.dispatch({type: 'ADD_TODO'})
store.dispatch(UserActionCreator.requestNewUserProfile())

Redux

Binding it all together

export class UserSummary extends React.Component {
    public render() {
        return (
            <div>
                <p>Hello HTML5mtl</p>
                <a onClick={this.props.onLogoutClick}>Logout</a>
            </div>
        )
    }
}

export function mapDispatchToProps(dispatch) {
    return {
        onLogoutClick: dispatch(UserActionCreator.userLogout()),
    }
}

export default connect(null, mapDispatchToProps)(UserSummary)

Redux

What have we achieved?

  • Components are decoupled from the state
  • Interactions with Redux is also decoupled
  • State is a POJO that is never mutated, allowing for awesome tools

Redux

Tooling - Redux dev tools

  • Inspect state and actions being dispatched
  • Go back in time by "canceling" actions
  • Hot reloaded code gets re-evaluated with the current actions
  • Much, much, more

Redux

Tooling - Redux logger

Redux

Tooling - redux-thunk

  • It's a middleware
  • Allows an action creator to return a function
  • Will be passed the store's dispatch
function fetchUserProfile(userId: string): Function {
    return dispatch => {
        fetch(`${api}/userProfile/${userId}`)
            .then(profile => {
                dispatch({type: 'FETCH_SUCCESS', payload: profile})
            })
    }
}

function saveAndClose(): Function {
    return dispatch => {
        dispatch(this.save())
        dispatch(this.close())
        dispatch(NavigationActionCreator.returnHome())
    }
}

Redux

A bit of sidetracking - Middlewares

  • Similar to Express or Koa middlewares (and a lot of other libraries)
  • Extension to the store, acts between the dispatching of an action and that action reaching the reducers
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import RootReducerfrom 'RootReducer'

const initialState = {}
const store = createStore(
    RootReducer,
    initialState,
    applyMiddleware(thunk),
)

Redux

Taking it all in

  • Redux becomes the core of the application. A lot goes through its workflow
  • Thinking in Redux might take a while
  • I suggest doing (not just reading) the examples on Redux's documentation. Really helped it click for me

Redux

Alternatives

  • MobX
  • Jumpsuit (kind of a Redux superset)
  • Meteor (using Meteor, but preferring React rather than Blaze)
  • Angular (again, using React as the view)

Take a deep breath

CSS Modules, but with some added sugar for ease of use with React

CSS Modules?

  • It's the idea of bundling CSS with your code and scoping the resulting CSS to the file that imports it
  • Allows inheritance of styles and importing of partial styles
  • Requires the use of a module bundler like Webpack (and a few loaders)

CSS Modules?

import React from 'react'
import styles from './table.css'

export default class Table extends React.Component {
    render () {
        return <div className={styles.table}>
            <div className={styles.row}>
                <div className={styles.cell}>A0</div>
                <div className={styles.cell}>B0</div>
            </div>
        </div>
    }
}
// Resulting HTML
<div class="table__table___32osj">
    <div class="table__row___2w27N">
        <div class="table__cell___1oVw5">A0</div>
        <div class="table__cell___1oVw5">B0</div>
    </div>
</div>

react-css-modules

react-css-modules

You're using CSS?

  • We already know CSS
  • As portable as a component built with Radium or the likes
  • No extra markup; in your code and the resulting HTML
  • Results in a single stylesheet, helps when loading the page

SASS actually, but yes. Here's why:

REact-css-modules

  • You have to use camelCase CSS class names
  • You have to use styles.className to define the className prop
  • Reference to an undefined CSS Module resolves to undefined without a warning

CSS Modules aren't good enough

import React from 'react'
import CSSModules from 'react-css-modules'
import styles from './table.scss'

@CSSModules(styles)
export default class Table extends React.Component {
    render () {
        return <div styleName='table'>
            <div styleName='row'>
                <div styleName='cell'>A0</div>
                <div styleName='cell'>B0</div>
            </div>
        </div>
    }
}

REact-css-modules

/** table.scss **/

.table {
    background: blue;
}

.row {
    border: purple dotted 6px;
}

.cell {
    background: pink;
}
import Table from 'Table'
import styles from './extendedTable.scss'

<Table styles={styles} />

REact-css-modules

Extending Styles

What if we want to override that previous table's style?

import * as React from 'react'
import * as classNames from 'classnames'
import * as CSSModules from 'react-css-modules'
import * as styles from './styles.scss'

@CSSModules(styles)
export default class SubmitButton extends React.Component {
    public render() {
        const buttonStyles = classNames({
            button: true,
            error: this.props.hasError,
        })

        return <button styleName={buttonStyles}>Submit</button>
    }
} 

REact-css-modules

Conditional Styles

Enter classNames. Small utility to determine styles based on any condition you may have

@import "palette";

.button {
    color: $primaryColor;

    & .error {
        color: $errorColor;
    }
}

REact-css-modules

Theming

Basically the same as theming with SASS

REact-css-modules

Alternatives

complete routing library for React

React Router

<Router history={browserHistory}>
    <Route path="/" component={App}>
        <Route path="about" component={About}/>
        <Route path="users" component={Users}>
            <Route path="/user/:userId" component={User}/>
        </Route>
        <Route path="*" component={NoMatch}/>
    </Route>
</Router>

What does it look like?

React Router

Integration with Redux

  • react-router can be used as is
  • react-router-redux allows us to keep URL and state in sync
  • react-router-redux is very "setup and forget"
import {Router, Route, browserHistory} from 'react-router'
import {syncHistoryWithStore, routerReducer} from 'react-router-redux'

const store = createStore(
  combineReducers({
    ...reducers,
    routing: routerReducer
  })
)
const history = syncHistoryWithStore(browserHistory, store)

ReactDOM.render(
    <Router history={history}>{/* Routes */}</Router>,
    document.getElementById('mount')
)

React Router

Navigation

import {Link, browserHistory} from 'react-router'

export default class Menu extends React.Component {
    render() {
        return (
            <ul>
                <li><Link to="/">Home</Link></li>
                <li><Link to="/news">News</Link></li>
                <li><a onClick={this.props.logoutClickHandler}>Logout</a></li>
            </ul>
        )
    }
}

export function mapDispatchToProps(dispatch) {
    return {
        logoutClickHandler: () => {
            dispatch(UserActionCreator.logout())
            browserHistory.push('/')
        }
    }
}

export default connect(null, mapDispatchToProps)(Menu)

React Router

Complex Views

<Router history={browserHistory}>
    <Route path="/" component={BaseLayout}>
        <Route path="about" components={{
            header: MainHeader,
            content: AboutContent,
            footer: Footer,
        }}/>
    </Route>
</Router>
export default BaseLayout extends React.Component {
    render() {
        return (
            <div className="header">{this.props.header}</div>
            <div className="content">{this.props.content}</div>
            <div className="footer">{this.props.footer}</div>
        )
    }
}

React Router

Something of note

Use Redux's mapStateToProps() to map react-router props to your components.

// Say we have a route "/search?num=20"

export SearchView extends React.Component {
    render() {
        return <SearchResults numToDisplay={this.props.numberOfResults} />
    }
}

function mapStateToProps(state, ownProps) {
    return {
        numberOfResults: ownProps.location.query.num
    }
}

export default connect(mapStateToProps, null)(SearchView)

React Router

That's about it!

For the basics anyway. react-router can handle:

  • Styling the active route
  • Confirming navigation
  • Server-side rendering
  • Code splitting and lazy loading (only load the JS required for the current view)

Reselect

Selector Library to help with relational data and derived data

(       No logo)

  • Allows the creator of selectors and caching so that no value is recalculated without need
  • Makes it easy to flatten the Redux state without sacrificing relational data
  • Bonus, it also further decouples components from your state

Reselect

Reselect

Reselect

Simple example

import { createSelector } from 'reselect'

export const profilesSelector = state => state.profiles
export const usersSelector = state => state.users
export const currentUserIdSelector = state => state.currentUserId

export const currentUserSelector = createSelector(
    [usersSelector, profilesSelector, currentUserIdSelector],
    (users, profiles, currentUserId) => {
        let user = users[currentUserId]
        user.profile = profiles[user.profileId]
        return user
    }
)
createSelector(...inputSelectors | [inputSelectors], resultFunc)

Reselect

With Redux

import {currentUserSelector} from 'selectors/UserSelectors'

// The view component here somewhere

function mapStateToProps(state) {
    return {
        user: currentUserSelector(state),
    }
}

Notice that mapStateToProps() is no longer bound to the state shape

Reselect

PAssing props to a selector

import {createSelector} from 'reselect'

profilesSelector = state => state.profiles

profileByIdSelectorCreator = (id) => {
    return createSelector(
        [profilesSelector],
        (profiles) => {
            return profiles[id]
        }
    )
}

// Use it like this
function mapStateToProps(state) {
    const profileByIdSelector = profileByIdSelectorCreator('72jbkaj292ka0')
    return {
        userProfile: profileByIdSelector(state),
    }
}

Reselect

In summary

We use it because it simplifies and optimizes computed values

We also like that it decouples our views from the state

Dependency injection

(aka Inversion of control)

Inversifyjs

Instead of importing concrete helper methods and services, you have a container building your services allowing you to abstract them to your application by working with interfaces instead

// BEFORE
import * as rest from 'rest'

export default class UserActionCreator {
    public getProfile() {
        return dispatch => {
            rest('/profile')
                .then(response => {
                    dispatch(getProfileSuccess(response))
                })
        }
    }
}
// AFTER
import {RestClient} from 'domain/interfaces/RestClient'

export default class UserActionCreator {
    private restClient: RestClient

    public constructor(restClient: RestClient) {
        this.restClient = restClient
    }

    public getProfile() {
        return dispatch => {
            this.restClient.get('/profile')
                .then(response => {
                    dispatch(getProfileSuccess(response))
                })
        }
    }
}

Inversifyjs

Benefits of abstraction

  • Reusability
  • Readability
  • Focus on what the object does, not how it does it
  • Remove code duplication via abstract classes

Inversifyjs

How it works

Inversifyjs

How it works

// Types.ts
// You can also use strings, but be careful about namespacing them
export default {
    RestClient: Symbol('RestClient')
    ActionCreator: Symbol('ActionCreator')
}

// Somewhere in your domain
export interface RestClient {
    get(resource: string, parameters: UrlParameters)
}

Define Interfaces and Types

Inversifyjs

How it works

// CujoClient.ts
import {injectable} from 'inversify'

@injectable()
class CujoClient implements RestClient {
    // Whatever your client does
}

// ActionCreator.ts
import {injectable, inject} from 'inversify'
import Types from 'types'
import {RestClient} from 'RestClient'

@injectable()
export default class ActionCreator {
    private restClient: RestClient

    public constructor(@inject(Types.RestClient) restClient: RestClient) {
        this.restClient = restClient
    }
}

Configure injectable objects

Inversifyjs

How it works

// Container.ts
let container = new Container()
container.bind<RestClient>(Types.RestClient).to(CujoClient)
container.bind<ActionCreator>(Types.ActionCreator).to(ActionCreator)

export default container

// Now, anywhere you can
import Container from 'Container'

let actionCreator = Container.get<ActionCreator>(Types.ActionCreator)
actionCreator.doSomeAction()

Wire it all together!

Inversifyjs

Simple (?) explanations

Types: Used to identify the service we want

@injectable(): Decorator that allows InversifyJS to have access to the method it needs to create and wire the object

@inject(): Tells InversifyJS which service to bind to the parameter

Inversifyjs

Abstraction: Concretely

Let's take the earlier ActionCreator example

import {injectable, inject} from 'inversify'
import Types from 'types'
import {RestClient} from 'RestClient'

@injectable()
export default class ProfileActionCreator {
    private restClient: RestClient

    public constructor(@inject(Types.RestClient) restClient: RestClient) {
        this.restClient = restClient
    }

    public getProfile(): Function {
        return dispatch => {
            this.restClient.get('/profile')
                .then(response => this.getProfileSuccess(response))
                .catch(error => this.getProfileFailure(error))
        }
    }
}

Inversifyjs

// Before
let container = new Container()
container.bind<RestClient>(Types.RestClient).to(CujoClient) // CujoClient
container.bind<ProfileActionCreator>(Types.ProfileActionCreator).to(ProfileActionCreator)

export default container

Abstraction: Concretely

// After
let container = new Container()
container.bind<RestClient>(Types.RestClient).to(RestfulClient) // Notice the change
container.bind<ProfileActionCreator>(Types.ProfileActionCreator).to(ProfileActionCreator)

export default container
// Usage has not changed at all in your code
import Container from 'Container'

let profileActionCreator = Container.get<ProfileActionCreator>(Types.ProfileActionCreator)
profileActionCreator.getProfile()

Inversifyjs

Testing is easier with constructor injection

No need to use SinonJS's stubs (though it's still useful!)

describe(`ActionCreator`, () => {
    let actionCreator: ActionCreator

    beforeEach(() => {
        const restClient = {
            get: () => {user: {name: 'Guyllaume'}}
        }

        actionCreator = new ActionCreator(restClient)
    })

    it(`Should successfully fetch the user's name`, () => {
        expect(actionCreator.getUserName('id').payload).to.equal('Guyllaume')
    })
})

Abstraction: Concretely

Inversifyjs

Other benefits of InversifyJS

  • Easy creation of factories
  • Allows the elimination of static methods
  • Container can be built based on environment

Overall, it really helps in following OOP's best practices (SOLID, GRASP)

The biggest part is done!

WIRING all that in a project

WIRING all that in a project

(Not like that)

WIRING all that in a project

Basics

  • All the logic should be in Services and ActionCreators
  • Never access props from the router or any other provider. Instead ask for specific values in your component's props
  • Reducers should only determine when information is returned to the state, never how
  • Never use strings, prefer string constants

Components

We have 4 types:

  • The ApplicationRoot: Only one per application, it's used to wire all the application together. App.tsx in the demo project.
  • Layouts: These are the various layouts the project will use. They get their children injected via React Router and they can be connected
  • Sections: The components that get injected into Layouts. They can also be connected
  • Components: Regular React components. They absolutely cannot be connected. We sometimes have wrapper components, see StatefulLink in the sample project.

Connecting components

You can in theory connect any component in any way, but we enforce some simple rules:

  • You can never directly access the state to send props. Use selectors
  • Sections and Layouts are allowed access to the Container
  • There is very minimal logic in the mapStateToProps() and mapDispatchToProps() methods
  • Always export the mapStateToProps() and mapDispatchToPros() methods
  • When connecting a component, always export the class and export default the connected version

Reusability

There are a few places where we accept project specificity

  • ActionCreators and Reducers: Most of them will not be reusable. You might be able to salvage some (like authentication), but it's rare.
  • View Sections: Layouts and components will be able to be reused from project to project, but view Sections will always be project specific.
  • The Container: It's possible to extend a base container with some general and basic services, but the container itself will not be reusable.

INversifyJS

InversifyJS is responsible for a few things:

  • ActionCreators (including a factory for ease of use)
  • Every services and utility (RestClient, AuthenticationService, Validators, Hydrators, etc)
  • StoreCreator

Reducers

Two simple rules:

  • Do not put any logic in reducers
  • Never mutate the state. Use Object.assign({}, newState) or Array.slice(0) or the spread operator instead

Extra rule if you are using TypeScript:

  • Type your reducer's action parameter
function profile(state: any = null, action: Action<User>): User {
    ...
}

In conclusion

  • It's really just what we use, based on our needs and our client's
  • We aim at maintainability and testability, our decisions reflect that
  • It's probably not perfect, I'm sure our stack will change in the future (redux-thunk being a good example)

Good resources

  • Redux "tutorial" - Really helped Redux click for me
  • awesome-react - A constantly updated list of awesome React resources. Don't like a library we use? Check for an alternative in there!
  • Reactiflux - A Discord community, tons of channels, tons of people

 

And finally, just... Code something. Play around!

  • Quality, scalability, service, maintanability
  • Custom Web application and platforms development
  • Consulting and team coaching

We also blog (sometimes)!

http://booborguru.com/

contact@majisti.com

514-316-9092

Thank you! Questions?

Email: guyllaume.cardinal@majisti.com

LinkedIn: Guyllaume Cardinal

Github: @gCardinal

Made with Slides.com