React Redux-Sagas Starter Guide
Tags: reactreduxsagas
Published: 2019-09-27 01:44:59

---
title: React Redux-Sagas Starter Guide
published: true
description: A jump-start guide to setting up Redux-Sagas in your app.
tags: React Redux Sagas
---

Super simple setup for redux-saga in React. It's very basic but works so let's get started.

~ Creating react app

```
npx create-react-app redux_project 
```

~ Installing Redux Saga packages

```
npm i axios react-redux redux redux-saga
```

We gonna delete everything except App.js and index.js. Clean up our App.js so it's just displaying simple text.

```
import React, { Component } from 'react';

class App extends Component {    
  
  render() {
    return(
      <div>
        Hello World
      </div>
    )
  }
}
export default App;
```

At this point we gonna need to do a little setup. We need to create 4 folders inside of our react-app. Under the src folder. Create the folder [actions] [api] [reducers] [sagas]. Each of these folders will contain code that will be connected to our react-app.

Let's create our actions in our actions folder called data.js

```
// actions types is object with the
// key: GET_DATA_REQUEST -> value: describes the action
export const Types = {
    GET_DATA_REQUEST: 'get_data_request',
}

// function that returns an object literal 
export const getDataRequest = () => ({
    type: Types.GET_DATA_REQUEST
})

```

We are going to create our reducers. Go into our [reducers] folder and create these two files. data.js and index.js. So what is a redux? A redux a store a place that contains the state of our application. It's like state in react but with a lot more complexity and functions. But before we can create our Redux we have to first create Types and actions.  

inside the [reducers] data.js

```
import {Types} from '../actions/data';

// create initial state for reducers
const INIT_STATE = {
    test: "Hello world!"
}

// reducer function to transform state
export default function data(state = INIT_STATE, action) {
    return state
}
```
Next we have to fill out our index.js inside of our [reducers]

```
import {combineReducers} from 'redux';
import DataReducer from './data';

export default combineReducers({
    data: DataReducer
})
```

Okay even tho we see the code that says api call and payload we are not going to worry about that right now cause the Redux store doesn't have anything directly to do with api's it just stores data from api's. Let's connect our Redux to our App, at this point we are only worry about getting that INIT_STATE to render on our page. Go to our index.js page in [src] to connect our Redux.

```
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';

import reducers from './reducers';
import { Provider } from 'react-redux';
import { createStore } from 'redux';

const store = createStore(reducers);

// pass our store into our Provider
ReactDOM.render(
    // Provider pass data into our App
    <Provider store={store}>
        <App />
    </Provider>
, document.getElementById('root'));

```

We have wrapped our application with Redux by use <Provider> and passing our Redux state as `store`. App lives under our Provider and is now ready to be connected to Redux. Open App.js.

```
import React, { Component } from 'react';
import { connect } from 'react-redux';

class App extends Component {    
  
  render() {
    return(
      <div>
        {this.props.data.test}
      </div>
    )
  }
}

// redux providing state takeover
const mapStateToProps = (state) => {
    console.log("App State ->", state);
    return {
      data: state.data
    }
}
export default connect(mapStateToProps, { })(App)
```

Redux Provider takes over our state and no longer allow us to change state, but now passes the data from Redux store into our application by using `props` and because we have access to props we can render that to our page. It now say "Hello Redux!".

Go inside the [actions] folder and create a file called data.js inside of the actions folder. Okay the pattern behind Redux is that try to think of the redux is a state with a giant (switch statement) filters out stuff and make sure that when it gets data in the form of an action "dispatch" it takes that specific "dispatch" and changes your application state is a specific way and gives your application a new state. So actions are special functions that creates an object to give to our Redux and by reading that object the Redux will know how to update our state.

```
// actions types is object with the
// key: GET_DATA_REQUEST -> value: describes the action
export const Types = {
    GET_DATA_REQUEST: 'get_data_request',
    GET_DATA_SUCCESS: 'get_data_success',
}

// function that returns an object literal 
export const getDataRequest = () => ({
    type: Types.GET_DATA_REQUEST
})
// key: payload will contain result of the api call
export const getDataSuccess = ({data}) => ({
    type: Types.GET_DATA_SUCCESS,
    payload: { data }
})
```

