Ich würde es so einrichten, dass Sie sich auf eine globale Statusvariable verlassen, um Ihren Komponenten mitzuteilen, wann sie gerendert werden sollen. Redux ist besser für dieses Szenario, in dem viele Komponenten miteinander sprechen, und Sie haben in einem Kommentar erwähnt, dass Sie es manchmal verwenden. Also skizziere ich eine Antwort mit Redux.
Sie müssten Ihre API-Aufrufe in den übergeordneten Container verschieben. Component A . Wenn Sie möchten, dass Ihre Enkelkinder erst nach Abschluss der API-Aufrufe gerendert werden, können Sie diese API-Aufrufe nicht in den Enkelkindern selbst behalten. Wie kann ein API-Aufruf von einer Komponente aus erfolgen, die noch nicht vorhanden ist?
Sobald alle API-Aufrufe ausgeführt wurden, können Sie mithilfe von Aktionen eine globale Statusvariable aktualisieren, die eine Reihe von Datenobjekten enthält. Jedes Mal, wenn Daten empfangen werden (oder ein Fehler abgefangen wird), können Sie eine Aktion auslösen, um zu überprüfen, ob Ihr Datenobjekt vollständig ausgefüllt ist. Sobald es vollständig ausgefüllt ist, können Sie eine loadingVariable auf aktualisieren falseund Ihre unter bestimmten Bedingungen rendernGrid Komponente .
Also zum Beispiel:
// 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 })
Ihr Reduzierer hält das globale Statusobjekt bereit, das angibt, ob alles bereit ist oder nicht:
// 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
}
}
}
In Ihren Handlungen:
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
}
}
}
Beiseite
Wenn Sie wirklich mit der Idee verheiratet sind, dass Ihre API-Aufrufe in jedem Enkelkind gespeichert sind, das gesamte Raster der Enkelkinder jedoch erst nach Abschluss aller API-Aufrufe gerendert wird, müssen Sie eine völlig andere Lösung verwenden. In diesem Fall müssten Ihre Enkelkinder von Anfang an gerendert werden, um ihre Aufrufe zu tätigen, haben jedoch eine CSS-Klasse mit display: none, die sich erst ändert, nachdem die globale Statusvariable loadingals falsch markiert wurde. Dies ist auch machbar, aber irgendwie neben dem Punkt der Reaktion.