Я бы настроил его так, чтобы вы полагались на глобальную переменную состояния, чтобы сообщить своим компонентам, когда нужно визуализировать. Redux лучше для этого сценария, когда многие компоненты общаются друг с другом, и вы упомянули в комментарии, что иногда используете его. Поэтому я набросаю ответ, используя Redux.
Вы должны переместить вызовы API в родительский контейнер Component A
. Если вы хотите, чтобы ваши внуки отображались только после завершения вызовов API, вы не можете оставить эти вызовы API в самих внуках. Как можно выполнить вызов API из компонента, который еще не существует?
После выполнения всех вызовов API вы можете использовать действия для обновления глобальной переменной состояния, содержащей набор объектов данных. Каждый раз, когда данные получены (или обнаружена ошибка), вы можете отправить действие, чтобы проверить, полностью ли заполнен ваш объект данных. Как только он будет полностью заполнен, вы можете обновить loading
переменную false
и условно отобразить ваш Grid
компонент.
Так, например:
// Component A
import { acceptData, catchError } from '../actions'
class ComponentA extends React.Component{
componentDidMount () {
fetch('yoururl.com/data')
.then( response => response.json() )
// send your data to the global state data array
.then( data => this.props.acceptData(data, grandChildNumber) )
.catch( error => this.props.catchError(error, grandChildNumber) )
// make all your fetch calls here
}
// Conditionally render your Loading or Grid based on the global state variable 'loading'
render() {
return (
{ this.props.loading && <Loading /> }
{ !this.props.loading && <Grid /> }
)
}
}
const mapStateToProps = state => ({ loading: state.loading })
const mapDispatchToProps = dispatch => ({
acceptData: data => dispatch( acceptData( data, number ) )
catchError: error=> dispatch( catchError( error, number) )
})
// Grid - not much going on here...
render () {
return (
<div className="Grid">
<GrandChild1 number={1} />
<GrandChild2 number={2} />
<GrandChild3 number={3} />
...
// Or render the granchildren from an array with a .map, or something similar
</div>
)
}
// Grandchild
// Conditionally render either an error or your data, depending on what came back from fetch
render () {
return (
{ !this.props.data[this.props.number].error && <Your Content Here /> }
{ this.props.data[this.props.number].error && <Your Error Here /> }
)
}
const mapStateToProps = state => ({ data: state.data })
Ваш редуктор будет удерживать объект глобального состояния, который скажет, все ли готово к работе или нет:
// reducers.js
const initialState = {
data: [{},{},{},{}...], // 9 empty objects
loading: true
}
const reducers = (state = initialState, action) {
switch(action.type){
case RECIEVE_SOME_DATA:
return {
...state,
data: action.data
}
case RECIEVE_ERROR:
return {
...state,
data: action.data
}
case STOP_LOADING:
return {
...state,
loading: false
}
}
}
В ваших действиях:
export const acceptData = (data, number) => {
// First revise your data array to have the new data in the right place
const updatedData = data
updatedData[number] = data
// Now check to see if all your data objects are populated
// and update your loading state:
dispatch( checkAllData() )
return {
type: RECIEVE_SOME_DATA,
data: updatedData,
}
}
// error checking - because you want your stuff to render even if one of your api calls
// catches an error
export const catchError(error, number) {
// First revise your data array to have the error in the right place
const updatedData = data
updatedData[number].error = error
// Now check to see if all your data objects are populated
// and update your loading state:
dispatch( checkAllData() )
return {
type: RECIEVE_ERROR,
data: updatedData,
}
}
export const checkAllData() {
// Check that every data object has something in it
if ( // fancy footwork to check each object in the data array and see if its empty or not
store.getState().data.every( dataSet =>
Object.entries(dataSet).length === 0 && dataSet.constructor === Object ) ) {
return {
type: STOP_LOADING
}
}
}
В стороне
Если вы действительно женаты на идее, что ваши вызовы API живут внутри каждого внука, но вся сетка внуков не рендерится, пока все вызовы API не будут завершены, вам придется использовать совершенно другое решение. В этом случае ваши внуки должны будут отображаться с самого начала, чтобы сделать их вызовы, но иметь класс css display: none
, который изменяется только после того, как глобальная переменная состояния loading
помечена как ложная. Это также выполнимо, но в некотором роде помимо точки Реагирования.