I have a component TreeNav whose data comes from api call. I have setup reducer/action/promise and all the plumbing, but in component render when I call map() over the data, getting "Uncaught TypeError: Cannot read property 'map' of undefined".
Troubleshooting revealed TreeNav render() is called twice. 2nd time is after data comes back from api. But due to 1st render() error, 2nd render() never runs.
Here are my code files:
-------- reducers/index.js ---------
import { combineReducers } from 'redux';
import TreeDataReducer from './reducer_treedata';
const rootReducer = combineReducers({
treedata: TreeDataReducer
});
export default rootReducer;
-------- reducers/reducer_treedata.js ---------
import {FETCH_TREE_DATA} from '../actions/index';
export default function (state=[], action) {
switch (action.type) {
case FETCH_TREE_DATA: {
return [action.payload.data, ...state];
}
}
return state;
}
-------- actions/index.js --------
import axios from 'axios';
const ROOT_URL = 'http://localhost:8080/api';
export const FETCH_TREE_DATA = 'FETCH_TREE_DATA';
export function fetchTreeData () {
const url = `${ROOT_URL}/treedata`;
const request = axios.get(url);
return {
type: FETCH_TREE_DATA,
payload: request
};
}
-------- components/tree_nav.js --------
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {fetchTreeData} from '../actions/index';
class TreeNav extends Component {
constructor (props) {
super(props);
this.state = {treedata: null};
this.getTreeData();
}
getTreeData () {
this.props.fetchTreeData();
}
renderTreeData (treeNodeData) {
const text = treeNodeData.text;
return (
<div>
{text}
</div>
);
}
render () {
return (
<div className="tree-nav">
{this.props.treedata.children.map(this.renderTreeData)}
</div>
);
}
}
function mapStateToProps ({treedata}) {
return {treedata};
}
// anything returned from this function will end up as props
// on the tree nav
function mapDispatchToProps (dispatch) {
// whenever selectBook is called the result should be passed to all our reducers
return bindActionCreators({fetchTreeData}, dispatch);
}
// Promote tree_nav from a component to a container. Needs to know about
// this new dispatch method, fetchTreeData. Make it available as a prop.
export default connect(mapStateToProps, mapDispatchToProps)(TreeNav);
In terms of the error with your second render, the state must be getting overridden in a way you're not expecting. So in your reducer, you're returning array that contains whatever data is, and a splat of the current state. With arrays that does a concat.
var a = [1,2,3]
var b = [a, ...[2,3,4]]
Compiles to:
var a = [1, 2, 3];
var b = [a].concat([2, 3, 4]);
So given you're expecting a children property, what i think you actually want is a reducer that returns an object, not an array, and do something like this instead:
return Object.assign({}, state, { children: action.payload.data });
From there be sure to update the initial state to be an object, with an empty children array.
Get rid of this line since you're using props instead of state. State can be helpful if you need to manage changes just internally to the component. But you're leveraging connect, so that's not needed here.
this.state = {treedata: null};
Solved this by checking for the presence of this.props.treedata, etc. and if not available yet, just should div "loading...".
render () {
if (this.props.treedata && this.props.treedata[0] && this.props.treedata[0].children) {
console.dir(this.props.treedata[0].children);
return (
<div className="tree-nav">
{this.props.treedata[0].children.map(this.renderTreeData)}
</div>
);
} else {
return <div>Loading...</div>;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With