React lecture

Preview:

Citation preview

React & ReduxChristoffer Noring

Google Developer Expert

ContentsReact basicsFluxReduxCSSMixinsRouter

Tooling and Best Practices covered by Fabrice and Mathieu

React Basics

React lib

React.createClass({})

class Component extends React.Component

React DOM

ReactDOM.render( [Component]/ [JSX Expression], [element] )

Render our app

Minimum Setup ES5<script src="node_modules/react/dist/react.js"></script> <script src="node_modules/react-dom/dist/react-dom.js"></script> <script src=“https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>

<body> <div id="example"></div> <script type="text/babel"> ReactDOM.render( ); </script> </body>

<h1>Hello, world!</h1>,document.getElementById('example')

Where to render the appContent

JSXSyntax extension to javascript

const element = <h1>Hello, world!</h1>;

Allows us to do things like:

Needs to be transpiled :

const element = ( <h1 className="greeting"> Hello, world! </h1> );

JSXconst element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' );

ES5

JSX is your friend, you don’t have to use it

HTML in my javascript!!

ComponentsES5 : React.createClass({})

Your first componentapp.js index.html

var App = React.createClass({ render: function() { return ( <div className="app"> Hello, world! I am an app component </div> ); } });

ReactDOM.render( <App />, document.getElementById('example') );

render() is like a paint method

<body> <div id="example"></div> <script type="text/babel">

</script> </body>

<h1>Hello, world!</h1>,document.getElementById('example')

ReactDOM.render( );

<script type="text/babel" src="app.js"></script>

replace with

createClass(), creates the component

Your second componentvar CV = React.createClass({ render: function() { return ( <div className="comments"> Showing your cv </div> ); } });

var App = React.createClass({ render: function() { return ( <CV /> ); } });

Component within a component = component centric programming

Call createClass() again

return CV component from App component

Component, input<Component prop={ data }>

this.props.prop

Component with datalet data = { name : 'chris', profession : 'software developer', skills : ['.net', 'javascript', 'angular', 'react'] }

This is what we want to renderlooks like a property

looks like a list

ReactDOM.render( <App data={ data } />, document.getElementById('example') );

property = { variable }

Access data inside component like this:var App = React.createClass({ render: function() { return ( <div> { this.props.data.name } </div> ); } });

this.props.data.name“chris”

Rendering a listvar App = React.createClass({ render: function() {

return ( <div> <div> { this.props.data.name } </div> <div>{ skills }</div> </div> ); } });

let skills = this.props.data.skills.map(skill => { return ( <div className="skill">{skill}</div> ); });

Projection

Interpolation

Everything is in the “App” component = BAD

RefactoringPutting everything in the App component isn’t very “react like” of us

var App = React.createClass({ render: function() { return ( <CV data={ this.props.data } /> ); } });

var CV = React.createClass({ render: function() { return ( <div className="cv"> { this.data.props.name } <Skills data={ this.data.props.skills } > </div> ); } });

var Skills = React.createClass({ render : function(){ var skills = this.props.data.map(function(skill) { return ( <div className="skill">{skill}</div> ); }); return( <div className="skills"> <h2>Skills</h2> { skills } </div> ); } })

We can do this even bettervar Skills = React.createClass({ render : function(){ var skills = this.props.data.map(function(skill) { return (

<Skill data={ skill } /> ); }); return( <div className="skills"> <h2>Skills</h2> { skills } </div> ); } })

var Skill = React.createClass({ render : function(){ return( <div className="skills"> <h3>{ this.props.data }</h3> </div> ); } })

A component should do one thing well

One Skill comp per skill item

Component tree so farApp

CV

Skills

Skill

Skill

Skill

etc…

What about methods?

<button type="submit" onClick={this.onSubmit} > Save new skills </button>

