Search code examples
javascriptreact-nativeuse-statereact-navigation-v5

React Native: Passing useState() data to unrelated screens


Explanation: I am creating a fitness app, my fitness app has a component called WorkoutTimer that connects to the workout screen, and that screen is accessed via the HomeScreen. Inside the WorkoutTimer, I have an exerciseCount useState() that counts every time the timer does a complete loop (onto the next exercise). I have a different screen called StatsScreen which is accessed via the HomeScreen tab that I plan to display (and save) the number of exercises completed.

What I've done: I have quite literally spent all day researching around this, but it seems a bit harder with unrelated screens. I saw I might have to use useContext() but it seemed super difficult. I am fairly new to react native so I am trying my best haha! I have attached the code for each screen I think is needed, and attached a screenshot of my homeScreen tab so you can get a feel of how my application works.

WorkoutTimer.js

import React, { useState, useEffect, useRef } from "react";
import {
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  Button,
  Animated,
  Image,
  SafeAreaView,
} from "react-native";

import { CountdownCircleTimer } from "react-native-countdown-circle-timer";
import { Colors } from "../colors/Colors";

export default function WorkoutTimer() {
  const [count, setCount] = useState(1);
  const [exerciseCount, setExerciseCount] = useState(0);
  const [workoutCount, setWorkoutCount] = useState(0);

  const exercise = new Array(21);
  exercise[1] = require("../assets/FR1.png");
  exercise[2] = require("../assets/FR2.png");
  exercise[3] = require("../assets/FR3.png");
  exercise[4] = require("../assets/FR4.png");
  exercise[5] = require("../assets/FR5.png");
  exercise[6] = require("../assets/FR6.png");
  exercise[7] = require("../assets/FR7.png");
  exercise[8] = require("../assets/FR8.png");
  exercise[9] = require("../assets/S1.png");
  exercise[10] = require("../assets/S2.png");
  exercise[11] = require("../assets/S3.png");
  exercise[12] = require("../assets/S4.png");
  exercise[13] = require("../assets/S5.png");
  exercise[14] = require("../assets/S6.png");
  exercise[15] = require("../assets/S7.png");
  exercise[16] = require("../assets/S8.png");
  exercise[17] = require("../assets/S9.png");
  exercise[18] = require("../assets/S10.png");
  exercise[19] = require("../assets/S11.png");
  exercise[20] = require("../assets/S12.png");
  exercise[21] = require("../assets/S13.png");

  return (
    <View style={styles.container}>
      <View style={styles.timerCont}>
        <CountdownCircleTimer
          isPlaying
          duration={45}
          size={240}
          colors={"#7B4FFF"}
          onComplete={() => {
            setCount((prevState) => prevState + 1);
            setExerciseCount((prevState) => prevState + 1);

            if (count == 21) {
              return [false, 0];
            }
            return [(true, 1000)]; // repeat animation for one second
          }}
        >
          {({ remainingTime, animatedColor }) => (
            <View>
              <Image
                source={exercise[count]}
                style={{
                  width: 150,
                  height: 150,
                }}
              />
              <View style={styles.timeOutside}>
                <Animated.Text
                  style={{
                    color: animatedColor,
                    fontSize: 18,
                    position: "absolute",
                    marginTop: 67,
                    marginLeft: 35,
                  }}
                >
                  {remainingTime}
                </Animated.Text>
                <Text style={styles.value}>seconds</Text>
              </View>
            </View>
          )}
        </CountdownCircleTimer>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({})
    

WorkoutScreen.js

import React, { useState } from "react";
import { StyleSheet, Text, View } from "react-native";

import WorkoutTimer from "../components/WorkoutTimer";

export default function WorkoutScreen() {
  return (
    <View style={styles.container}>
      <WorkoutTimer />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});
    

HomeScreen.js

import React from "react";
import { StyleSheet, Text, View, SafeAreaView, Button } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { AntDesign } from "@expo/vector-icons";

import { Colors } from "../colors/Colors";

export default function HomeScreen({ navigation }) {
  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.pageRef}>SUMMARY</Text>
      <Text style={styles.heading}>STRETCH & ROLL</Text>
      <View style={styles.content}>
        <TouchableOpacity
          style={styles.timerDefault}
          onPress={() => navigation.navigate("WorkoutScreen")}
        >
          <Button title="START WORKOUT" color={Colors.primary} />
        </TouchableOpacity>
        <TouchableOpacity
          style={styles.statContainer}
          onPress={() => navigation.navigate("StatsScreen")}
        >
          <AntDesign name="barschart" size={18} color={Colors.primary} />
          <Text style={{ color: Colors.primary }}>Statistics</Text>
          <AntDesign name="book" size={18} color={Colors.primary} />
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({})
    

StatsScreen.js

import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";

export default function StatsScreen() {
  return (
    <View style={styles.container}>
      <Text display={exerciseCount} style={styles.exerciseText}>
        {exerciseCount}
      </Text>
      <Text display={workoutCount} style={styles.workoutText}>
        {workoutCount}
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({});

Home Screen Image


Solution

  • As far as I can tell, you're almost there! You're trying to get your 2 state variables from the WorkoutTimer like this:

    import { exerciseCount, workoutCount } from "../components/WorkoutTimer";
    

    Unfortunatly this won't work :( . These two variables change throughout your App's life-time and that kinda makes them "special".

    In React, these kinds of variables need to be declared in a parent component and passed along to all children, which are interested in them. So in your current Setup you have a parent child relationship like:

    HomeScreen -> WorkoutScreen -> WorkoutTimer.
    

    If you move the variables to HomeScreen (HomeScreen.js)

    export default function HomeScreen({ navigation }) {
      const [exerciseCount, setExerciseCount] = useState(0);
      const [workoutCount, setWorkoutCount] = useState(0);
    

    you can then pass them along to WorkoutScreen or StatsScreen with something like:

    navigation.navigate("WorkoutScreen", { exerciseCount })
    navigation.navigate("StatsScreen", { exerciseCount })
    

    You'll probably have to read up on react-navigation's documentation for .navigate I'm not sure I remember this correctly.

    In order to read the variable you can then:

      export default function WorkoutScreen({ navigation }) {
        const exerciseCount  = navigation.getParam(exerciseCount);
    
        return (
          <View style={styles.container}>
            <WorkoutTimer exerciseCount={exerciseCount} />
          </View>
        );
      }
    

    and finally show it in the WorkoutTimer:

    export default function WorkoutTimer({ exerciseCount }) {
    

    Of course that's just part of the solution, since you'll also have to pass along a way to update your variables (setExerciseCount and setWorkoutCount).

    I encourage you to read through the links I posted and try to get this to work. After you've accumulated a few of these stateful variables, you might also want to look at Redux, but this is a bit much for now.

    Your app looks cool, keep at it!