Search code examples
reactjsreduxdjango-rest-frameworkaxiosreact-bootstrap-table

Redux Store and nested JSON from Axios API


I tried every possible variation of this code, but I don't really manage to get whatever the API fetched into my data store. I am absolutely stuck and would appreciate some help. I think I just don't get the essential part of this construct and I would really like to understand how it works properly.

The data looks like this - it's basically a simple JSON (from a django restframework API) with some nested elements:

EDIT 2 (changed JSON to screenshot of axios API/ Redux action) Data Types

My Redux action - works perfectly fine. console.log pulls exactly the data from above (with correct inputs) :

    // ./action/plan.js 

import axios from 'axios';

    export function fetchBudgets(){
      return function(dispatch){
        axios.get("/api/budgets/")
        .then((response) => {
          console.log(response)
          dispatch({ type: "FETCH_BUDGETS", budgets: response.data})
        })
        .catch((err) => {
          dispatch({type: "FETCH_DATA_REJECTED", budgets: err})
        })
      }
    }

So until now, everything seems fine. The problems starts with the reducer - as I am not sure how to model the reducer to use the nested data.

My reducer:

       // ./reducer/plan.js

        const initialState = {}


export default function budgets(state=initialState, action) {


    switch (action.type) {

        case 'FETCH_BUDGETS':
        console.log(action)
            return {
                    ...state,
                        id: action.budgets.id,
                        value_jan: action.budgets.value_jan,
                        value_feb: action.budgets.value_feb,
                        value_mar: action.budgets.value_mar,
                        value_apr: action.budgets.value_apr,
                        value_may: action.budgets.value_may,
                        value_jun: action.budgets.value_jun,
                        value_jul: action.budgets.value_jul,
                        value_aug: action.budgets.value_aug,
                        value_sep: action.budgets.value_sep,
                        value_oct: action.budgets.value_oct,
                        value_nov: action.budgets.value_nov,
                        value_dec: action.budgets.value_dec,
                        p_version: action.budgets.p_version,
                        entry_time: action.budgets.entry_time,
                        campaign: {
                            ...state.campaign, ...action.budgets.campaign
                        },
                        segment: {
                            ...state.segment, ...action.budgets.segment
                        },
                        touch_point: {
                            ...state.touch_point, ...action.budgets.touch_point
                        },
                        year: {
                            ...state.year, ...action.budgets.year
                        },
                        user: {
                            ...state.user, ...action.budgets.user
                        }
                    }

        default:
            return state
    }



}

I already cannot display data in here - so this.props.fetchBudgets() doesn't seem to fetch any data.

My .jsx App

//./container/PlanContainer.jsx

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


    import BootstrapTable from 'react-bootstrap-table-next';
    import cellEditFactory from 'react-bootstrap-table2-editor';

    import 'jquery';
    import 'popper.js'
    import 'bootstrap';
    import 'underscore'
    import _ from 'lodash'


    import {plan} from "../actions";


    const columns = [
                          { dataField: 'id', text: 'ID', hidden: true}, 
                          { dataField: 'year', text: 'Year', editable: false}, 
                          { dataField: 'segment', text: 'Segment', editable: false},
                          { dataField: 'campaign.name',text: 'Campaign', editable: false},
                          { dataField: 'touch_point',text: 'Touchpoint', editable: false},
                          { dataField: 'value_jan',text: 'Jan'},
                          { dataField: 'value_feb',text: 'Feb'},
                          { dataField: 'value_mar',text: 'Mar'},
                          { dataField: 'value_apr',text: 'Apr'},
                          { dataField: 'value_may',text: 'May'},
                          { dataField: 'value_jun',text: 'Jun'},
                          { dataField: 'value_jul',text: 'Jul'},
                          { dataField: 'value_aug',text: 'Aug'},
                          { dataField: 'value_sep',text: 'Sep'},
                          { dataField: 'value_oct',text: 'Oct'},
                          { dataField: 'value_nov',text: 'Nov'},
                          { dataField: 'value_dec',text: 'Dec'},
                          { dataField: 'user',text: 'User'},
                        ];

    const RemoteCellEdit = (props) => {

      const { columns, data, keyField } = props

      const cellEdit = {
        mode: 'click',
        errorMessage: props.errorMessage,
        blurToSave: true
      };

      return (
        <div>
          <BootstrapTable
            remote={ { cellEdit: true } }
            keyField = { keyField }
            data={ data }
            columns={ columns }
          />


        </div>
      );
    };

    class PlanContainer extends React.Component {

      componentDidMount() {

        this.props.fetchBudgets();
        console.log(this.props.fetchBudgets())

      }




      render() {
        return (
          <div>
          <RemoteCellEdit
            data={ this.props.budgets }
            columns = { columns }
            keyField = 'id'
          />



        </div>
        );
      }
    }


    const mapStateToProps = state => {
        return {
            budgets: state.budgets,
        }
    }

    const mapDispatchToProps = dispatch => {
      return {
        fetchBudgets: () => {
          dispatch(plan.fetchBudgets());
        },
      }
    }



    export default connect(mapStateToProps, mapDispatchToProps)(PlanContainer);

