all logos are copyrighted by their respective owners
all slides are licensed CC-BY-SA and code samples MIT
tiny.cc/react-second-steps
hacker and floss activist
digital nomad
full-stack JS dev with
highly opinionated
batteries included (with ui-router)
created for medium and big SPAs
slow with large datasets
preferring custom magic to JS
two-way data-binding
very little opinionated
just a V of MVC
created for components, not SPAs
blazing fast
embracing commonJS
one-way data-binding
multi-component views?
view-specific logic?
authentication?
available with no auth token
available with an auth token
when auth token exists
when no auth token
when a request fails with 403
app/
actions/
components/
config/
reducers/
requests/
stores/
views/
index.js
...are regular components
{this.props.children}
componentWillReceiveProps()
just with
and
class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkCredentials(this.props.authToken)
}
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
checkCredentials (authToken) {
(...)
}
render () {
return (
<div>
{this.props.children}
</div>
)
}
}
Pseudocode!
function requireAuthentication (Component) {
return class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkCredentials(this.props.authToken)
}
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
checkCredentials (authToken) {
(...)
}
render () {
return (
<div>
{this.props.authToken
? <Component {...this.props} />
: null // You can add a spinner Component here
}
</div>
)
}
}
}
Pseudocode!
checkCredentials (authToken) {
if (!authToken) {
const storageToken = window.localStorage.getItem('authToken')
if (!storageToken) {
this.redirectToLogin()
} else {
this.props.setAuthToken(storageToken)
}
}
}
import React from 'react'
import { connect } from 'react-redux'
export default function requireAuthentication (Component) {
class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkCredentials(this.props.authToken)
}
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
checkCredentials (authToken) {
if (!authToken) {
const storageToken = window.localStorage.getItem('authToken')
if (!storageToken) {
this.redirectToLogin()
} else {
this.props.setAuthToken(storageToken)
}
}
}
redirectToLogin () {
this.context.router.push('/login')
}
render () {
return (
<div>
{this.props.authToken
? <Component {...this.props} />
: null // You can add a spinner Component here
}
</div>
)
}
}
(...)
const mapStateToProps = (state) => ({
authToken: state.auth.token
})
const mapDispatchToProps = (dispatch) => {
return {
setAuthToken: (authToken) => {
dispatch(setAuthToken(authToken))
}
}
}
return connect(
mapStateToProps,
mapDispatchToProps
)(AuthenticatedComponent)
}
<Router history={browserHistory}>
<Route path='/login' component={Login} />
<Route path '/' component={authenticatedComponent}>
<IndexRoute component={Dashboard}>
</Route>
</Router>
<Router history={browserHistory}>
<Route path='/login' component={Login} />
<IndexRoute component={requireAuthentication(Dashboard)}>
</Router>
Pseudocode!
app/index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { Router, browserHistory, Route, IndexRoute } from 'react-router'
import { mainStore } from './stores/mainStore'
import requireAuthentication from './views/auth'
import Login from './views/login'
import Dashboard from './views/dashboard'
render(
<Provider store={mainStore}>
<Router history={browserHistory}>
<Route path='/login' component={Login} />
<IndexRoute component={requireAuthentication(Dashboard)}>
</Router>
</Provider>,
document.getElementById('app')
)
const mainReducer = combineReducers({
auth,
locale,
api: combineReducers({
login,
account,
clients
}),
views: combineReducers({
progressBar,
activeWidgets
})
})
app/actions/auth.js
const AUTH_SET_TOKEN = 'AUTH_SET_TOKEN'
const AUTH_DELETE_TOKEN = 'AUTH_DELETE_TOKEN'
export function setAuthToken (token) {
return {
type: AUTH_SET_TOKEN,
token
}
}
export function invalidateAuthToken () {
window.localStorage.removeItem('authToken')
return {
type: AUTH_DELETE_TOKEN
}
}
app/reducers/auth.js
import {
AUTH_SET_TOKEN,
AUTH_DELETE_TOKEN
} from '../actions/auth'
export default function auth (
state = {
token: undefined
}, action
) {
switch (action.type) {
case AUTH_SET_TOKEN:
return Object.assign({}, state, {
token: action.token
})
case AUTH_DELETE_TOKEN:
return Object.assign({}, state, {
token: undefined
})
default:
return state
}
}
app/requests/data.js
import fetch from 'isomorphic-fetch'
import { invalidateAuthToken } from '../actions/auth'
import config from '../config'
export function getAuthenticatedData () {
return fetch(config.request_addres, {
method: 'GET'
}).then((response) => {
if (response.status === 403) {
invalidateAuthToken()
}
return response
})
}
<Router history={browserHistory}>
<Route path='/login' component={Login} />
<Route path='/' component={requireAuthentication(Navbar)}>
<Route path='/dashboard' component={Dashboard} />
<Route path='/clients' component={Clients}>
<IndexRoute component={NewClient}>
</Route>
</Route>
</Router>
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
Pseudocode!
checkCredentials (authToken) {
const storageToken = window.localStorage.getItem('authToken')
if (authToken || storageToken) {
this.props.setAuthToken(authToken || storageToken)
this.redirectToDashboard()
}
}
import React from 'react'
import { connect } from 'react-redux'
export default function requireUnauthenticated (Component) {
class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkCredentials(this.props.authToken)
}
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
checkCredentials (authToken) {
const storageToken = window.localStorage.getItem('authToken')
if (authToken || storageToken) {
this.props.setAuthToken(authToken || storageToken)
this.redirectToDashboard()
}
}
redirectToDashboard () {
this.context.router.push('/dashboard')
}
render () {
return (
<div>
{this.props.authToken
? <Component {...this.props} />
: null // You can add a spinner Component here
}
</div>
)
}
}
(...)
const mapStateToProps = (state) => ({
authToken: state.auth.token
})
const mapDispatchToProps = (dispatch) => {
return {
setAuthToken: (authToken) => {
dispatch(setAuthToken(authToken))
}
}
}
return connect(
mapStateToProps,
mapDispatchToProps
)(requireUnauthenticated)
}
user permissions to component groups
auto-redirecting on 'global' level
keep auth data or user preferences
set and change locale
views can be implemented as components
with Redux componentWillReceiveProps works wonders
use single Redux store per App
it's handy to create higher order components