0

I am using ReactJS, ExpressJS, NodeJS, PostgreSQL, with Redux

I'm attempting to retrieve one row of a SQL table from my backend via an axios call. The call returns the requested data in an object nested in an array [{..}]. The problem occurs when I attempt to destructure the data or access it via bracket notation.

If I use this notation this.props.returnedData[0] to access the one and only object in the returned array I get errors that the returnedData[0] doesn't exist or is undefined.

keep in mind, I can see that it has in fact returned the data in the array via a console.log.

I believe this is due to the componentDidMount returning the data after React has already attempted to render the returnedData[0].

So, questions. Is there a way to destructure or access that data any other way? Is there a "better" way (regarding timing) to retrieve data than using componentDidMount?

Austin Callaghan
  • 145
  • 1
  • 3
  • 12
  • `console.log` is synchronized. You might want to use `console.log(JSON.parse(JSION.stringify(this.props.returnedData)))` and see the issue. Widely speaking, throwing an `IF` somewhere in the code would solve the problem – Anand Undavia Sep 24 '18 at 06:53
  • yes, element is rendered before props have data so you are getting this error. check [link 1](https://stackoverflow.com/questions/38461706/how-do-i-render-a-component-when-the-data-is-ready) and [link 2](https://stackoverflow.com/questions/44911012/rendering-react-component-after-api-response) to resolve this. – Jigar Shah Sep 24 '18 at 06:55

2 Answers2

1

If I understand the issue correctly, you are trying to access an array index that does not exist, because your data has not arrived yet. One way to resolve this issue may be to do a quick check if the array has got an index before accessing it. Something like this:

const data = this.props.returnedData.length > 0 ? this.props.returnedData[0]: {}

And then you use the new "data" variable further on, for example in the rendering method or similar.

J.Lindebro
  • 213
  • 3
  • 9
1

Excuse the lengthy explanation, but it shows all steps...

Let's walk through this example: A user goes to a Dashboard view...

client/src/containers/dashboard.js (this HOC container basically just passes Redux actions and state to a DashboardPanels component.)

import React from 'react';
import { connect } from 'react-redux';
import { getDashboardData } from '../actions/dashboardActions';
import DashboardPanels from '../components/dashboard/DashboardPanels';

export default connect(state => ({ dashboardData: state.dashboard.data }), { getDashboardData })(props => <DashboardPanels {...props} />)

client/src/components/dashboard/DashboardPanels (the DashboardPanels component is mounted and then executes the passed down Redux getDashboardData() action in its componentDidMount lifecycle method -- NOTICE: If data hasn't populated yet, it shows a spinner!)

import React, { Component } from 'react';
import { Row } from 'antd';

import PageContainer from '../app/panels/pageContainer';
import MessagesPanel from './messagesPanel';
import PlansPanel from './plansPanel';
import PromotionalsPanel from './promotionalsPanel';
import Spinner from '../app/loaders/Spinner';
import SubcribersPanel from './subscribersPanel';
import TemplatesPanel from './templatesPanel';
import TransactionsPanel from './transactionsPanel';

export default class Dashboard extends Component {     
    componentDidMount = () => this.props.getDashboardData();

    render = () => (
        !this.props.dashboardData
            ? <Spinner />
            : <PageContainer>
                <Row style={{ marginTop: 30 }}>
                    <SubcribersPanel {...this.props.dashboardData} />
                    <PlansPanel {...this.props.dashboardData} />
                    <PromotionalsPanel {...this.props.dashboardData} />
                    <TransactionsPanel {...this.props.dashboardData} />
                    <MessagesPanel {...this.props.dashboardData} />
                    <TemplatesPanel {...this.props.dashboardData} />
                </Row>
              </PageContainer>
    )
}

client/src/actions/dashboardActions.js (this makes an AJAX request to the express API server)

import { app } from './axiosConfig';
import * as types from './types';

const getDashboardData = () => dispatch => (
    app.get(`dashboard`)
    .then(({data}) => dispatch({ type: types.SET_DASHBOARD_DATA, payload: data }))
    .catch(err => dispatch({ type: types.SERVER_ERROR, payload: err }))
)

export {
  getDashboardData
}

routes/dashboard.js (front-end AJAX request hits the express API server's dashboard route -- passes through authentication, then gets sent to the getAll controller)

    const { getAll } = require('../controllers/dashboard);
    const { requireRelogin, requireAuth } = require('../services/strategies');

    app.get('/api/dashboard', requireAuth, getAll);
}

controllers/dashboard.js (The getAll controller receives the request, queries the PostgresSQL DB and returns JSON or what you referred to as a nested object in an array [{...}]. NOTICE that I'm using res.send() with the ...spread operator!)

const { db, query: { getAllDashboardDetails }} = require('../database);
const { beginofMonth, endofMonth, parseStringToNum, sendError } = require('../shared/helpers');

        // GETS ALL DASHBOARD DATA
    exports.getAll = async (req, res, done) => {
            const beginMonth = beginofMonth();
            const endMonth = endofMonth();

            try {
                const dashboard = await db.many(getAllDashboardDetails, [req.session.id, beginMonth, endMonth])

                return res.status(201).send(...dashboard);
            } catch (err) { return sendError(err, res, done); }
        }

dashboard JSON is structured like so on the back-end:

dashboard [{
    subscribers: '146',
    inactivesubscribers: '12',
    plans: '12',
    popularplans: [ [Object], [Object], [Object], [Object], [Object], [Object] ],
    promotionals: '12',
    popularpromotionals: [ [Object], [Object], [Object], [Object], [Object], [Object] ],
    credits: '5',
    creditstotal: '149.95',
    dues: '5',
    duestotal: '149.95',
    charges: '7',
    chargestotal: '209.93',
    refunds: '7',
    refundstotal: '209.93',
    messages: '5',
    activetemplates: '4',
    inactivetemplates: '4' 
}]

client/src/reducers/index.js (however, because we used res.send() with the ...spread operator, it now becomes a simple object by the time it is saved to Redux's state via the dashboardReducer)

import { routerReducer as routing } from 'react-router-redux';
import { combineReducers } from 'redux';
import * as types from '../actions/types';

const dashboardReducer = (state = {}, { payload, type }) => {
    switch (type) {
        case types.SET_DASHBOARD_DATA:
            return {
                ...state,
                data: payload
            };
        default: return state;
    }
}

export default = combineReducers({
    dashboard: dashboardReducer,
    routing
});

Redux's dashboard state is now structured like so:

dashboard: {
    data: {
       subscribers: '146',
       inactivesubscribers: '12',
       plans: '12',
       popularplans: [ [Object], [Object], [Object], [Object], [Object], [Object] ],
       promotionals: '12',
       popularpromotionals: [ [Object], [Object], [Object], [Object], [Object], [Object] ],
       credits: '5',
       creditstotal: '149.95',
       dues: '5',
       duestotal: '149.95',
       charges: '7',
       chargestotal: '209.93',
       refunds: '7',
       refundstotal: '209.93',
       messages: '5',
       activetemplates: '4',
       inactivetemplates: '4'
    } 
 }

Since the DashboardPanels is connected to Redux via a HOC component, we can now access Redux's state.dashboard.data via this.props.dashboardData.

And the result:

enter image description here

Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51