Skip to content

Instantly share code, notes, and snippets.

@ivosabev
Last active May 12, 2017 08:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ivosabev/22c4501585935fd944ae2b5d921e790d to your computer and use it in GitHub Desktop.
Save ivosabev/22c4501585935fd944ae2b5d921e790d to your computer and use it in GitHub Desktop.
The Hitchhiker's Guide to the Galaxy of React - Sample Code

In a few simple steps we will create an app that has a link with a counter that increases every time we click it.

We will start with very low and rudementary implementation of a store and with each example evolve it to reach a basic Flux and then Redux implementation.

  1. Global variable - single component
  2. Global variable - multiple components
  3. Props
  4. State
  5. Flux
  6. Redux
  7. Context
  8. Context with HOC

To run the examples install "create-react-app" and replace the "src/App.js" file

// Install create-react-app globally
npm install -g create-react-app

// Create new project
create-react-app my-app

// Starts the project and opens localhost:3000
cd my-app/
npm start
/*
We will create a global store, show its value and add a link to increment it with one on every click.
*/
import React from 'react';
const store = {count: 1}
class App extends React.Component {
handleClick = (e) => {
e.preventDefault();
++store.count;
this.forceUpdate();
}
render() {
return (
<div style={{paddingTop: 50, textAlign: 'center'}}>
<h1>Clicked: {store.count}</h1>
<a href="#" onClick={this.handleClick}>Click Me!</a>
</div>
);
}
}
export default App;
/*
We will extract the <h1> and <a> as functional components and pass the event handler as a prop
*/
import React from 'react';
const store = {count: 1}
const Counter = () => (<h1>Clicked: {store.count}</h1>);
const Clicker = (props) => (<a href="#" onClick={props.onClick}>Click Me!</a>);
class App extends React.Component {
handleClick = (e) => {
e.preventDefault();
++store.count;
this.forceUpdate();
}
render() {
return (
<div style={{paddingTop: 50, textAlign: 'center'}}>
<Counter />
<Clicker onClick={this.handleClick} />
</div>
);
}
}
export default App;
/*
We will move the store from the global variable space to a class property of <App> and pass it as a prop to our <Counter>
This example shows the React way of doing things, passing data down as a read-only props and passing actions up via
methods given as props to the children elements.
*/
import React from 'react';
const Counter = (props) => (<h1>Clicked: {props.store.count}</h1>);
const Clicker = (props) => (<a href="#" onClick={props.onClick}>Click Me!</a>);
class App extends React.Component {
store = {count: 1}
handleClick = (e) => {
e.preventDefault();
++this.store.count;
this.forceUpdate();
}
render() {
return (
<div style={{paddingTop: 50, textAlign: 'center'}}>
<Counter store={this.store} />
<Clicker onClick={this.handleClick} />
</div>
)
}
}
export default App;
/*
Calling `this.forceUpdate()` on every store change is not the React way of doing things. This is why we will introduce
`this.state`. When changing the React state, React will automatically know and update our UI.
*/
import React from 'react';
const Counter = (props) => (<h1>Clicked: {props.store.count}</h1>);
const Clicker = (props) => (<a href="#" onClick={props.onClick}>Click Me!</a>);
class App extends React.Component {
state = {
store: {count: 1}
}
handleClick = (e) => {
e.preventDefault();
this.setState({
store: {count: ++this.state.store.count},
});
}
render() {
return (
<div style={{paddingTop: 50, textAlign: 'center'}}>
<Counter store={this.state.store} />
<Clicker onClick={this.handleClick} />
</div>
)
}
}
export default App;
/*
We create a basic Flux implementation by adding a dispatcher and actions. The dispatcher processes the actions and
mutates tha store accordingly.
*/
import React from 'react';
function increment(store) {
return {
...store,
count: ++store.count,
};
}
const Counter = (props) => (<h1>Clicked: {props.store.count}</h1>);
const Clicker = (props) => (<a href="#" onClick={() => props.dispatch(increment)}>Click Me!</a>);
class App extends React.Component {
state = {
store: {count: 1}
}
dispatch = (action) => {
this.setState({
store: action(this.state.store),
});
}
render() {
return (
<div style={{paddingTop: 50, textAlign: 'center'}}>
<Counter store={this.state.store} />
<Clicker dispatch={this.dispatch} />
</div>
)
}
}
export default App;
/*
Redux is a popular Flux implementation. To make our example work more like Redux we need to make few small changes.
Instead of manipulating the store directly from our action we introduce a `reducer`, which will manipulate the store
for us based on what action we pass to it. Now we have our own Redux implementation. To make things more interesting we
can change our increment action to increase our counter with any number we pass as a payload, for example 2.
*/
import React from 'react';
function increment(payload) {
return {
type: 'INCREMENT',
payload,
};
}
function reducer(action, store) {
switch (action.type) {
case 'INCREMENT':
return {
...store,
count: store.count + action.payload,
}
default:
return store;
}
}
const Counter = (props) => (<h1>Clicked: {props.store.count}</h1>);
const Clicker = (props) => (<a href="#" onClick={() => props.dispatch(increment(2))}>Click Me!</a>);
class App extends React.Component {
state = {
store: {count: 1}
}
dispatch = (action) => {
this.setState({
store: reducer(action, this.state.store),
});
}
render() {
return (
<div style={{paddingTop: 50, textAlign: 'center'}}>
<Counter store={this.state.store} />
<Clicker dispatch={this.dispatch} />
</div>
)
}
}
export default App;
/*
*/
import React from 'react';
function increment(payload) {
return {
type: 'INCREMENT',
payload,
};
}
function reducer(action, store) {
switch (action.type) {
case 'INCREMENT':
return {
...store,
count: store.count + action.payload,
}
default:
return store;
}
}
const Counter = (props, context) => (<h1>Clicked: {context.store.count}</h1>);
Counter.contextTypes = {
store: React.PropTypes.object,
};
const Clicker = (props, context) => (<a href="#" onClick={() => context.dispatch(increment(2))}>Click Me!</a>);
Clicker.contextTypes = {
dispatch: React.PropTypes.func,
};
class App extends React.Component {
state = {
store: {count: 1}
}
dispatch = (action) => {
this.setState({
store: reducer(action, this.state.store),
});
}
static childContextTypes = {
dispatch: React.PropTypes.func,
store: React.PropTypes.object,
}
getChildContext() {
return {
dispatch: this.dispatch,
store: this.state.store,
};
}
render() {
return (
<div style={{paddingTop: 50, textAlign: 'center'}}>
<Counter />
<Clicker />
</div>
)
}
}
export default App;
/*
In general using context is highly discouraged in React, but if you like to live dangerously and insist on using it.
Your best bet is to isolate the usage of context with a High-Order Component and pass the context as props to your
dumb/functional components. To improve debuggin we also set the displayName property of our HOC manually to tell us
the name of the inner/wrapped component.
*/
import React from 'react';
function increment(payload) {
return {
type: 'INCREMENT',
payload,
};
}
function reducer(action, store) {
switch (action.type) {
case 'INCREMENT':
return {
...store,
count: store.count + action.payload,
}
default:
return store;
}
}
function injectContext(InnerComponent) {
return class OuterComponent extends React.Component {
static displayName = `WithContext(${InnerComponent.displayName || InnerComponent.name || 'Component'})`;
static contextTypes = {
store: React.PropTypes.object,
dispatch: React.PropTypes.func,
}
render() {
return (<InnerComponent {...this.props} store={this.context.store} dispatch={this.context.dispatch} />);
}
}
}
const Counter = (props) => (<h1>Clicked: {props.store.count}</h1>);
const CounterContainer = injectContext(Counter);
const Clicker = (props) => (<a href="#" onClick={() => props.dispatch(increment(2))}>Click Me!</a>);
const ClickerContainer = injectContext(Clicker);
class App extends React.Component {
state = {
store: {count: 1}
}
dispatch = (action) => {
this.setState({
store: reducer(action, this.state.store),
});
}
static childContextTypes = {
dispatch: React.PropTypes.func,
store: React.PropTypes.object,
}
getChildContext() {
return {
dispatch: this.dispatch,
store: this.state.store,
};
}
render() {
return (
<div style={{paddingTop: 50, textAlign: 'center'}}>
<CounterContainer />
<ClickerContainer />
</div>
)
}
}
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment