0

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

cesco
  • 53
  • 8
  • Your action is `{type: "FETCH_BUDGETS", budgets: response.data}` and in the reducer you have `return [...state, action.budgets.data]` where `action.budgets === response.data` (I'm assuming the syntax error is a typo). To me this looks like you have an extra `.data` in the reducer that you probably don't need. – Yoshi Jul 02 '18 at 13:05
  • Hi Yoshi, thanks - you are totally right. But unfortunately it doesn't solve my issue. I update my post with a pic of my browser's console - the first element is the store, which doesn't really receive anything from the reducer (4th element). – cesco Jul 02 '18 at 13:15
  • What are the actual data-types you're dealing with? In your reducer you're handling the data as an array, shouldn't it then be `[...state, ...action.budgets.data]` (or similar). Otherwise `action.budgets.data` would simply be inserted at the next index after `...state`. That said, you could try simply returning `action.budgets` as the new state. – Yoshi Jul 02 '18 at 13:22
  • I added a screenshot of whatever the Axios API fetches from the database - it also shows the data types I'm using. The API works perfectly fine - my guess is that I am missing something in the reducer. The reducer is currently [...state, ...action.budgets.data] - but the issue remains the same. – cesco Jul 02 '18 at 13:51
  • That's weird, I don't really see what could be causing the issue. I don't think this would fix it, but you should make your api calls in the componentDidMount lifecycle method – Zuma Jul 02 '18 at 13:55
  • Just to be sure, your reducer is [...state, ...actions.budgets] right ? If yes could you change it in your post just so there won't be any mistakes – Zuma Jul 02 '18 at 13:59
  • tried to exchange the constructor with `componentDidMount(){ this.budgets = this.props.fetchBudgets();` - unfortunately it didn't change anything. I have a different example that doesn't use Axios for the API and doesn't have a nested object as data source - and that example works perfectly fine. My assumption is, that it must be axios or the reducer (which needs to work with a nested object). @Zuma - edited code to [...state, ...action.budgets] – cesco Jul 02 '18 at 14:07
  • also, just edited the .jsx app to display the full code. – cesco Jul 02 '18 at 14:12
  • Your reducer code is syntactically wrong. Also, your merge is destructuring an array instead of object. That's not going to do the object merge as you intend. If that's all correctly done, then I suggest you update your question with correct code. – Mrchief Jul 02 '18 at 14:16
  • Do you have any suggestions, how to do it better? The Axios Api is actually outputting an Array with Dicts in it - I though it might be correct. – cesco Jul 02 '18 at 14:19
  • Also, your app jsx is messed up. You can't do things the way you're doing. For starters, change `data={ this.state.data }` to `data={ this.props.data }` – Mrchief Jul 02 '18 at 14:19
  • If you want an array, then what you have is fine. I'd still say fix the bad syntax and add the spread operators. That way people can help with the real errors. – Mrchief Jul 02 '18 at 14:21
  • If changing render to use `this.props.data` fixes things, then let me know and I'll post an explanation as to what all is wrong with your container. – Mrchief Jul 02 '18 at 14:27
  • what is plan ? In your app you import plan from ../actions and then call plan.fetchBudgets – Zuma Jul 02 '18 at 14:31
  • Also you really need to clean your code. In PlanContainer, you have a state that is defined outside of any method, `this.budget` should be `this.state.budget` and also you should always modify the state with `this.setState` to trigger a re-rendering of your component. Also, and that's more of a conception issue, you shouldn't be using local states in an application that is using redux, your store should be the only place storing information about the state of your app. – Zuma Jul 02 '18 at 14:42
  • I changed the PlanContainer according to Mrchief's suggestions and I tried to apply Object spread to the reducer - unfortunately, I wasn't very successful (see edit 3). – cesco Jul 02 '18 at 15:58
  • @Zuma - plan from ../actions is the Redux action. I will clean the code, by adding the paths on top of each section. – cesco Jul 02 '18 at 15:59

1 Answers1

0

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!

Mrchief
  • 75,126
  • 20
  • 142
  • 189
  • Now reducer is returning an object instead of an array. Are you sure you want to do that? You can't render the table that way. Also, a sidenote: if your goal is to display the budgets returned by the API, then you shouldn't be merging/concatenating it existing state. In your reducer, you can simply return `action.budgets`. – Mrchief Jul 02 '18 at 17:39
  • Also, it'd help if you can create a codesandbox (you can fork this one: https://codesandbox.io/s/jpn9m4rwr9) and use something like jsonapi or mockaxios to fake axios calls. – Mrchief Jul 02 '18 at 17:41
  • thank you so much! I will try to transfer the insights to my code - try to update soon. – cesco Jul 02 '18 at 17:55
  • `changing the reducer index, adding the separate store` (both similar to the codesandbox that you posted) and `implementing Object spread` in the plan reducer did the trick. Again, thank you a lot! – cesco Jul 03 '18 at 17:40