1

I am creating a Reat Native app which connects to an API from which it gets data.

I am using React Navigation to handle navigation. The app has a Stack Navigator and a Bottom Tab Navigator. The StackNavigator has 4 screens:

  • SignupScreen which handles creating account;
  • LoginScreen for handlong log in;
  • SplashScreen that checks for a local token and logs in the user automatically;
  • A LoadingScreen that triggers the initial fetch call to the API, stores the response in state and navigates to the MainFlow screen;
  • A MainFlow screen that contains the TabNavigator.

The TabNavigator has two screens, FeedScreen, Account and More where the initial screen is FeedScreen.

The signup/login/local flows are all working fine.

The issue: Once the user is logged in successfully the LoadingScreen is triggering the API call but the MainFlow components are being rendered before the data is in state. Because the components in MainFlow need the data, an error is thrown. How can I render the FeedScreen components only once the data is there?

In the LoadingScreen I am triggering an API call on useEffect from a context object, QuestionContext:

const LoadingScreen = ({ navigation }) => {
  const [loading, setLoading] = useState(true);
  const { state: authState } = useContext(AuthContext);
  const { getQuestionsForUser, getAllQuestions } = useContext(QuestionContext);

  useEffect(() => {
    getAllQuestions();
  }, []);

  return (
    <View style={styles.container}>
      <YonStatusBar backgroundColor="#310B3B" />
      <Image source={splashLogo} containerStyle={styles.splashLogo} />
      <ActivityIndicator />
    </View>
  );
};

export default LoadingScreen;

getAllQuestions is a function in QuestionContext which makes the API call and navigates to FeedScreen:

const getAllQuestions = (dispatch) => {
  return async () => {
    try {
      const token = await AsyncStorage.getItem('token');
      const config = { headers: { Authorization: `Bearer ${token}` } };
      const response = await yonyonApi.get(`/questions`, config);
      dispatch({ type: 'GET_ALL_QUESTIONS', payload: response.data });
      RootNavigation.navigate('MainFlow');
    } catch (e) {
      console.log(e);
    }
  };
};

getAllQuestions is working fine: the API call is successful and I can see that the response is stored in state. However, it navigates to MainFlow before that happens.

Finally, this is the FeedScreen:

const FeedScreen = () => {
  const { state: questionState } = useContext(QuestionContext);

  return (
    <ScrollView style={styles.container}>
      {console.log(questionState.questions)}
      <View style={styles.listContainer}>
        <QuestionCard />
      </View>
    </ScrollView>
  );
};

export default FeedScreen;

The FeedScreen renders a QuestionCard which needs the data in questionState. This is what throwing the error: the QuestionCard is being rendered before the data is in state.

How can I make the navigation only navigate to FeedScreen once the necessary data is in state? Or alternatively, render something else than the QuestionCard while the data is not there and once the data is in questionState render the QuestionCard?

franciscofcosta
  • 833
  • 5
  • 25
  • 53

2 Answers2

4

For me i will use screen instead of two screens as follows :

 const FeedScreen = () => {

  const [loading, setLoading] = useState(true);
  const { state: authState } = useContext(AuthContext);
  const [data, setData] = useState([]);

const getAllQuestions = (dispatch) => {
  return async () => {
    try {
      const token = await AsyncStorage.getItem('token');
      const config = { headers: { Authorization: `Bearer ${token}` } };
      const response = await yonyonApi.get(`/questions`, config);
      setData(response.data)
      setLoading(false)
    } catch (e) {
      console.log(e);
    }
  };
};
  useEffect(() => {
    getAllQuestions();
  }, []);

      return (
        <ScrollView style={styles.container}>
          {
            (loading)?
             <ActivityIndicator/>
             :
             <View style={styles.listContainer}>
                <QuestionCard  data={data}/>
              </View>
           }
        </ScrollView>
      );
    };

    export default FeedScreen;
abdelrhman
  • 133
  • 9
  • Thank you for your. help. However, this doesn't allow me to use the state variables I have stored in the context. I wouldn't want to create local state in my screen but rather to use the one in the Context. The Context is, at the moment, taking care of all of the async operations such as making the API call. How can I, in the screen, wait for all those operations to be finished and state to be populated, before I render the QuestionCard component? Thank you – franciscofcosta May 27 '20 at 07:12
0

Why don't you set the initial state of your context to null and render your component if it is not null ?

const [questionState, setQuestionState] = useState(null); 

...

const FeedScreen = () => {
  const { state: questionState } = useContext(QuestionContext);

  return (
    <ScrollView style={styles.container}>
      {!!questionState?.questions && console.log(questionState.questions)}
      <View style={styles.listContainer}>
        <QuestionCard />
      </View>
    </ScrollView>
  );
};

export default FeedScreen;
mcnk
  • 1,690
  • 3
  • 20
  • 29