Skip to content

Commit

Permalink
Add redux and redux-saga with documentation and an example
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthieu Napoli committed Aug 1, 2018
1 parent 89e61ba commit 11127a9
Show file tree
Hide file tree
Showing 16 changed files with 911 additions and 27 deletions.
25 changes: 23 additions & 2 deletions App/App.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import React, { Component } from 'react'
import { HomeScreen } from 'App/Containers/HomeScreen'
import HomeScreen from 'App/Containers/HomeScreen'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/lib/integration/react'
import createStore from 'App/Stores'

const { store, persistor } = createStore()

export default class App extends Component {
render() {
return <HomeScreen />
return (
/**
* @see https://github.com/reduxjs/react-redux/blob/master/docs/api.md#provider-store
*/
<Provider store={store}>
{/**
* PersistGate delays the rendering of the app's UI until the persisted state has been retrieved
* and saved to redux.
* The `loading` prop can be `null` or any react instance to show during loading (e.g. a splash screen),
* for example `loading={<SplashScreen />}`.
* @see https://github.com/rt2zz/redux-persist/blob/master/docs/PersistGate.md
*/}
<PersistGate loading={null} persistor={persistor}>
<HomeScreen />
</PersistGate>
</Provider>
)
}
}
29 changes: 28 additions & 1 deletion App/Containers/HomeScreen.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import React from 'react'
import { Platform, StyleSheet, Text, View } from 'react-native'
import { connect } from 'react-redux'
import { PropTypes } from 'prop-types'
import ExampleActions from 'App/Stores/Example/Actions'

const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
android: 'Double tap R on your keyboard to reload,\n' + 'Shake or press menu button for dev menu',
})

export class HomeScreen extends React.Component {
class HomeScreen extends React.Component {
componentDidMount() {
this.props.refreshTemperature()
}

render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>To get started, edit App.js</Text>
<Text style={styles.instructions}>{instructions}</Text>
<Text style={styles.instructions}>
The weather temperature is: {this.props.temperature}
</Text>
</View>
)
}
Expand All @@ -34,3 +44,20 @@ const styles = StyleSheet.create({
marginBottom: 5,
},
})

HomeScreen.propsTypes = {
temperature: PropTypes.number,
}

const mapStateToProps = (state) => ({
temperature: state.example.get('weatherTemperature'),
})

const mapDispatchToProps = (dispatch) => ({
refreshTemperature: () => dispatch(ExampleActions.requestWeatherTemperature()),
})

export default connect(
mapStateToProps,
mapDispatchToProps
)(HomeScreen)
16 changes: 16 additions & 0 deletions App/Sagas/ExampleSaga.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { put } from 'redux-saga/effects'
import ExampleActions from 'App/Stores/Example/Actions'

/**
* A saga can contain multiple functions.
*
* This example saga contains only one to fetch the weather temperature.
*/
export function* fetchWeatherTemperature() {
// Fetch the temperature from an API (in this example we hardcode 24 degrees)
const temperature = 24

// Dispatch a redux action using `put()`
// @see https://redux-saga.js.org/docs/basics/DispatchingActions.html
yield put(ExampleActions.updateWeatherTemperature(temperature))
}
3 changes: 3 additions & 0 deletions App/Sagas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[Redux Saga](https://redux-saga.js.org/) is a library that makes application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage.

This directory contains the sagas of the application. Sagas will for example connect to an API to fetch data, perform actions, etc.
13 changes: 13 additions & 0 deletions App/Sagas/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { takeLatest } from 'redux-saga/effects'
import { ExampleTypes } from 'App/Stores/Example/Actions'
import { fetchWeatherTemperature } from './ExampleSaga'

export default function* root() {
yield [
/**
* @see https://redux-saga.js.org/docs/basics/UsingSagaHelpers.html
*/
// Call `fetchWeatherTemperature()` when a `REQUEST_WEATHER_TEMPERATURE` action is triggered
takeLatest(ExampleTypes.REQUEST_WEATHER_TEMPERATURE, fetchWeatherTemperature),
]
}
47 changes: 47 additions & 0 deletions App/Stores/CreateStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { applyMiddleware, compose, createStore } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { persistReducer, persistStore } from 'redux-persist'
import immutableTransform from 'redux-persist-transform-immutable'

// Defaults to localStorage for web and AsyncStorage for react-native
import storage from 'redux-persist/lib/storage'

const persistConfig = {
transforms: [
/**
* This is necessary to support immutable reducers.
* @see https://github.com/rt2zz/redux-persist-transform-immutable
*/
immutableTransform(),
],
key: 'root',
storage: storage,
/**
* Blacklist state that we do not need/want to persist
*/
blacklist: [
// 'auth',
],
}

export default (rootReducer, rootSaga) => {
const middleware = []
const enhancers = []

// Connect the sagas to the redux store
const sagaMiddleware = createSagaMiddleware()
middleware.push(sagaMiddleware)

enhancers.push(applyMiddleware(...middleware))

// Redux persist
const persistedReducer = persistReducer(persistConfig, rootReducer)

const store = createStore(persistedReducer, compose(...enhancers))
const persistor = persistStore(store)

// Kick off the root saga
sagaMiddleware.run(rootSaga)

return { store, persistor }
}
29 changes: 29 additions & 0 deletions App/Stores/Example/Actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createActions } from 'reduxsauce'

