skip to Main Content

My app has a calendar component that displays content for each day as you swipe horizontally (similar to the iOS Calendar app). I implemented this with a FlatList which renders a DayContent component for each day.

Each DayContent component has its own local state populated on render by reading from the database for the specified date. For simplicities sake, let’s say each DayContent component displays a counter and a button that takes you to a new screen EditCount to edit the counter (this needs to be a separate screen since in my application the edit is more complicated than increment/decrement).

Here is the problem:

I first thought about implementing this EditCount screen by passing count and setCount in the router params. However, I noticed that React Navigation passes router params as copies and not by reference, so when I call setCount in the EditCount screen, the count is not updated in the EditCount screen although it is updated back in the DayContent component.

I then considered storing the count and setCount variables in my central state management system (Context API). However, I am unsure how this would work since each day in the calendar needs its own count and setCount.

Ideally, I don’t want to update the database in EditCount, so if there are any other methods I would appreciate them.

I have been struggling with this seemingly straightforward problem for a few days now, please let me know if you have any ideas.

EDIT

The data I pass to my FlatList is an Array of length 7. The way I have it set up is that if I swipe to the edges of the array, (ie first, second, or second last, last index in the array) I recreate the array centred around the current day to create the illusion of an infinite list.

So every time I recreate this array of length 7, I assume it re-renders all the days and makes an API call to each one (ie 7 times on each re-render). Not sure if this is the most efficient method.

I am using Firebase Firestore to make my database requests, I haven’t looked too deeply into how the caching mechanism works, haven’t done any explicit caching though.

const DayContent = ({ dateStr }) => {
  const [count, setCount] = useState();

  useEffect(() => {
    const dbContent = getContentFromDate(dateStr);
    setCount(dbContent);
  }, []);

  const handleEdit = () => {
    navigation.navigate("EditCount", { count, setCount });
  };

  return (
    <View>
      <TouchableOpacity onPress={handleEdit}>
        <Text>Edit</Text>
      </TouchableOpacity>
      {count}
    </View>
  );
};

const EditCount = () => {
  const { count, setCount } = useRoute().params;

  return (
    <View>
      <TouchableOpacity onPress={navigation.goBack}>
        <Text>Go back</Text>
      </TouchableOpacity>=
      <TouchableOpacity onPress={setCount((prev) => prev + 1)}>
        <Text>Increment</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={setCount((prev) => prev - 1)}>
        <Text>Decrement</Text>
      </TouchableOpacity>
      {count} /////// Does not update
    </View>
  );
};

2

Answers


  1. I think you can update the EditCount screen’s count params value separately too, by running

    navigation.setParams({
      count: count + 1,
    });
    

    in addition to the original state update.

    But I think this is not the best approach because you will have 2 separate states, one in EditCount and another one in the DayContent.

    For a better solution I think you could use a global state management library (I have used Valtio for similar cases, but any other will work also) or context.

    The general logic would be like this:

    • set up the store, and save the day data into the store when you receive it from API, day data should object / array with some kind of ID for each day, you could use the date as the ID
    • create a mutation which will have 2 arguments: day ID and count, this mutation has to find the correct day from the store and then update the days count value
    • whenever some component changes the day’s the count, call out this mutation function with the day ID and new count value

    For doing this with Valtio, the code would look something like this

    Set up the store:

    import { proxy } from 'valtio'
    
    export const store = proxy({
        days: [],
    })
    

    Save the data to the store

    const setInitialDayCount = useCallback(() => {
        // from API
        const day = { dateStr: '2023-03-15', count: 0 }
        store.days.push(day)
    }, [])
    

    Update the day data when it is changed

    const updateDay = useCallback((dateStr: string, count: number) => {
        const day = store.days.find((day) => day.dateStr === dateStr)
        if (day) {
            day.count = count
        }
    }, [])
    

    Use the day’s count from global state

    const { days } = useSnapshot(store)
    
    const day = days.find((day) => day.dateStr === dateStr)
    const count = day?.count
    
    Login or Signup to reply.
  2. For readability, performance and scalability, consider using setParams if it meets your needs.

    const EditCountScreen = () => {
      const { setParams, goBack } = useNavigation();
      const { count, setCount } = useRoute().params;
    
      return (
        <SafeAreaView>
          <View style={styles.editCount}>
            <TouchableOpacity onPress={() => {
              setCount((prev) => prev + 1);
              setParams({ count: count + 1 });
            }}>
              <Text style={styles.h1}>+</Text>
            </TouchableOpacity>
            <Text style={styles.h1}>{count}</Text>
            <TouchableOpacity onPress={() => {
              setCount((prev) => prev - 1);
              setParams({ count: count - 1 });
            }}>
              <Text style={styles.h1}>-</Text>
            </TouchableOpacity>
          </View>
          <TouchableOpacity onPress={goBack}>
            <Text style={styles.h1}>Back</Text>
          </TouchableOpacity>
        </SafeAreaView>
      );
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search