Build process
NPM scripts
Webpack
Babel
ESLint
Automated tests
Mocha
Chai
Stateless components
import React from 'react'
const Button => ({ onClick, isDisabled }) => (
<button className="button" onClick={onClick} disabled={isDisabled}/>
)
export default Button
components/button.js
High-order components
Providers
import React, { Component, PropTypes, Children } from 'react'
class ThemeProvider extends Component {
static propTypes = {
themeName: PropTypes.string.isRequired,
children: PropTypes.element.isRequired
}
static childContextTypes = {
theme: PropTypes.object.isRequired
}
getChildContext() {
return {
theme: {
name: this.props.themeName
}
}
}
render() {
return Children.only(this.props.children)
}
}
import React from 'react'
import { render } from 'react-dom'
import ThemeProvider './theme/provider'
render(
<ThemeProvider name="my-theme">
// ...
</ThemeProvider>
, document.getElementById('root'))
theme/provider.js
index.js
Decorators
import { Component, PropTypes } from 'react'
const Themeable = ComposedComponent => class extends Component {
static contextTypes = {
theme: PropTypes.object.isRequired
}
render() {
const { theme } = this.context
return <ComposedComponent {...this.props} themeName={theme.name}/>
}
}
import React from 'react'
import Themeable from './theme/decorator';
@Themeable
const Button => ({ themeName }) => (
// ...
)
export default Button
theme/decorator.js
components/button.js
Immutable application state
Immutable.js
Flux Standard Action
Redux promise
Redux actions
Redux modules
import { expect } from 'chai'
import { fromJS } from 'immutable'
describe('Todos state', () => {
it('adds todos', () => {
const state = fromJS([]);
const nextState = module.handleAdd(state, { payload: { id: 1, text: 'Buy beer' } })
expect(nextState).to.include(fromJS({ id: 1, text: 'Buy beer' }))
})
it('removes todos', () => {
const state = fromJS([
{ id: 1, text: 'Buy beer' },
{ id: 2, text: 'Pick up the kids' }
])
const nextState = module.handleRemove(state, { payload: 2 })
expect(nextState).to.be.empty
})
})
state/modules/todos-spec.js
import { fromJS, List } from 'immutable'
import { createAction, handleActions } from 'redux-actions'
const ADD = 'todos/ADD';
const REMOVE = 'todos/REMOVE';
export function handleAdd(state, action) {
return state.push(fromJS(action.payload)))
}
export function handleRemove(state, action) {
return state.filter(todo => todo.get('id') !== action.payload))
}
const defaultState = List()
const reducer = handleActions({
[ADD]: handleAdd,
[REMOVE]: handleRemove
}, defaultState)
export const addTodo = createAction(ADD)
export const removeTodo = createAction(REMOVE)
export default reducer
state/modules/todos.js
React Redux
import { combineReducers, applyMiddleware, createStore } from 'redux'
import promiseMiddleware from 'redux-promise'
import { default as todos } from './modules/todos';
export function finalCreateStore(initialState, history) {
return createStore(
combineReducers({ todos, ... }),
initialState,
applyMiddleware(promiseMiddleware)
)
}
state/index.js
import React from 'react'
import { render } from 'react-dom'
import { Router, browserHistory } from 'react-router'
import { Provider as StateProvider } from 'react-redux'
import { finalCreateStore } from 'state/index'
import routes from './routes';
const store = finalCreateStore();
render(
<StateProvider store={store}>
<Router history={browserHistory} routes={routes}/>
</StateProvider>,
document.getElementById('root')
)
index.js
import React from 'react'
import { connect, bindActionCreators } from 'react-redux'
import { addTodo, removeTodo } from '../state/modules/todos'
export const TodoItem = ({ data, onRemove }) => (
<li className="todo-item" key={data.id}>
{data.text} <span onClick={onRemove.bind(null, id)}>x</span>
</li>
)
export const TodoList = ({ data, onRemove }) => (
<ul className="todo-list">{data.map(todo => <TodoItem data={todo} onRemove={onRemove}/>)}</ul>
)
function mapStateToProps(state) {
return { data: state.todos }
}
@connect(
mapStateToProps,
bindActionCreators({ addTodo: onAdd, removeTodo: onRemove })
)
export const Todos = ({ data, onAdd, onRemove }) => (
<div className="todos">
<TodoList data={data} onRemove={onRemove}/>
...
</div>
)
export default Todos
index.js
Prop types and default props
import React, { PropTypes } from 'react'
import { List } from 'immutable'
export const TodoItem ...
TodoItem.propTypes = {
data: PropTypes.shape({
id: PropTypes.number.isRequired
text: PropTypes.string.isRequired,
}).isRequired,
onRemove: PropTypes.func.isRequired
}
export const TodoList ...
TodoList.propTypes = {
data: PropTypes.instanceOf(List),
onRemove: PropTypes.func
}
TodoList.defaultProps = {
data: List()
}
components/todos.js
Routing
React Router
import React from 'react'
import { Route } from 'react-router'
import Todos from './components/todos';
const routes = {
index: '/',
todos: '/todos'
}
export function getRoute(name) {
return routes[name]
}
export default (
<Route path={routes.index}>
<Route path={routes.todos} component={Todos}/>
...
</Route>
)
routes.js
import React from 'react'
import { render } from 'react-dom'
import { Router, browserHistory } from 'react-router'
import routes from './routes';
render(
<Router history={browserHistory} routes={routes}/>,
document.getElementById('root')
)
index.js
Named components
export default (
<Route path={routes.index} component={App}>
<Route path={routes.todos} components={{ main: Todos, sidebar: TodosSidebar }}/>
...
</Route>
)
routes.js
import React from 'react'
export const App = ({ main, sidebar }) => (
<div className="app">
<div className="app-sidebar">{sidebar}</div>
<div className="app-main">{main}</div>
</div>
)
export default App
components/app.js
Redux React Router
Single responsibility principle
Thank you!
Questions?
React best practices
By Christoffer Niska
React best practices
- 1,782