0

I'm struggling to learn React-Redux. I'm making a simple application that fetch data from Teleport API and show the list according to the user's input.

My issue is that even though action is dispatched in container component, state is not changed and the result does not show up.

This is a screenshot of console after dispatching action twice.

enter image description here

I suppose that there is an issue to store data properly. I appreciate if you can help me.

Here is my code.

/container/search.js

class Search extends Component{
 constructor(props){
    super(props);
    this.state = {
      city : ""
    }
   this.handleSubmit = this.handleSubmit.bind(this);
   this.handleChange = this.handleChange.bind(this);
   }

handleChange(event) {
    console.log(event.target.value)
    this.setState({
        city: event.target.value
    });
}

handleSubmit(event){
    event.preventDefault();
    console.log(this.state.city)
    this.props.addData(this.state.city);
    this.setState({
        city: ""
    });
}
render(){
    return(
        <div>
        <form onSubmit={this.handleSubmit}>
        <input type="text"
               placeholder="add city name"
               onChange={this.handleChange}
               value={this.state.city} />
        <button>Submit</button>
        </form>
        </div>
        )
}
}

function mapDispatchToProps(dispatch) {
return bindActionCreators({ addData }, dispatch);
}

export default connect(null, mapDispatchToProps)(Search);

/actions/index.js

import axios from 'axios';

const ROOT_URL = "https://api.teleport.org/api/cities/?search";

const ADD_DATA = 'ADD_DATA';

export function addData(city){
    const url = `${ROOT_URL}=${city}`;
    const request = axios.get(url);
    return {
        type: ADD_DATA,
        payload: request
    };
 }

/reducers/reducer_data.js

import { ADD_DATA } from "../actions/index";

export default function( state=[], action) {
switch(action.type) {
    case ADD_DATA:
        return [action.payload.data, ...state];
}
return state;
}

/reducers/index.js

import { ADD_DATA } from "../actions/index";

export default function( state=[], action) {
    switch(action.type) {
        case ADD_DATA:
            return [action.payload.data, ...state];
}
return state;
}

//EDIT // index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import logger from 'redux-logger'

import reducers from './reducers';
import Search from './containers/search';

const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, logger)(createStore);

ReactDOM.render(
    <Provider store={createStoreWithMiddleware(reducers)}>
    <Search />
    </Provider>
, document.querySelector('.container'));
aaayumi
  • 1,224
  • 8
  • 27
  • 47
  • 4
    `axios.get(url)` is asynchronous. Don't you need to wait for it before sending it ? – Hemerson Carlin Nov 14 '17 at 10:02
  • I use `redux-promise` for asynchronous request. I will update my code. – aaayumi Nov 14 '17 at 10:11
  • 1
    You may need to use some middleware like redux-thunk to join axios/fetches in redux logic, read this: https://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux – dpetrini Nov 14 '17 at 10:14

2 Answers2

1

You can use redux api middleware to do the api call.All you have to change is

actions/index.js

import {CALL_API} from 'redux-api-middleware';


export const addData=(city)=>({

    [CALL_API]:{

        endpoint:"https://api.teleport.org/api/cities",
        query:{
              "search":city
             }
        method:'GET',
        types:["ADD_DATA","ADD_DATA_SUCCESS","ADD_DATA_FAILURE"]
    }
    });

/reducers/reducer_data.js

           import {combineReducers} from 'redux'
                const InitialData={
                    dataList:[],
                    error:null,
                    loading:false
                }
                export const dataList=(state=InitialData,action)=>{
                    switch(action.type){
                        case "ADD_DATA":
                            return Object.assign({},state,{
                                dataList:[],error:null,loading:true
                            })
                        case "ADD_DATA_SUCCESS":
                            return Object.assign({},state,{
                                dataList:action.payload,error:null,loading:false
                            })
                        case "ADD_DATA_FAILURE":
                           error = action.payload.data || { message: action.payload };
                            return Object.assign({},state,{
                                dataList:action.payload,error:error,loading:false
                            })
                        default:
                            return state;
                    }
                }

            //Its better to use combine reducer to combine all the reducers 
            //you have in your app  as one as redux permits only one store per app  


            export const reducers=combineReducers({
            DataList:dataList
            })

export default reducers

store.js

import {
  applyMiddleware,
  createStore,compose
} from 'redux';

// import thunk from 'redux-thunk';
import { apiMiddleware } from 'redux-api-middleware';
import {CALL_API} from 'redux-api-middleware';
import promise from 'redux-promise';
import reducers from './reducer';
import { logger} from 'redux-logger'; 
import ReduxThunk from 'redux-thunk' 

import qs from 'querystring'



 function queryMiddleware() {
  return next => action => {
    if (action.hasOwnProperty(CALL_API) && action[CALL_API].hasOwnProperty('query')) {
      const request = action[CALL_API];
      request.endpoint = [
        request.endpoint.replace(/\?*/, ''),
        qs.stringify(request.query),
      ].join('?');
      delete request.query;

      return next({ [CALL_API]: request });
    }

    return next(action);
  };
}
export function ConfigureStore(IntitialState={}){
    const stores=createStore(reducers,IntitialState,compose(
        applyMiddleware(queryMiddleware,ReduxThunk,apiMiddleware,promise),
         window.devToolsExtension ? window.devToolsExtension() : f => f
        ));


    return stores;
}; 
 export const  store=ConfigureStore()

index.js

import React from 'react'
import  ReactDOM from 'react-dom'
import store from './store'
import Search from './Search'
ReactDOM.render((
     <Provider store={store}>
            <Search />
        </Provider>
    ),document.getElementById('main-container'))

Note: you can install Redux devtools extension in chrome and u can check the redux store in your chrome developer tools.I think this will be easy to find out what happens in your redux store.

Rajesh kumar
  • 332
  • 3
  • 4
  • 12
0

Since axios.get(url) is asynchronous you need to wait for the request to succeed and then then dispatch an action. You could make use of middlewares such as redux-thunk while creating a store and then do the following

/actions/index.js

const ADD_DATA = 'ADD_DATA';

export function addData(city){

    return function(dispatch) {
         const url = `${ROOT_URL}=${city}`;
         axios.get(url)
               .then((res) => {
                    dispatch({
                       type: ADD_DATA,
                       payload: res.data
                    });
               });
    }

 }

/reducers/index.js

import { ADD_DATA } from "../actions/index";

export default function( state=[], action) {
    switch(action.type) {
        case ADD_DATA:
            return [...state, action.payload];
}
return state;
}
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • I tried your answer and it showed an error: `Error: Actions must be plain objects. Use custom middleware for async actions.`. I will use redux-thunk and will let you know. – aaayumi Nov 14 '17 at 10:31
  • I added redux-thunk and tried. Still both states before and after action are empty... – aaayumi Nov 14 '17 at 13:12