Ustawiłbym to tak, abyś polegał na globalnej zmiennej stanu, aby powiedzieć swoim komponentom, kiedy ma być renderowany. Redux jest lepszy w tym scenariuszu, w którym wiele komponentów ze sobą rozmawia, a wspomniałeś w komentarzu, że czasami go używasz. Więc naszkicuję odpowiedź za pomocą Redux.
Trzeba przenieść wywołań API do kontenera macierzystego Component A. Jeśli chcesz, aby wnuki były renderowane tylko po zakończeniu wywołań API, nie możesz zachować tych wywołań API w samych wnukach. Jak można wywołać API z komponentu, który jeszcze nie istnieje?
Po wykonaniu wszystkich wywołań interfejsu API można użyć akcji do zaktualizowania globalnej zmiennej stanu zawierającej kilka obiektów danych. Za każdym razem, gdy dane są odbierane (lub wychwytywany jest błąd), możesz wysłać akcję, aby sprawdzić, czy obiekt danych jest całkowicie wypełniony. Po jej całkowitym wypełnieniu możesz zaktualizować loadingzmienną falsei warunkowo renderować Gridkomponent.
Na przykład:
// 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 })
Twój reduktor zatrzyma globalny obiekt stanu, który powie, czy wszystko jest już gotowe, czy nie:
// 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
}
}
}
W twoich działaniach:
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
}
}
}
Na bok
Jeśli jesteś naprawdę żonaty z myślą, że Twoje wywołania API znajdują się wewnątrz każdego wnuka, ale że cała siatka wnuków nie jest renderowana, dopóki wszystkie wywołania API nie zostaną zakończone, musisz użyć zupełnie innego rozwiązania. W takim przypadku wasze wnuki musiałyby być renderowane od samego początku, aby wykonywać połączenia, ale miały klasę css display: none, która zmienia się dopiero po tym, jak globalna zmienna stanu loadingjest oznaczona jako fałsz. Jest to również wykonalne, ale w pewnym sensie poza React.