Okay now comes the tricky part...
THe way we are going to use Sagas with Redux is our application is with generators. So generators are functions that contains state and is very similar to our closure in JS. Where a function when run creates it's private variables so that it could be used later and can't be accessed by the scope outside it's own closure. Generator takes that idea one step further and is designed in a way that when you write the generator code you decide how it will return data by using the `yield` key word it returns some data but maintain it's state until a condition is met.

What we are going to do is create two generator functions the `fetchData` generator has the job of making the api calls in the future, which if successful we will then pass that data from the api response to our `watchGetchData` generator and if `fetchData` gives us the go ahead we will then pass that data to our Redux.

```
import { takeEvery, fork, put } from 'redux-saga/effects';
import * as actions from '../actions/data';

// create a generator function
function* fetchData() {
    try {
        yield put(actions.getDataSuccess( {data: "Saga Data!"} ))
    }catch(e) {
        console.log(e);
    }
}
function* watchFetchData() {
    // create watcher of fetchData function
    yield takeEvery(actions.Types.GET_DATA_REQUEST, fetchData);
}

const DataSagas = [
    fork(watchFetchData)
];

export default DataSagas;
```

make sure we fill in the code for index.js for [sagas] just like [reducers]

```
import { all } from 'redux-saga/effects';
import DataSagas from './data';

// combine all sagas
export default function* rootSaga() {
    yield all([
        ...DataSagas,
    ]);
}
```

change our [reducers] data.js code to return the new data being passed into redux from our sagas.

```
// reducer function to transform state
export default function data(state = INIT_STATE, action) {
    switch(action.type) {
        case Types.GET_DATA_SUCCESS: {
            console.log("redux -> ", action.payload.data)    
            return {
                test: action.payload.data
            }
        }
        default: return state;
    }
}
```

With our redux connected to our sagas we can now update our App.js code and see if our Sagas are working. We start the application by calling the getDataRequest action then our Sagas is called giving us the data back and passing that data back into our application. 

```
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getDataRequest } from './actions/data'; 

class App extends Component {    
  componentDidMount() {
    this.props.getDataRequest();
  }

  render() {
    return(
      <div>
        <h1>
          {this.props.data}
        </h1>
      </div>
    )
  }
}

// redux providing state takeover
const mapStateToProps = (state) => {
    console.log("App State ->", state);
    return {
      data: state.data.test
    }
}
export default connect(mapStateToProps, { getDataRequest })(App)

```

The last step is to have our application make api calls from our saga generators.
Inside the [api] folder we are going to create data.js. All we are doing is making a get request to our test dummy db.

```
import axios from 'axios';

// data api calls
export const getData = () => {
    return axios.get('http://localhost:4000/comments')
}
```

Let's add in the api call into our saga

```
import { takeEvery, call, fork, put } from 'redux-saga/effects';
import * as actions from '../actions/data';
import * as api from '../api/data';

// create a generator function
function* fetchData() {
    // try to make the api call
    try {
        // yield the api responsse into data
        const data = yield call(api.getData);
        yield put(actions.getDataSuccess( {data: data.data} ))
    }catch(e) {
        console.log(e);
    }
}
function* watchFetchData() {
    // create watcher of fetchData function
    yield takeEvery(actions.Types.GET_DATA_REQUEST, fetchData);
}

const DataSagas = [
    fork(watchFetchData)
];

export default DataSagas;
```

And also update our redux to return the data from our api call

```
import {Types} from '../actions/data';

// create initial state for reducers
const INIT_STATE = {
    test: "Hello Redux!"
}

// reducer function to transform state
export default function data(state = INIT_STATE, action) {
    switch(action.type) {
        case Types.GET_DATA_SUCCESS: {
            console.log("redux -> ", action.payload.data.test)    
            return {
                test: action.payload.data.test
            }
        }
        default: return state;
    }
}

```

