Fetching data from an API using Redux Saga
saga pattern
flow -->
Fetching and reducing return values in Saga is a two fold process: The yield call executes the API call. And yield put takes the fetched data and sends it to your reducer. The reducer then updates the state.
redux saga helps us manage the asynchronous flow of our application
really efficiently, one more things
- It's simpler to test and better at handling failures
- Generators is similar to function but we need an asterisk after the function
- Generators returns multiple value.
- Done value significant, finished its execution
Step -5 (Customize return)
Required some library
redux
react-redux
redux-saga
yarn add redux react-redux redux-saga
This is my folder structure as below;
...............................................................................................................................................................................................................
src/constants/Type.js
Type.js
export const REQUEST_API_DATA = "REQUEST_API_DATA";
export const RECEIVE_API_DATA = "RECEIVE_API_DATA";
......................................................................................................................................................................................................................
src/actions/actionCreator.js
actionCreator.js
import { REQUEST_API_DATA, RECEIVE_API_DATA } from '../constants/Type'
export const requestApiData = () => {
return {
type: REQUEST_API_DATA
}
}
export const receiveApiData = data => {
return {
type: RECEIVE_API_DATA,
data
}
}
..............................................................................................................................................................................................................................
src/reducers/reducer.js
reducer.js
import { RECEIVE_API_DATA } from '../constants/Type'
const initialState ={
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case RECEIVE_API_DATA:
return action.data
default:
return state;
}
};
export default reducer
...................................................................................................................................................................................................................................
src/reducers/index.js
index.js
import { combineReducers } from 'redux'
import reducer from './reducer';
const rootReducer =combineReducers({
reducer
})
export default rootReducer
............................................................................................................................................................................................................................
src/sagas/dataSaga.js
dataSaga.js
import { call, put, takeLatest } from 'redux-saga/effects'
import { REQUEST_API_DATA } from '../constants/Type'
import { receiveApiData } from '../actions/actionCreator'
import { fetchApi } from '../api/api'
/**worker saga */
function* workerSaga(action) {
try {
const response = yield call(fetchApi)
yield put(receiveApiData(response.data))
}
catch (e) {
console.log(e)
}
}
/** watcher saga */
function* watcherSaga() {
yield takeLatest(REQUEST_API_DATA, workerSaga)
}
export default watcherSaga
........................................................................................................................................................................................................................
src/store/store.js
store.js
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "../reducers/index";
import watcherSaga from "../sagas/dataSaga";
const sagaMiddleware = createSagaMiddleware()
const configureStore=()=>{
const store= createStore(
rootReducer, applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(watcherSaga)
return store
}
export default configureStore
.....................................................................................................................................................................................................................
src/components/Home.js
Home.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { requestApiData } from '../actions/actionCreator';
class Home extends Component {
componentDidMount() {
this.props.requestApiData();
}
person = (item, index) =>
<div key={index}>
<h1> {item.gender}</h1>
<h1> {item.name.title}</h1>
<h1> {item.name.first}</h1>
<h1>{item.name.last}</h1>
<img src={item.picture.medium} />
</div>
render() {
const { results = [] } = this.props.hello;
// return results.length ? <h1></h1> : <h1></h1>
return results.length
? <h1>
{results.map(this.person)}
</h1>
: <h1>loading...</h1>;
}
}
/**
render() {
const { results=[]}=this.props.data
return (
< div >
<h1>{results.map(this.person)}</h1>
</div >
*/
const mapStateToProps = state => {
return {
hello: state.reducer
}
}
const mapDispatchToProps = dispatch => {
return {
requestApiData: () => dispatch(requestApiData())
}
}
const Home1 = connect(mapStateToProps, mapDispatchToProps)(Home)
export default Home1
....................................................................................................................................................................................................................
/
src/App.js
App.js
import React, { Component, } from 'react'
import { Provider} from 'react-redux'
import configureStore from './store/store'
import Home1 from './components/Home'
const store = configureStore()
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Home1 />
</Provider>
)
}
}
...................................................................................................................................................................................................................................
src/api/api.js
api.js
export const fetchApi = async () => {
try {
const response = await fetch("https://randomuser.me/api");
const data = await response.json();
return data;
} catch (e) {
console.log(e);
}
};
bcoz redux-saga, Each yield in a generator basically represents an asynchronous step in a more synchronous/sequential process — somewhat like await in an async function.
const fetch=()=>{
return axios({
method:'get',
url:'https://randomuser.me/api'
})
}
...............................................................................................................................................................................................................................
.........................................................................................................................................................................................................................
Redux Saga | Redux
export const FETCH_DATA = 'FETCH_DATA';
export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
..............................................................................................................................................................................................................................
saga.js
import { FETCH_DATA, FETCH_DATA_SUCCESS } from '../constants/actionType'
import { call, put, takeEvery } from 'redux-saga/effects'
function* workerSaga(){
const data= yield call(()=>
fetch('https://httpbin.org/get')
.then((res)=> res.json())
)
//yield put({type: FETCH_DATA_SUCCESS, data})
yield put({type: FETCH_DATA_SUCCESS, payload:{data}})
}
function* watcherSaga(){
yield takeEvery(FETCH_DATA, workerSaga)
}
export default watcherSaga
....................................................................................................................................................................................................................................
reducer.js
import { FETCH_DATA, FETCH_DATA_SUCCESS } from '../constants/actionType'
import { combineReducers } from 'redux'
const initialState = {
data: {}
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_DATA_SUCCESS:
return {
...state,
//data:action.data
data: action.payload.data
}
default:
return state
}
}
const AllReducer = combineReducers({
nav:reducer
})
export default AllReducer
..................................................................................................................................................................................................................................
Home.js
import React, { Component, useEffect } from 'react'
import { connect } from 'react-redux'
import { FETCH_DATA } from '../constants/actionType'
class Home extends Component {
componentDidMount() {
this.props.fetchData()
}
render() {
const {results}=this.props
return (
<div>{JSON.stringify(results)}</div>
)
}
}
OR
/**
const Home = ({results, fetchData}) => {
useEffect(() => {
fetchData();
},[]);
return (
<div>
{JSON.stringify(results)}
</div>
);
};
*/
const mapStateToProps = (state) => {
return {
results: state.nav.data
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch({ type: FETCH_DATA })
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home)
...........................................................................................................................................................................................................................................
App.js
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga';
import Home from './components/Home'
import watcherSaga from './sagas/saga'
import AllReducer from './reducers/reducer'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(AllReducer, applyMiddleware(sagaMiddleware))
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Home/>
</Provider>
)
}
}
sagaMiddleware.run(watcherSaga)
.................................................................DONE.................................................................................
..................................................................................................................................................................................................................................
import React, { Component } from 'react';
import { View, Text } from 'react-native'
class App extends Component {
state = {
todos: {}
}
componentDidMount = () => {
// fetch('http://jsonplaceholder.typicode.com/todos/1')
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then((data) => {
console.log(data)
this.setState({ todos: data })
console.log(this.state.todos)
})
.catch(console.log)
}
render() {
const users = this.state.todos
return (
<View>
{users.length ? (users.map((item, index) => <Text>{item.name}</Text>)) : (<Text>Loading...</Text>)}
</View>
)
}
}
export default App;
..........................................................................................................................................................................................................................
import React, { Component } from 'react'
import { View, Text, ActivityIndicator } from 'react-native'
class App extends Component {
constructor(props) {
super(props)
this.state = {
todos: []
}
}
componentDidMount() {
fetch(`https://jsonplaceholder.typicode.com/posts/?id=${1}`,{
method:'GET'
})
.then(res => res.json())
.then((data) => {
this.setState({ todos: data })
console.log(this.state.todos)
})
.catch(console.log)
}
render() {
return (
<View>
{this.state.todos.map((item, index)=>{
return(
<View>
<Text>{item.title}</Text>
</View>
)
})}
</View>
)
}
}
export default App
import React, { Component } from 'react'
import { View, Text, ActivityIndicator } from 'react-native'
class App extends Component {
constructor(props) {
super(props)
this.state = {
todos: []
}
}
componentDidMount() {
fetch(`https://jsonplaceholder.typicode.com/posts/?id=${1}`,{
method:'GET'
})
.then(res => res.json())
.then((data) => {
this.setState({ todos: data })
console.log(this.state.todos)
})
.catch(console.log)
}
render() {
return (
<View>
{this.state.todos.map((item, index)=>{
return(
<View>
<Text>{item.title}</Text>
</View>
)
})}
</View>
)
}
}
export default App
..............................................................................................................................................................................................................................
import React, { Component } from 'react'
import { View, Text, ActivityIndicator } from 'react-native'
class App extends Component{
constructor(props){
super(props)
this.state={
users:[]
}
}
componentDidMount(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(res=>res.json())
.then(responseJson=>{
console.log(responseJson)
this.setState({
users:responseJson
})
})
}
render(){
const user = this.state.users
return(
<View>
{user.length ? (user.map((item, index)=><View key={index.toString()}><Text>{item.name}</Text></View>)):(<ActivityIndicator size='large' color='red'></ActivityIndicator>)}
</View>
)
}
}
export default App
Happy coding :)
..........................................................................................................................................................................................................................
redux-saga with functional component
src\constants\Type.js
export const REQUEST_API_DATA = 'REQUEST_API_DATA'
export const RECEIVE_API_DATA = 'RECEIVE_API_DATA'
src\actions\actionCreators.js
export const requestApiData = () => {
return {
type: Type.REQUEST_API_DATA
}
}
export const receieveApiData = data => {
return {
type: Type.RECEIVE_API_DATA,
payload: data
}
}
src\reducers\nameReducer.js
import { RECEIVE_API_DATA } from '../constants/Type'
const initialState = {
data: []
}
const counterReducer = (state = initialState, { type, payload }) => {
console.log(type)
console.log(payload)
switch (type) {
case RECEIVE_API_DATA:
return {
...state,
data: payload
}
default:
return state
}
}
export default counterReducer
src\sagas\counterSaga.js
import { call, put, takeEvery } from 'redux-saga/effects'
import { receieveApiData, } from '../actions/actionCreators'
import { REQUEST_API_DATA } from '../constants/Type'
import axios from 'axios'
const fetchApi = async () => {
try {
const response = await fetch('https://randomuser.me/api')
console.log(response)
const data = await response.json();
return data
} catch (e) {
console.log(e)
}
}
// const fetchApi = () => {
// return axios({
// method: "GET",
// url: 'https://randomuser.me/api'
// }).then(res => res.data)
// .catch(e => {
// console.log(e)
// }
// )
// }
function* workerSaga() {
try {
const dataasas = yield call(fetchApi)
// const res = dataasas.results
yield put(receieveApiData(dataasas.results))
} catch (e) {
console.log(e)
}
}
function* watcherSaga() {
yield takeEvery(REQUEST_API_DATA, workerSaga)
}
export default watcherSaga
App.js
import React from 'react'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import Home from '../src/components/Home'
import counterReducer from '../src/reducers/nameReducer'
import watcherSaga from '../src/sagas/counterSaga'
const sagaMiddleware = createSagaMiddleware()
const middleware = [sagaMiddleware]
const store = createStore(counterReducer, applyMiddleware(...middleware))
const App = () => {
return (
<Provider store={store}>
<Home />
</Provider>
)
}
sagaMiddleware.run(watcherSaga)
export default App
Happy coding :)
..........................................................................................................................................................................................................................
Store.js
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import {rootReducer, rootSaga } from './modules';
const sagaMiddleware = createSagaMiddleware();
var middleware = applyMiddleware(sagaMiddleware);
var composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default createStore( rootReducer, {}, composeEnhancers(
middleware
));
sagaMiddleware.run(rootSaga);
Happy coding :)
..........................................................................................................................................................................................................................
Example -
export const LOAD_DATA = 'LOAD_DATA'
export const LOAD_DATA_SUCCESS = 'LOAD_DATA_SUCCESS'
......................
import * as Types from './actionTypes'
export const loadData = () => {
return {
type: Types.LOAD_DATA
}}
export const fetchData = data => {
return {
type: Types.LOAD_DATA_SUCCESS,
payload: data
}}
.............................
import * as Types from './actionTypes'
const initialState = {
data: []
}
const dataReducer = (state = initialState, action) => {
switch (action.type) {
case Types.LOAD_DATA_SUCCESS:
return {
...state,
data: action.payload
}
case Types.LOAD_DATA:
return {
...state
}
default:
return state
}}
export default dataReducer
.....................
import * as Types from './actionTypes'
import { call, put, takeEvery } from 'redux-saga/effects'
import { fetchData } from './actions'
import axios from 'axios'
const fetchApi = () => {
return axios({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/users'
}).then(response => response.data)
.catch(error => console.log(error))
}
function* watcherSaga() {
const response = yield call(fetchApi)
console.log(response)
yield put(fetchData(response))
}
function* rootSaga() {
yield takeEvery(Types.LOAD_DATA, watcherSaga)
}
export default rootSaga
.................
App.js
import React from 'react'
import { createStore , compose} from 'redux'
import { Provider } from 'react-redux'
import createSagaMiddleware from 'redux-saga'
import dataReducer from './redux/reducer'
import Home from './redux/Home'
import rootSaga from './redux/saga'
import { applyMiddleware } from 'redux'
const sagaMiddleware = createSagaMiddleware()
const middleware = [sagaMiddleware]
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(dataReducer, composeEnhancers(applyMiddleware(...middleware)) )
const App = () => {
return (
<Provider store={store}>
<Home/>
</Provider>
)
}
sagaMiddleware.run(rootSaga)
export default App
Happy coding :)
..........................................................................................................................................................................................................................
App.js
// Imports: Dependencies
import React from 'react';
import { Provider } from 'react-redux';
// Imports: Screens
import Counter from './screens/Counter';
// Imports: Redux Store
import { store } from './store/store';
// React Native App
export default function App() {
return (
// Redux: Global Store
<Provider store={store}>
<Counter />
</Provider>
);
}
.............
Counter.js
// Imports: Dependencies
import React, { Component } from 'react';import { Button, Dimensions, SafeAreaView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { connect } from 'react-redux';
// Screen Dimensions
const { height, width } = Dimensions.get('window');
// Screen: Counter
class Counter extends React.Component {
render() {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.counterTitle}>Counter</Text>
<View style={styles.counterContainer}>
<TouchableOpacity onPress={this.props.reduxIncreaseCounter}>
<Text style={styles.buttonText}>+</Text
</TouchableOpacity>
<Text style={styles.counterText}>{this.props.counter}</Text>
<TouchableOpacity onPress={this.props.reduxDecreaseCounter}>
<Text style={styles.buttonText}>-</Text
</TouchableOpacity>
</View>
</SafeAreaView>
)
}
}
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
counterContainer: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
counterTitle: {
fontFamily: 'System',
fontSize: 32,
fontWeight: '700',
color: '#000',
},
counterText: {
fontFamily: 'System',
fontSize: 36,
fontWeight: '400',
color: '#000',
},
buttonText: {
fontFamily: 'System',
fontSize: 50,
fontWeight: '300',
color: '#007AFF',
marginLeft: 40,
marginRight: 40,
},
});
// Map State To Props (Redux Store Passes State To Component)
const mapStateToProps = (state) => {
console.log('State:');
console.log(state);
// Redux Store --> Component
return {
counter: state.counter.counter,
};
};
// Map Dispatch To Props (Dispatch Actions To Reducers. Reducers Then Modify The Data And Assign It To Your Props)
const mapDispatchToProps = (dispatch) => {
// Action
return {
// Increase Counter
reduxIncreaseCounter: () => dispatch({
type: 'INCREASE_COUNTER',
value: 1,
}),
// Decrease Counter
reduxDecreaseCounter: () => dispatch({
type: 'DECREASE_COUNTER',
value: 1,
}),
};
};
// Exports
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
store.js
// Imports: Dependencies
import { createStore, applyMiddleware } from 'redux';
import { createLogger } from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
// Imports: Redux Root Reducer
import rootReducer from '../reducers/index';
// Imports: Redux Root Saga
import { rootSaga } from '../sagas/index';
// Middleware: Redux Saga
const sagaMiddleware = createSagaMiddleware();
// Redux: Store
const store = createStore(
rootReducer,
applyMiddleware(
sagaMiddleware,
createLogger(),
),
);
// Middleware: Redux Saga
sagaMiddleware.run(rootSaga);
// Exports
export {
store,
}
index.js (Root Reducer)
// Imports: Dependencies
import { combineReducers } from 'redux';
// Imports: Reducers
import counterReducer from './counterReducer';
// Redux: Root Reducer
const rootReducer = combineReducers({
counter: counterReducer,
});
// Exports
export default rootReducer;
counterReducer.js
// Initial State
const initialState = {
counter: 0,
};
// Redux: Counter Reducer
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREASE_COUNTER_ASYNC': {
return {
...state,
counter: state.counter + action.value,
};
}
case 'DECREASE_COUNTER': {
return {
...state,
counter: state.counter - action.value,
};
}
default: {
return state;
}
}
};
// Exports
export default counterReducer;
index.js (Root Saga)
// Imports: Dependencies
import { all, fork} from 'redux-saga/effects';
// Imports: Redux Sagas
import { watchIncreaseCounter, watchDecreaseCounter } from './counterSaga';
// Redux Saga: Root Saga
export function* rootSaga () {
yield all([
fork(watchIncreaseCounter),
fork(watchDecreaseCounter),
]);
};
counterSaga.js
// Imports: Dependencies
import { delay, takeEvery, takeLatest, put } from 'redux-saga/effects';
// Worker: Increase Counter Async (Delayed By 4 Seconds)
function* increaseCounterAsync() {
try {
// Delay 4 Seconds
yield delay(4000);
// Dispatch Action To Redux Store
yield put({
type: 'INCREASE_COUNTER_ASYNC',
value: 1,
});
}
catch (error) {
console.log(error);
}
};
// Watcher: Increase Counter Async
export function* watchIncreaseCounter() {
// Take Last Action Only
yield takeLatest('INCREASE_COUNTER', increaseCounterAsync);
};
// Worker: Decrease Counter
function* decreaseCounter() {
try {
// Dispatch Action To Redux Store
yield put({
type: 'DECREASE_COUNTER_ASYNC',
value: 1,
});
}
catch (error) {
console.log(error);
}
};
// Watcher: Decrease Counter
export function* watchDecreaseCounter() {
// Take Last Action Only
yield takeLatest('DECREASE_COUNTER', decreaseCounter);
};
Thanks for sharing this blog.This article gives lot of information.
ReplyDeleteMern stack online training
Mern stack training in hyderabad