Thursday, May 16, 2019

Saga

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.


generator | What are Generators?
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 -1 
Step -2

Step -3(Iterator)
Step -4 (Customize return)
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={
      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);
};














1 comment: