Search code examples
typescriptreact-nativereact-hooks

Passing in API data before calling child component React Native (TypeScript)


I am trying to make an API call in my parent component and pass that data to the child component but I think the child component is getting rendered before the API call is complete and I'm receiving undefined data in my child component.

Specifically, the error is: Possible unhandled promise rejection (id: 0): TypeError: Cannot convert undefined value to object

I want to wait for the API call to finish then use that data to pass it on to my child.

Here's the parent:

const Parent = () => {
    ...
    const [intraday, setIntraday] = useState<IntradayInterface>();
    const [daily, setDaily] = useState<DailyInterface>();
    const [weekly, setWeekly] = useState<WeeklyInterface>();
    const [monthly, setMonthly] = useState<MonthlyInterface>();

    useEffect(() => {
      async function UpdateDetails()
      {
          const intradayJSON = await getIntradayData(newSymbol);
          const dailyJSON = await getDailyData(newSymbol);
          const weeklyJSON = await getWeeklyData(newSymbol);
          const monthlyJSON = await getMonthlyData(newSymbol);
      
          setIntraday(parseIntradayData(intradayJSON));          
          setDaily(parseDailyData(dailyJSON));
          setWeekly(parseWeeklyData(weeklyJSON));
          setMonthly(parseMonthlyData(monthlyJSON));
           
          setIsLoading(false);
      }
      UpdateStockDetails();
    }, []); 

    if(isLoading){
      return <ActivityIndicator size='large' />
    }
    else{
      return (
        <ScrollView style={{ backgroundColor: black}}>
          <View style={{ flex: 1, justifyContent: 'space-between', padding: 10 }}>
          <View>
              <Graph symbol={newSymbol} 
                intraday={intraday!}
                daily={daily!}
                weekly={weekly!}
                monthly={monthly!}/>
          </View>
...

And the child is my Graph component.

Is there anyway I can make the parent wait for the api call to finish so I can pass on the result data to the child Graph component?


Solution

  • You can conditionally render the Graph component when all the props it needs are available. You should also surround any asynchronous logic in a try/catch in order to handle thrown exceptions and rejected Promises.

    Example:

    const Parent = () => {
      ...
      const [intraday, setIntraday] = useState<IntradayInterface>();
      const [daily, setDaily] = useState<DailyInterface>();
      const [weekly, setWeekly] = useState<WeeklyInterface>();
      const [monthly, setMonthly] = useState<MonthlyInterface>();
    
      useEffect(() => {
        async function updateDetails() {
          try {
            const [intradayJSON, dailyJSON, weeklyJSON, monthlyJSON] =
              await Promise.all([
                getIntradayData(newSymbol),
                getDailyData(newSymbol),
                getWeeklyData(newSymbol),
                getMonthlyData(newSymbol)
              ]);
          
            setIntraday(parseIntradayData(intradayJSON));          
            setDaily(parseDailyData(dailyJSON));
            setWeekly(parseWeeklyData(weeklyJSON));
            setMonthly(parseMonthlyData(monthlyJSON));
          } catch(error) {
            // catch and handle/ignore errors/rejections
          } finally {
            // Clear loading status regardless of success/failure
            setIsLoading(false);
          }
        }
    
        updateStockDetails();
      }, []);
    
      const canShowGraph = newSymbol && intraday && daily && weekly && monthly;
    
      if (isLoading) {
        return <ActivityIndicator size='large' />
      } else {
        return (
          <ScrollView style={{ backgroundColor: black }}>
            <View
              style={{
                flex: 1,
                justifyContent: 'space-between',
                padding: 10
              }}
            >
              <View>
                {canShowGraph && (
                  <Graph
                    symbol={newSymbol} 
                    intraday={intraday}
                    daily={daily}
                    weekly={weekly}
                    monthly={monthly}
                  />
                )}
              </View>
              ...
            </View>
            ...
          </ScrollView>
        );
      }
    };
    

    It may also be a good idea to render some fallback UI in case the loading does fail and there isn't graph data to pass as props.

    Example:

    <View>
      {canShowGraph ? (
        <Graph
          symbol={newSymbol} 
          intraday={intraday}
          daily={daily}
          weekly={weekly}
          monthly={monthly}
        />
      ) : <View>No data.</View>}
    </View>