var CV = React.createClass({ onSubmit : function(e){ e.preventDefault(); // do stuff }, render : function(){ return (

) }

Add method to our object literal

Refer to method in markup

State

Component State

var App = React.createClass({ getInitialState : function() { return { a : ‘some state’ }; }, render : function() { return <div>{ this.state.a }</div> }

});

Reading state getInitialState is read once per bootstrap,

define your state here

this.state.<prop>

Changing statevar App = React.createClass({

getInitialState : function() { return { newSkill : ‘some state’, b : ‘some other state’ }; }, render : function() { return <div> <input value={this.state.newSkill} > { this.state.a } </div> }

});

For every change of input field update state

onChange={this.onSkillChange}

bind to onchange

onSkillChange : function(e){ this.setState({newSkill : e.target.value}); }

State summarygetInitialState(){ return { prop : ‘’ } }

{ this.state.prop }

this.setState({ prop : ‘newValue’ })

Lifecycle events

Initial State Changes Props changes

Unmounting

InitialgetDefaultProps

getInitialState

componentWillMount

render

componentDidMount

Set initial values

access DOMfetch data

State changesshouldComponentUpdate

componentWillUpdate

render

componentDidUpdate

boolean : is rerendering needed

prepare for update

perform DOM operations

Props changecomponentWillReceiveProps

shouldComponentUpdate

componentWillUpdate

render

componentDidUpdate

called only when props have changed

perform DOM operations

Unmounting

componentWillUnmount

is called before component is removed from DOM

do cleanup, think of it as a destructor

ES6

We want to write in ES6 cause it has nice features

Object.assign() Let ConstSpread Operator Lambdas

ES6 modules

import {} from ‘’export default

export { a,b,c }

ES6 componentclass Todo extends React.Component { constructor(){ super(); this.action = this.action.bind(this); this.state.prop = value }

render() { return ( <div onClick={this.action}>{this.props.title}</div> ) }

action(e){ this.props.click( this.props.id ); } }

GOTCHA, we need to call the following, for our methods to be picked up

We inherit from React.Component instead of

calling React.createClass({})

Otherwise its business as usual

we DON’t use getInitialState(){} we just set this.state in constructor

We create a proper class rather than an object literal

PropTypes

Catch bugs with type checking

Will give an error in a tool, wrong type

Component.propTypes ={ title : React.PropTypes.string }

<Component title=1 >

Example 1 - wrong type

Component.propTypes ={ title : React.PropTypes.string.isRequired }

Example 2 - required

<Component > Will giver error, prop missing

Many validation types

oneOfType oneOf

arrayOfobjectOf

element instanceOf

symbolcustom

Further reading, https://facebook.github.io/react/docs/typechecking-with-proptypes.html

FluxArchitecture pattern by Facebook

Action describes what should happen with what data

Action

Dispatcher

Store

React e.g. Add todo

Store notifies listener that data has changed and needs to be reread

Dispatch action to store, tell the store

Unidirectional flow, everything flows in one direction

Action

Dispatcher

Store

React

{ type : ADD_TODO, todo : { title : ‘dfdfd’ } }

What Payload

DispatcherAction

Dispatcher

Store

React

Dispatches actions dispatch(action) Handles a dispatched action through register(function(action){})

StoreAction

Dispatcher

Store

React

Contains your state, usually same file have ability to perform ajax and also notify listeners when state has been updated

var Todos = React.createClass({ getInitialState : function(){ return { todos : Store.getTodos() } } render : function(){ return ([ render todos ]) },

})

//todos-component.js

componentDidMount : function(){ Store.addChangeListener( this._onChange ) }, componentWillUnmount : function(){ Store.removeChangeListener( this._onChange ) }, onChange() { this.setState({ todos : Store.getTodos() }) }

Get data

1

Update state so render() is called

2

store.jsvar todos = []; function loadTodos(){ return todos; }

var Store = merge(EventEmitter.prototype, { getTodos : function(){ return todos; } emitChange : function(){ emit(‘change’) }, addChangeListener : function(callback){ this.on(‘change’, callback) }, removeChangeListener : function(callback) { this.removeListener(‘change’, callback) } })

Dispatcher.register(function(payload){ var action = payload.action; switch(action) { case ADD_TODO: todos.push( payload.data ) case LOAD_TODOS: loadTodos(); } Store.emitChange(); })

Calls _onChange() on the component

Called when Dispatcher.dispatch()

getData addListener removeListener notifyChange

var AddTodo = React.createClass({ render : function(){ return ([ todo input ]) }, createTodo : function(todo){ Actions.addTodo( todo ) } })

//add-todo-component.js

Calls the ActionCreator with a type and a payload

ActionCreatorActions = { addTodo : function(todo) { Dispatcher.dispatch( { actionType : ADD_TODO, data : todo } ) } … }

Dispatch the action

Redux

Why Redux or Flux growing pains

Scales well on larger complex apps

Boiler plate is way too big on smaller apps

Data flow

Action

Store

React

Reducers

No dispatcher!!

Action

Action

Store

React

Reducers

function addTodo( todo ) { return { type : ‘ADD_TODO’, todo: todo } }

Action Creator

Represent user intent

Must have a type

Redux Store

store.dispatch( action );

store.subscribe( listener );

store.getState()replaceReducer( nextReducer )

Action

Store

React

Reducers

Reducers

Action

Store

React

Reducers

function reducer(state, action) { return newState; }

(state, action) => state

Must be pure

Multiple reducers per app

We want to change the state, without mutating so how?

Change object

Change Array

Change ObjectWhat about a large object hierarchy?

Object.assign( target, oldObject, newState )

Object.assign( {}, { update : false, name : ‘’ }, { update : true })

{ update : true, name : ‘’ }

Merged

Changing array//mutable state = [ { id : 1, name : 'tomato' } ]

state.push( { id: 2, name : 'cucumber' } );

return state;

Mutating

Point of mutation

//immutable state = [ { id : 1, name : 'tomato' } ]

return [ ...state, Object.assign({}, state, course) ]

Immutable

old array+

new itemSpread operator …

Reducer no-no listMutate arguments

Perform side effects

Call non-pure functions

Benefits to immutable stateClarity answers - who changed that state?

Mutable, anyone could have changed it

Immutable, only a reducer could have changed it

Performance

No need to check every single property on an object

if(oldState != newState), reference check

Time travel debugging

State Summary

ES5lodash merge

lodash extend

Object-assign ( NPM )

Reference checking is super fast

Enables time-travel debugging through browser plugin

Object.assign()

… spread operator for arrays

ES6

Change state by

All Reducers are called on each dispatch

addItemReducer

removeItemReducer

listItemsReducer

Dispatching addItem{ type: ADD_ITEM, : item : item }

returns new state

return state

return state

Connect Redux to our App

react-redux

Action

Store

React

Reducers

Provider, attaches app to store

Connect, creates container items

Connect store data to our app

ComponentApp

ProviderStore Data

Setup our store

Wrap App component in a Provider

import { combineReducers } from 'redux'; import todos from './todo-reducer';

const rootReducer = combineReducers({ todos : todos}) // OR todos only, LHS is implied

export default rootReducer;

reducers/index.js

Root reducer

Combines all reducers in one, this is one we feed to the store

Initialise storeimport { createStore, applyMiddleware } from 'redux'; import rootReducer from '../reducers' // index.js import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';

export default function configureStore(initialState) { return createStore( rootReducer, initialState, applyMiddleware( reduxImmutableStateInvariant() ) ) }

configure-store.js

Create store give it a

root reducer initial state

and add middleware

Provider

<Provider store={store} > <App /> </Provider>

Uses Reacts context - don’t touch

import configureStore from './store/configureStore'; import { Provider } from 'ReactRedux';

Make your store available to all your components

Component

Wrap our component in a container component using

Connect

Presentational Component

Container component

export default connect( mapStateToProps, mapDispatchToProps )( PresentationalComponent )

what statewhat functions

what state should I expose as props

mapStateToProps

mapStateToProps = () => { return { appState : ‘’, otherAppState : ‘’ } }

// Componentthis.props.appStatethis.props.otherAppState

Every time a change happens this function is rerun, so don’t do anything expensive in there

mapDispatchToProps cont..mapDispatchToProps = (dispatch) { return { loadTodos : () => { dispatch( loadTodos() ); }, addTodo : (todo) => { dispatch( addTodo(todo) ); } } }

Manual approach, but clear whats happening, recommended when starting out

e.g. this.props.loadTodos();

mapDispatchToProps - nicerwhat actions do we want to expose to the component

mapDispatchToProps = () => { return { actions : bindActionCreators(actions, dispatch) } }

This is a shorthand using Redux

this.actions.methodName()

Container componentSmart component

Focus on how things work

“Aware” of Redux

Subscribe to Redux state

Dispatch redux actions

react-redux lib

Presentational componentNormal component that show data through props, knows nothing about Redux

Presentation component gets decorated

class TodoComponent extends React.Component{ onSave(){ this.props.dispatch(todoActions.addTodo(this.state.todo)); //console.log(this.state.todo.title); } }

function mapStateToProps(state, ownProps) { return { todos : state.todos } }

export default connect( mapStateToProps) (TodoComponent); 1

2

3

Reduxify your component in 3 steps

Decorate our Component

Expose state

Calling and dispatching an action

Redux flow so farActions Dispatches an action

Reducers current state + action = new state

Store let connected components know of state change

ReactRedux determine wether to tell React to update UI

React new data passed through props

YES

Something happenedReact

Redux concepts so farContainer and Presentation components

Has methods, Has state, Knows Redux

Just props No Redux

ReactReduxProvider Pass store data to ComponentsConnect

mapStateToProps Pass state and methods to ComponentsmapDispatchToProps

Code Demo

Improving our Redux

Change our call to .connect()export default connect( mapStateToProps) (TodoComponent);

export default connect( mapStateToProps mapDispatchToProps) (TodoComponent);

function mapDispatchToProps(dispatch) { return addTodo : todo => dispatch(todoActions.addTodo(todo)) }

Wrap the action in a dispatch()

onSave(){ this.props.dispatch(todoActions.addTodo(this.state.todo)); }

Change to

onSave(){ this.props.addTodo(this.state.todo)); }

All knowledge of dispatch is removed Much cleaner

Dispatch() is no longer injected into the component

Fixing the component

And a little more…

import { bindActionCreators } from ‘redux’

This

function mapDispatchToProps(dispatch) { return addTodo : todo => dispatch(todoActions.addTodo(todo)) }

Becomes thisfunction mapDispatchToProps(dispatch) { return actions : bindActionCreators( todoActions, dispatch ) }

MAGIC !!onSave(){ this.props.actions.addTodo(this.state.todo)); }

Clean up magic strings

export default { ADD_TODO : 'ADD_TODO' }

actionTypes.js

todo-actions.jsimport actionTypes from './action-types';

function addTodo(todo) { return { type : actionTypes.ADD_TODO, todo : todo }; }

todo-reducer.jsexport default function todoReducer(state = [], action) { switch(action.type) { case actionTypes.ADD_TODO: return [ ...state, Object.assign({}, action.todo) ]

And so on…

Replace string with constant

Async load

export function loadTodos() { return function(dispatch) { return service.getTodos().then( todos => { dispatch( loadTodosSuccess(todos) ) }).catch(error => { console.error(error) }) } }

export function loadTodosSuccess(todos) { return { type : types.LOAD_TODOS, todos : todos } }

todo-actions.js

index.jsconst store = configureStore();

store.dispatch( loadTodos() )

set initial data

Call dispatch when Ajax is done

export default function todoReducer( state = [], action) { switch(action.type) { … case actionTypes.LOAD_TODOS : return action.todos default : return state; } }

todo-reducer.js

Componentclass TodoComponent extends React.Component{ render(){ let todos = this.state.todos.map( todo => { return <div>{ todo }</div> }) return ( <div>{ todos }</div> ) } }

function mapStateToProps(state, ownProps) { return { todos : state.todos } }

export default connect( mapStateToProps) (TodoComponent);

Async load summaryCreate async action that calls dispatch when done

Add reducer for loading data

Add said reducer to rootReducer

store.dispatch( loadAction() ) in index.js

ensure its exposed in mapStateToProps

use in presentation component

Thank you

Recommended