/**
* We use reduxsauce's `createActions()` helper to easily create redux actions.
*
* Keys are action names and values are the list of parameters for the given action.
*
* Action names are turned to SNAKE_CASE into the `Types` variable. This can be used
* to listen to actions:
*
* - to trigger reducers to update the state, for example in App/Stores/Example/Reducers.js
* - to trigger sagas, for example in App/Sagas/index.js
*
* Actions can be dispatched:
*
* - in React components using `dispatch(...)`, for example in App/App.js
* - in sagas using `yield put(...)`, for example in App/Sagas/ExampleSaga.js
*
* @see https://github.com/infinitered/reduxsauce#createactions
*/
const { Types, Creators } = createActions({
// Request to fetch the current weather temperature
requestWeatherTemperature: null,
// Update the current weather temperature (after it was fetched)
updateWeatherTemperature: ['temperature'],
})

export const ExampleTypes = Types
export default Creators
8 changes: 8 additions & 0 deletions App/Stores/Example/InitialState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Map } from 'immutable'

/**
* The initial values for the redux state.
*/
export const INITIAL_STATE = Map({
weatherTemperature: null,
})
3 changes: 3 additions & 0 deletions App/Stores/Example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This directory contains an example of a store and all its files.

You can safely delete it after removing it from [App/Stores/index.js](../index.js).
18 changes: 18 additions & 0 deletions App/Stores/Example/Reducers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { INITIAL_STATE } from './InitialState'
import { createReducer } from 'reduxsauce'
import { ExampleTypes } from './Actions'

/**
* Example of a reducer that updates the `temperature` property.
*/
export const updateWeatherTemperature = (state, { temperature }) =>
state.merge({
weatherTemperature: temperature,
})

/**
* @see https://github.com/infinitered/reduxsauce#createreducer
*/
export const reducer = createReducer(INITIAL_STATE, {
[ExampleTypes.UPDATE_WEATHER_TEMPERATURE]: updateWeatherTemperature,
})
17 changes: 17 additions & 0 deletions App/Stores/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[Redux](https://redux.js.org/) is a library that helps managing the application state.

This directory contains redux [actions, reducers and stores](https://redux.js.org/basics).

Here is an example of how to organize those concepts by theme:

```bash
User/
Actions.js # Contains the redux actions for user management
InitialState.js # Contains the initial values for the state related to the user
Reducers.js # Contains the redux reducers for user management
Team/
Actions.js
InitialState.js
Reducers.js
...
```
16 changes: 16 additions & 0 deletions App/Stores/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { combineReducers } from 'redux'
import configureStore from './CreateStore'
import rootSaga from 'App/Sagas'
import { reducer as ExampleReducer } from './Example/Reducers'

export default () => {
const rootReducer = combineReducers({
/**
* Register your reducers here.
* @see https://redux.js.org/api-reference/combinereducers
*/
example: ExampleReducer,
})

return configureStore(rootReducer, rootSaga)
}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This boilerplate contains:

- [`App/Components`](App/Components): presentational components
- [`App/Containers`](App/Containers): container components
- [`App/Stores`](App/Stores): redux [actions, reducers and stores](https://redux.js.org/basics)
- [`App/Sagas`](App/Sagas): redux sagas

For more information on each directory, click the link and read the directory's README.

Expand Down
2 changes: 1 addition & 1 deletion app.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"name": "Boilerplate",
"displayName": "Boilerplate"
}
}
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,17 @@
"rename": "react-native-rename"
},
"dependencies": {
"immutable": "^3.8.2",
"prop-types": "^15.6.1",
"react": "16.3.1",
"react-native": "0.55.4"
"react-native": "0.55.4",
"react-native-sensitive-info": "^5.1.0",
"react-redux": "^5.0.7",
"redux": "^3.7.2",
"redux-persist": "^5.9.1",
"redux-persist-transform-immutable": "^5.0.0",
"redux-saga": "^0.16.0",
"reduxsauce": "^0.7.0"
},
"devDependencies": {
"babel-jest": "23.2.0",
Expand Down
Loading

0 comments on commit 11127a9

Please sign in to comment.