Skip to content

Commit 739d3cd

Browse files
author
Matthieu Napoli
committed
Refactor the redux/redux-saga example to include error handling and better naming
1 parent b700f91 commit 739d3cd

File tree

10 files changed

+97
-19
lines changed

10 files changed

+97
-19
lines changed

App/Containers/HomeScreen.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const instructions = Platform.select({
1111

1212
class HomeScreen extends React.Component {
1313
componentDidMount() {
14-
this.props.refreshTemperature()
14+
this.props.fetchTemperature()
1515
}
1616

1717
render() {
@@ -23,6 +23,7 @@ class HomeScreen extends React.Component {
2323
<Text style={styles.instructions}>
2424
The weather temperature is: {this.props.temperature}
2525
</Text>
26+
<Text style={styles.instructions}>{this.props.errorMessage}</Text>
2627
</View>
2728
)
2829
}
@@ -47,14 +48,16 @@ const styles = StyleSheet.create({
4748

4849
HomeScreen.propsTypes = {
4950
temperature: PropTypes.number,
51+
errorMessage: PropTypes.string,
5052
}
5153

5254
const mapStateToProps = (state) => ({
53-
temperature: state.example.get('weatherTemperature'),
55+
temperature: state.example.get('temperature'),
56+
errorMessage: state.example.get('errorMessage'),
5457
})
5558

5659
const mapDispatchToProps = (dispatch) => ({
57-
refreshTemperature: () => dispatch(ExampleActions.requestWeatherTemperature()),
60+
fetchTemperature: () => dispatch(ExampleActions.fetchTemperature()),
5861
})
5962

6063
export default connect(

App/Sagas/ExampleSaga.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
import { put } from 'redux-saga/effects'
1+
import { put, call } from 'redux-saga/effects'
22
import ExampleActions from 'App/Stores/Example/Actions'
3+
import { WeatherService } from 'App/Service/WeatherService'
34

45
/**
56
* A saga can contain multiple functions.
67
*
78
* This example saga contains only one to fetch the weather temperature.
89
*/
9-
export function* fetchWeatherTemperature() {
10-
// Fetch the temperature from an API (in this example we hardcode 24 degrees)
11-
const temperature = 24
10+
export function* fetchTemperature() {
11+
// Fetch the temperature from an API
12+
const temperature = yield call(WeatherService.fetchTemperature)
1213

1314
// Dispatch a redux action using `put()`
1415
// @see https://redux-saga.js.org/docs/basics/DispatchingActions.html
15-
yield put(ExampleActions.updateWeatherTemperature(temperature))
16+
if (temperature) {
17+
yield put(ExampleActions.fetchTemperatureSuccess(temperature))
18+
} else {
19+
yield put(
20+
ExampleActions.fetchTemperatureFailure('There was an error while fetching the temperature.')
21+
)
22+
}
1623
}

App/Sagas/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { takeLatest } from 'redux-saga/effects'
22
import { ExampleTypes } from 'App/Stores/Example/Actions'
3-
import { fetchWeatherTemperature } from './ExampleSaga'
3+
import { fetchTemperature } from './ExampleSaga'
44

55
export default function* root() {
66
yield [
77
/**
88
* @see https://redux-saga.js.org/docs/basics/UsingSagaHelpers.html
99
*/
10-
// Call `fetchWeatherTemperature()` when a `REQUEST_WEATHER_TEMPERATURE` action is triggered
11-
takeLatest(ExampleTypes.REQUEST_WEATHER_TEMPERATURE, fetchWeatherTemperature),
10+
// Call `fetchTemperature()` when a `FETCH_TEMPERATURE` action is triggered
11+
takeLatest(ExampleTypes.FETCH_TEMPERATURE, fetchTemperature),
1212
]
1313
}

App/Service/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This directory contains services that are meant to connect the application to other APIs, for example

App/Service/WeatherService.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { create } from 'apisauce'
2+
3+
const weatherApiClient = create({
4+
baseURL: 'https://query.yahooapis.com/v1/public/',
5+
headers: {
6+
Accept: 'application/json',
7+
'Content-Type': 'application/json',
8+
},
9+
timeout: 3000,
10+
})
11+
12+
function fetchTemperature() {
13+
const locationQuery = escape(
14+
"select item.condition.temp from weather.forecast where woeid in (select woeid from geo.places where text='Lyon, Rhone-Alpes, FR') and u='c'"
15+
)
16+
17+
return weatherApiClient.get('yql?q=' + locationQuery + '&format=json').then((response) => {
18+
if (response.ok) {
19+
return response.data.query.results.channel.item.condition.temp
20+
}
21+
22+
return null
23+
})
24+
}
25+
26+
export const WeatherService = {
27+
fetchTemperature,
28+
}

App/Stores/Example/Actions.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ import { createActions } from 'reduxsauce'
1919
* @see https://github.com/infinitered/reduxsauce#createactions
2020
*/
2121
const { Types, Creators } = createActions({
22-
// Request to fetch the current weather temperature
23-
requestWeatherTemperature: null,
24-
// Update the current weather temperature (after it was fetched)
25-
updateWeatherTemperature: ['temperature'],
22+
// Fetch the current weather temperature
23+
fetchTemperature: null,
24+
// The temperature was successfully fetched
25+
fetchTemperatureSuccess: ['temperature'],
26+
// An error occurred
27+
fetchTemperatureFailure: ['errorMessage'],
2628
})
2729

2830
export const ExampleTypes = Types

App/Stores/Example/InitialState.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ import { Map } from 'immutable'
44
* The initial values for the redux state.
55
*/
66
export const INITIAL_STATE = Map({
7-
weatherTemperature: null,
7+
temperature: null,
8+
errorMessage: null,
89
})

App/Stores/Example/Reducers.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,25 @@ import { ExampleTypes } from './Actions'
55
/**
66
* Example of a reducer that updates the `temperature` property.
77
*/
8-
export const updateWeatherTemperature = (state, { temperature }) =>
8+
export const updateTemperature = (state, { temperature }) =>
99
state.merge({
10-
weatherTemperature: temperature,
10+
temperature: temperature,
11+
errorMessage: null,
12+
})
13+
14+
/**
15+
* Example of a reducer that updates the `errorMessage` property.
16+
*/
17+
export const showErrorMessage = (state, { errorMessage }) =>
18+
state.merge({
19+
temperature: '??',
20+
errorMessage: errorMessage,
1121
})
1222

1323
/**
1424
* @see https://github.com/infinitered/reduxsauce#createreducer
1525
*/
1626
export const reducer = createReducer(INITIAL_STATE, {
17-
[ExampleTypes.UPDATE_WEATHER_TEMPERATURE]: updateWeatherTemperature,
27+
[ExampleTypes.FETCH_TEMPERATURE_SUCCESS]: updateTemperature,
28+
[ExampleTypes.FETCH_TEMPERATURE_FAILURE]: showErrorMessage,
1829
})

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"rename": "react-native-rename"
1313
},
1414
"dependencies": {
15+
"apisauce": "^0.15.2",
1516
"immutable": "^3.8.2",
1617
"prop-types": "^15.6.1",
1718
"react": "16.3.1",

yarn.lock

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,13 @@ anymatch@^2.0.0:
563563
micromatch "^3.1.4"
564564
normalize-path "^2.1.1"
565565

566+
apisauce@^0.15.2:
567+
version "0.15.2"
568+
resolved "https://registry.yarnpkg.com/apisauce/-/apisauce-0.15.2.tgz#89c8dfd353bf2f3202a59d7f817eba7ec1c3b0dd"
569+
dependencies:
570+
axios "^0.18.0"
571+
ramda "^0.25.0"
572+
566573
append-transform@^1.0.0:
567574
version "1.0.0"
568575
resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab"
@@ -722,6 +729,13 @@ aws4@^1.6.0:
722729
version "1.7.0"
723730
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289"
724731

732+
axios@^0.18.0:
733+
version "0.18.0"
734+
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
735+
dependencies:
736+
follow-redirects "^1.3.0"
737+
is-buffer "^1.1.5"
738+
725739
babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
726740
version "6.26.0"
727741
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@@ -2496,6 +2510,12 @@ flat-cache@^1.2.1:
24962510
graceful-fs "^4.1.2"
24972511
write "^0.2.1"
24982512

2513+
follow-redirects@^1.3.0:
2514+
version "1.5.2"
2515+
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.2.tgz#5a9d80e0165957e5ef0c1210678fc5c4acb9fb03"
2516+
dependencies:
2517+
debug "^3.1.0"
2518+
24992519
for-in@^1.0.1, for-in@^1.0.2:
25002520
version "1.0.2"
25012521
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -4653,6 +4673,10 @@ ramda@^0.24.1:
46534673
version "0.24.1"
46544674
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857"
46554675

4676+
ramda@^0.25.0:
4677+
version "0.25.0"
4678+
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9"
4679+
46564680
ramdasauce@^2.0.0:
46574681
version "2.1.0"
46584682
resolved "https://registry.yarnpkg.com/ramdasauce/-/ramdasauce-2.1.0.tgz#65ea157a9cfc17841a7dd6499d3f9f421dc2eb36"

0 commit comments

Comments
 (0)