0

I am trying to consume an API that I have created, I have followed a code that I have used before but when the component is loaded, as the json list is empty, it shows and empty list, I can see in the log that the list is being loaded afterwards but no refresh or anything on the component. I tried to add a validation that if the length of the list is cero just not print anything but this leads to an error. I can guess that there is an issue regarding the middleware (I am using redux-promise). As you can see I have added the middleware on the application definition, I can't see what its missing Any ideas? Here is my code:

actions/index.js:

import axios from 'axios';

export const FETCH_TESTS = 'FETCH_TESTS';
const ROOT_URL = 'http://some-working-api-entry.com';

export function fetchTests(){
  const request = axios.get(`${ROOT_URL}/tests/?format=json`);
  return {
    type: FETCH_TESTS,
    payload: request
  }
}

reducers/reducer_tests.js

import { FETCH_TESTS } from '../actions/index';

export default function(state = [], action){
  switch (action.type) {
    case FETCH_TESTS:
      return [action.payload.data, ...state]; //ES6 syntaxis\   

 }
 return state;
}

actions/index.js

import { combineReducers } from 'redux';
import TestsReducer from './reducer_tests';

const rootReducer = combineReducers({
  tests: TestsReducer
});

export default rootReducer;

containers/list_tests.js

import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchTests } from '../actions';

class TestList extends Component{

    componentDidMount(){
        this.props.fetchTests();
    }

    renderTest(){
      return _.map(this.props.tests, test => {
        return (
          <tr key={test.id}>
            <td>{test.id}</td>
            <td>{test.col1}</td>
            <td>{test.col2}</td>
            <td>{test.col3}</td>
            <td>{test.col4}</td>
        </tr>
        );
      });
  }

  render(){
    return (
      <table className="table table-hover">
        <thead>
          <tr>
            <th>ID</th>
            <th>Col 1</th>
            <th>Col 2</th>
            <th>Col 3</th>
            <th>Col 4</th>
          </tr>
        </thead>
        <tbody>
          { this.renderTest() }
        </tbody>
      </table>
    );
  }
}

function mapStateToProps(state){
    return {tests: state.tests}
  }
//export default connect(mapStateToProps)(TestList)
export default connect(mapStateToProps, { fetchTests})(TestList);

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';

import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);

ReactDOM.render(
    <Provider store={createStoreWithMiddleware(reducers)}>
      <App />
    </Provider>
    , document.getElementById('root'));

package.json

{
  "name": "someapp",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.18.0",
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "react-redux": "^5.0.7",
    "react-scripts": "1.1.4",
    "redux": "^4.0.0",
    "redux-logger": "^3.0.6",
    "redux-promise": "^0.5.3",
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

EDIT: From the action creator (the array contains the only object on the api list entrypoint):

config: Object { timeout: 0, xsrfCookieName: "XSRF-TOKEN", xsrfHeaderName: "X-XSRF-TOKEN", … }
​
data: Array [ {…} ]
​
headers: Object { "content-type": "application/json" }
​
request: XMLHttpRequest { readyState: 4, timeout: 0, withCredentials: false, … }

​ status: 200 ​ statusText: "OK" ​ proto: Object { … }

from reducer:

payload:[object Object]

If I log the tests props on the container, first it logs an empty array [] then it logs an array of length 1

Cheluis
  • 1,402
  • 3
  • 22
  • 51
  • Does the action go to the reducer? You can check the output of the network request with: `axios.get(`${ROOT_URL}/tests/?format=json`).then(result=>console.log("result:",result)||result);` – HMR May 12 '18 at 13:25
  • Could you please update question with output of axios.get and a console.log in your reducer that logs every action? – HMR May 12 '18 at 13:40
  • Surely its not updating because your doing nothing with the props? You call `mapStateToProps` but do not catch them in `componentDidReceiveProps`. You should set them to your component state here which would cause the component to re-render. – TPHughes May 12 '18 at 14:00

2 Answers2

-1

You have to call dispatcher to update store.

In containers/list_tests.js

const mapDispatchToProp = dispatch => ({
      fetchTests : () => dispatch(fetchTests())
})

export default connect(mapStateToProps, mapDispatchToProp )(TestList);

EDIT 1: Found another issue.

export function fetchTests(){
  return axios.get(`${ROOT_URL}/tests/?format=json`)
       .then(res => {
             return {
                type: FETCH_TESTS,
                 payload: res.data
              }
        });

}
Ritwick Dey
  • 18,464
  • 3
  • 24
  • 37
  • I think action binding has been done correctly with `export default connect(mapStateToProps, { fetchTests})(TestList);` Not sure if res.data will help, OP needs to post what the response is and if it was successful at all. – HMR May 12 '18 at 13:30
  • I don't know how it is correct => `export default connect(mapStateToProps, { fetchTests})(TestList);` .... He wants to update redux store. If we don't call `dispatch` then how redux store will be updated? May be I'm not getting your point. @HMR – Ritwick Dey May 12 '18 at 13:36
  • Yes, you can [pass an object](https://github.com/reactjs/react-redux/blob/master/docs/api.md): If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props. – HMR May 12 '18 at 13:39
-1
const request = axios.get(`${ROOT_URL}/tests/?format=json`);
 // here is your issue because axios returns promise.

Create a doGet method like this.

export function doGet(url, onSuccess, onFailure) {

        return axios.get(url)
            .then((response) => {
                if (onSuccess) {
                    onSuccess(response);
                }

                return response.data || {};
            })
            .catch((error) => {
                if (onFailure) {
                    onFailure(error);
                }
            });
    }

And call this method on your componentDidMount like:

doGet(
   'your/api/url',
   (response) => console.log('Api success callback', response),
   (error) => console.log('error callback', error)
)
GAJESH PANIGRAHI
  • 1,204
  • 10
  • 17
  • Sorry, you're wrong. Axios.get returns a promise and should be handled by redux-promise. Adding callback option to something that is already a promise is pointless. – HMR May 12 '18 at 13:26
  • I am not wrong, I have created a generic doGet method for all get request. – GAJESH PANIGRAHI May 12 '18 at 13:30
  • I agree that calling a get function instead of using axios directly (possibly from many different places) is better design but that is not the current problem. Returning promise for payload is perfectly fine for action when using [redux-promise](https://github.com/redux-utilities/redux-promise) middleware – HMR May 12 '18 at 13:33
  • Ok, I agree with you. – GAJESH PANIGRAHI May 12 '18 at 13:34