View
276
Download
0
Category
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