javascript - ¿Por qué se llama dos veces el renderizado de mi componente React, una vez sin datos y luego más tarde con datos, pero con una excepción por ser demasiado tarde?
Tengo un componente TreeNav cuyos datos provienen de una llamada a la API. He configurado reducer/action/promise y toda la estructura, pero en el renderizado del componente, cuando llamo a map() sobre los datos, obtengo "Uncaught TypeError: Cannot read property 'map' of undefined".
La resolución de problemas reveló que TreeNav render() se llama dos veces. La segunda vez es después de que los datos regresan de la API. Pero debido al error en la primera render(), la segunda render() nunca se ejecuta.
Aquí están mis archivos de código:
-------- reducers/index.js ---------
import { combineReducers } from 'redux';
import TreeDataReducer from './reducer_treedata';
const rootReducer = combineReducers({
treedata: TreeDataReducer
});
export default rootReducer;
-------- reductores/reductor_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
};
}
-------- componentes/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);
Respuesta #1
En cuanto al error con tu segunda renderización, el estado debe estar siendo anulado de una manera que no esperas. Así que en tu reductor, estás devolviendo un arreglo que contiene cualquiera que sea la información, y un desbordamiento del estado actual. Con los arreglos, eso hace un concat.
var a = [1,2,3]
var b = [a, ...[2,3,4]]
Compila a:
var a = [1, 2, 3];
var b = [a].concat([2, 3, 4]);
Entonces, dado que esperas una propiedad children, lo que creo que realmente quieres es un reductor que devuelva un objeto, no un arreglo, y hacer algo como esto en su lugar:
return Object.assign({}, state, { children: action.payload.data });
A partir de ahí, asegúrate de actualizar el estado inicial para que sea un objeto, con un arreglo vacío de hijos.
Elimina esta línea ya que estás usando props en lugar de state. El state puede ser útil si necesitas manejar cambios solo internamente en el componente. Pero estás usando connect, así que no es necesario aquí.
this.state = {treedata: null};
Respuesta #2
Resuelto esto comprobando la presencia de this.props.treedata, etc., y si aún no está disponible, simplemente mostrar un div con "cargando...".
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>;
}
}
