Over the hype and under the hood
React wat
- "A JavaScript library for building user interfaces" - reactjs.org
- Emphasis on 'library'
- Can be run both server side and client side
-
Component tree of re-usable components
-
Just Javascript = fast learning curve
👶 2012
🔓 2013
📱 2015
🏃 2017
What went down
Need to know
- state object
- props object
- render function
- (lifecycle functions)
An entire app I
$ npm init -y
$ npm install --save react react-dom
$ mkdir src
$ touch src/index.html
$ touch src/App.js
$ yarn init -y
$ yarn add react react-dom
or
or
An entire app II
<!DOCTYPE html>
<html>
<head>
<title>FluffyReact</title>
</head>
<body>
<div id="my-app-root"></div>
<script src="App.js"></script>
</body>
</html>
import React from 'react';
import ReactDom from 'react-dom';
class App extends React.Component {
render() {
return <h1>It's so fluffy!!!</h1>;
}
}
ReactDom.render(
<App />,
document.getElementById('my-app-root')
);
index.html
x
import React from 'react';
import ReactDom from 'react-dom';
class App extends React.Component {
render() {
return React.createElement('h1', null, 'It\'s so fluffy!!!');
}
}
ReactDom.render(
React.createElement(App, null, null),
document.getElementById('my-app-root')
);
App.js
<!DOCTYPE html>
<html>
<head>
<title>FluffyReact</title>
</head>
<body>
<div id="my-app-root"></div>
<script src="App.jsx"></script>
</body>
</html>
An entire app III
import React from 'react';
import ReactDom from 'react-dom';
class App extends React.Component {
render() {
return <h1>It's so fluffy!!!</h1>;
}
}
ReactDom.render(<App />, document.getElementById('my-app-root'));
- We've got a bunch of stuff that most browsers don't understand just yet e.g Module imports, Classes and JSX
- We need to transpile this so it can run in most browsers
App.js
An entire app IV
$ parcel src/index.html
- "Blazing fast, zero configuration web application bundler" - parceljs.org
- Out the box babel transpilation and hot module resolution with zero configuration
- That's it! Your app should be available through your browser at localhost:1234
$ npm install -g parcel-bundler
$ yarn global add parcel-bundler
or
Component anatomy:
case study
- Meet Sipho, he loves fluff
- He knows how much fluff he
owns - He knows how to make more fluff
- He gets told what colour fluff
can make - He makes fluff when we tell
him to
Todo app
Demo Source: github.com/ntkzwane/fluffy-react
import React from 'react';
class Sipho extends React.Component {
constructor(props) {
super(props);
this.gimmeFluff = this.gimmeFluff.bind(this);
this.state = { fluffs: 0 }; // knows how much fluff he owns
}
gimmeFluff() { // knows how to make more fluff
this.setState({ fluffs: this.state.fluffs + 1 });
}
render() {
return (
<div>
<h1>
Sipho has {this.state.fluffs}
{this.props.colour} fluff(s) {/* gets told what colour fluff can make */}
</h1>
<button onClick={this.gimmeFluff}> {/* makes fluff when we tell him to */}
Generate Fluffs
</button>
</div>
);
}
}
export default Sipho;
Sipho.jsx
render() {
return <Sipho colour="purple" />;
}
render() {
return <h1>It's so fluffy!!!</h1>;
}
- Meet Palesa, she’s an enabler
- She knows how to make fluff, but only for Sipho
- She makes fluff when we tell her to
- She knows how much fluff Sipho has
- She knows how much fluff she’s given to Sipho
Component anatomy:
even more fluff
- Problem:
- We need to share state between components
- We need to update a sibling component’s state
- Solution:
- Move the state into the parent component
- Let the parent component control state changes
Component data flow
import React from 'react';
import ReactDom from 'react-dom';
import Sipho from './Sipho';
import Palesa from './Palesa';
class App extends React.Component {
constructor (props) {
super(props);
this.giveSiphoFluff = this.giveSiphoFluff.bind(this);
this.state = { siphosFluffs: 0 };
}
giveSiphoFluff() {
this.setState({ siphosFluffs: this.state.siphosFluffs + 1 });
}
render () {
return (
<div>
<Sipho
colour="purple"
fluffs={this.state.siphosFluffs}
gimmeFluff={this.giveSiphoFluff}
/>
<hr />
<Palesa
siphosFluffs={this.state.siphosFluffs}
giveSiphoFluff={this.giveSiphoFluff}
/>
</div>
);
}
}
ReactDom.render(<App />, document.getElementById('my-app-root'));
App.jsx
import React from 'react';
class Palesa extends React.Component {
constructor(props) {
super(props);
this.state = { charityFluffs: 0 }; // knows how much fluff she’s given to Sipho
this.giveFluff = this.giveFluff.bind(this);
}
giveFluff() { // knows how to make fluff for Sipho
this.setState({ charityFluffs: this.state.charityFluffs + 1 }, () => {
this.props.giveSiphoFluff();
});
}
render() {
return (
<div>
<h1>
{/* knows how much fluff Sipho has */}
Palesa's contributed {this.state.charityFluffs} fluff(s)
towards Sipho's {this.props.siphosFluffs} fluff(s)
</h1>
<button onClick={this.giveFluff}>
Generate Fluff For Sipho!
</button>
</div>
);
}
}
export default Palesa;
Palesa.jsx
import React from 'react';
const Sipho = ({ colour, fluffs, gimmeFluff }) => (
<div>
<h1>Sipho has {fluffs} {colour} fluff(s)</h1>
<button onClick={gimmeFluff}>Generate Fluff!</button>
</div>
);
export default Sipho;
Sipho.jsx
import React from 'react';
function Sipho(props) {
return (
<div>
<h1>Sipho has {props.fluffs} {props.colour} fluff(s)</h1>
<button onClick={props.gimmeFluff}>Generate Fluff!</button>
</div>
)
};
export default Sipho;
import React from 'react';
class Sipho extends React.Component {
render() {
return (
<div>
<h1>Sipho has {this.props.fluffs} {this.props.colour} fluff(s)</h1>
<button onClick={this.props.gimmeFluff}>Generate Fluff!</button>
</div>
);
}
}
export default Sipho;
A stateful catastrophe
- Internal React state works beautifully for simple apps
- Gets messy with complex applications, that have state changes in different places, that may be asynchronous, and dependent on other models
- What started off as a neat dependency tree can end up being spaghetti
Unidirectional data flow
- We need a single source of truth! As well as a controlled way of updating it
-
React grew up side by side with an architectural pattern aimed at facilitating this paradigm
-
Flux is not a tool, its a pattern
-
Many libraries and helpers exist that help you adopt Flux in your application
A realisation of the principle
- Redux is a state management library influenced by Flux principles
- There exists only one application store, and each sub-domain is a slice of that store
- A source so true, it's immutable (store)
- The read-only state can only be mutated emitting an event specifying an intent to update the state (action)
- Pure functions handle these actions to handle state mutations (reducer)
$ npm install --save redux react-redux
$ yarn add redux react-redux
or
Redux
View 👉 Action 👉 Reducer 👉 Store 👉 View
- Provides a store object with the following API:
- dispatch () - trigger a store change
- subscribe() - respond to store changes
- getState() - read the store's data
Redux-managed fluff:
Reducer
const initialState = {
siphosFluffs: 0,
charityFluffs: 0
};
export function fluffs(state = initialState, action) {
switch (action.type) {
case 'GIMME_FLUFF':
return {
...state,
siphosFluffs: state.siphosFluffs + 1
};
case 'GIVE_SIPHO_FLUFF':
return {
...state,
siphosFluffs: state.siphosFluffs + 1,
charityFluffs: state.charityFluffs + 1
};
default:
return state;
}
}
fluffs.js
Redux-managed fluff:
Actions
export function giveSiphoFluff() {
return { type: 'GIVE_SIPHO_FLUFF' };
}
export function gimmeFluff() {
return { type: 'GIMME_FLUFF' };
}
actions.js
Pause, HOC
- We need to be able to connect our component to the dispatcher as well as the store
- We could manually tap into dispatch(), subscribe() and getState() API
- Rather use optimised React-Redux bindings:
connect()
- Higher order component
-
Accepts our function as an argument
-
Mutates our props to include our actions and state from the store
<Provide />
- Just a component
- Makes the store available to our components
import React from 'react';
import ReactDom from 'react-dom';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';
import * as actions from './actions';
import { fluffs } from './reducer';
import Sipho from './Sipho';
import Palesa from './Palesa';
const App = ({ siphosFluffs, charityFluffs, gimmeFluff, giveSiphoFluff }) => (
<div>
<Sipho
colour="purple"
fluffs={siphosFluffs}
gimmeFluff={gimmeFluff}
/>
<hr />
<Palesa
siphosFluffs={siphosFluffs}
charityFluffs={charityFluffs}
giveFluff={giveSiphoFluff}
/>
</div>
);
const mapStateToProps = state => ({
siphosFluffs: fluffs(state, {}).siphosFluffs,
charityFluffs: fluffs(state, {}).charityFluffs
});
const mapDispatchToProps = dispatch => ({
giveSiphoFluff: () => dispatch(actions.giveSiphoFluff()),
gimmeFluff: () => dispatch(actions.gimmeFluff())
});
const store = createStore(fluffs);
const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App);
ReactDom.render(
<Provider store={store}>
<AppContainer />
</Provider>,
document.getElementById('my-app-root')
);
App.jsx
import React from 'react';
const Sipho = ({ colour, fluffs, gimmeFluff }) => (
<div>
<h1>Sipho has {fluffs} {colour} fluff(s)</h1>
<button onClick={gimmeFluff}>Generate Fluff!</button>
</div>
);
export default Sipho;
Sipho.jsx
import React from 'react';
const Palesa = ({ charityFluffs, siphosFluffs, giveFluff }) => (
<div>
<h1>
Palesa's contributed {charityFluffs} fluff(s)
towards Sipho's {siphosFluffs} fluff(s)
</h1>
<button onClick={giveFluff}>
Generate Fluff For Sipho!
</button>
</div>
);
export default Palesa;
Palesa.jsx
But Why?
- Cleaner views
- Testable code
- Debug-able state changes
- Linear state dependency tree
- Smoother scaling (your app, and the team)
- Only if you need it, though
Questions?
ReactJS: Over the hype and under the hood
By Ntokozo Zwane
ReactJS: Over the hype and under the hood
- 159