Finally, my store - according to the console.log nothing is beeing passed:

// .Planning.jsx    

import React from "react"
    import { hot } from 'react-hot-loader'
    import { render } from "react-dom"
    import {
      createStore,
      compose,
      applyMiddleware,
      combineReducers,
    } from "redux"
    import { Provider } from "react-redux"
    import thunk from "redux-thunk"

    import PlanContainer from "./containers/PlanContainer"
    import reducerApp from "./reducers";
    import Sidebar from "./components/Sidebar"

    import axios from 'axios';
    import axiosMiddleware from 'redux-axios-middleware';



    let store = createStore(reducerApp, applyMiddleware(thunk, axiosMiddleware(axios)));

    console.log(store)

    class Planning extends React.Component {
      render() {
        return (

          <Sidebar>
            <Provider store={store}>
              <PlanContainer />
            </Provider>
          </Sidebar>

        )
      }
    }

    render(<Planning />, document.getElementById('Planning'))

Again, I would appreciate as I've been stuck on this issue for quite some time and I really want to understand how to do this properly.

Edit: Here's a screenshot of my browser: 1st element is the store, second in the .jsx app, 3rd of the action (that looks perfectly fine) and 4th of the action in the reducer. Store, jsx, action, reducer


Solution

  • PlanContainer is messed up. Here's how:

    componentDidMount() {
        this.budgets = this.props.fetchBudgets();   
      }
    

    this.budgets is pointing to the value returned by this.props.fetchBudgets() which, in this case, is a Promise, and not the actual data.

    state = {
          data: this.budgets
        };
    

    state now holds the promise, not the data.

    render() {
        return (
          <div>
          <RemoteCellEdit
            data={ this.state.data }
            ...
      }
    

    So data here is not the actual data but the promise.

    The confusion is happening because you are mixing redux state with react state. Use one or the other, not both (there are expcetions to this but not in this particular scenario).

    There are some more issues with PlanContainer which are not clear as to whether they are real issues, or just a result of code ommission in OP.

    See annotations below:

    class PlanContainer extends React.Component {
    
      componentDidMount() {
        this.props.fetchBudgets();
      }
    
      constructor(props) {
        ... // removed for brevity, use the same code as you have right now
      }
    
    
    
      render() {
        return (
          <div>
          <RemoteCellEdit
            data={ this.props.budgets}
            columns = { this.columns }
            keyField = 'id'
            errorMessage={ /* should come from props.data or similar - it's not in state */ }
          />
    
          <tbody>
          {this.props.budgets} /* not sure what this is for - I assumed RemoteCellEdit is the one rendering the data */
          </tbody>
    
        </div>
        );
      }
    }
    

    Fixing these should set you on the correct course. Good luck!