Okay we come full circle, our application call an action and it dispatched to our saga to make an ajax call and give that that back once the ajax call is completed and return that data to our redux store and it gets render to our page.
Last but not least let's post to our dummy db and get that data back once we have posted it.

First we create new post actions

```
// actions types is object with the
// key: GET_DATA_REQUEST -> value: describes the action
export const Types = {
    GET_DATA_REQUEST: 'get_data_request',
    GET_DATA_SUCCESS: 'get_data_success',
    GET_POSTS_REQUEST: 'get_posts_request',
    GET_POSTS_SUCCESS: 'get_posts_success',
    CREATE_POST_REQUEST: 'create_post_request',
}

// function that returns an object literal 
export const getDataRequest = () => ({
    type: Types.GET_DATA_REQUEST
})
// key: payload will contain result of the api call
export const getDataSuccess = ({data}) => ({
    type: Types.GET_DATA_SUCCESS,
    payload: { data }
})

// reading the posts
export const getPostsRequest = () => ({
    type: Types.GET_POSTS_REQUEST
})
export const getPostsSuccess = ({posts}) => ({
    type: Types.GET_POSTS_SUCCESS,
    payload: { posts }
})
export const createPostRequest = ({post}) => ({
    type: Types.CREATE_POST_REQUEST,
    payload: {post}
})
```

Update our api routes

```
import axios from 'axios';

// data api calls
export const getData = () => {
    return axios.get('http://localhost:4000/comments')
}
export const getPosts = () => {
    return axios.get('http://localhost:4000/posts')
}
export const createPost = ({post}) => {
    console.log("api call ->", post)
    let randomId = Math.random()*100;
    return axios.post('http://localhost:4000/posts',{"id":randomId, "msg": post + randomId})
}
```

Update our sagas
```
import { takeEvery, call, fork, put } from 'redux-saga/effects';
import * as actions from '../actions/data';
import * as api from '../api/data';

// create a generator function
function* fetchData() {
    // try to make the api call
    try {
        // yield the api responsse into data
        const data = yield call(api.getData);
        yield put(actions.getDataSuccess( {data: data.data} ))
    }catch(e) {
        console.log(e);
    }
}
function* watchFetchData() {
    // create watcher of fetchData function
    yield takeEvery(actions.Types.GET_DATA_REQUEST, fetchData);
}

function* fetchPosts() {
    try {
        const posts = yield call(api.getPosts);
        console.log(posts);
        yield put(actions.getPostsSuccess( {posts: posts.data} ))
    }catch(e) {
        console.log(e);
    }
}
function* watchFetchPosts() {
    yield takeEvery(actions.Types.GET_POSTS_REQUEST, fetchPosts);
}

function* createPost(action) {
    try {
        yield call(api.createPost, {post: action.payload.post});
        const posts = yield call(api.getPosts);
        yield put(actions.getPostsSuccess( {posts: posts.data} ))
    }catch(e) {
        console.log(e);
    }
}
function* watchCreatePost() {
    yield takeEvery(actions.Types.CREATE_POST_REQUEST, createPost);
}


const DataSagas = [
    fork(watchFetchData),
    fork(watchFetchPosts),
    fork(watchCreatePost)
];

export default DataSagas;
```

Last but not least update our App.js
```
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getDataRequest, getPostsRequest, createPostRequest } from './actions/data'; 

class App extends Component {    
  componentDidMount() {
    this.props.getDataRequest();
    this.props.getPostsRequest();
  }

  render() {
    return(
      <div>
        <h1>
          {this.props.data}
          {this.props.posts.map((e,i) => {
            return(
              <div key={i}>{e.msg}</div>
            )
          })}
        </h1>

        <button onClick={() => this.props.createPostRequest({"post": "Random Num Post - "})}>CLick to Add POst</button>
      </div>
    )
  }
}

// redux providing state takeover
const mapStateToProps = (state) => {
    console.log("App State ->", state);
    return {
      data: state.data.test,
      posts: state.data.posts
    }
}
export default connect(mapStateToProps, { getDataRequest, getPostsRequest, createPostRequest })(App)  
```

And Click that button we are good to go!