react-nativereact-context

Problem using useContext with different views/pages


I am a novice and I need to use a parameter for all view/page of my React Native App.

Scenario: From my setting.tsx view, I can select a field as a preference. From the stations.tsx view, I use queryFetch to request all stations and measures form my Database (it works fine). If I previously selected a field from setting.tsx, and then I go to the stations.tsx, the selected field ID must be shown. If I return to setting.tsx and changed the field and I retrun to stations.tsx, stations.tsx must display the new selected field. Later, I will filter all registered stations for the selected field.

Problem: I tried to implement useContext bud sadly, the selected field is not updated between <fieldContext.Provider value={field}> .. </fieldContext.Provider>. If I move to stations.tsx view, the view does not print the ID of the selected field. If I change and I select another field from setting.tsx, the stations.tsx view still does not print the correct field ID. I suppose my hook useContext.ts is wrong and I would appreciate you help.

Code and Description: I first created a hook fieldContext.tsx (I commented some code which display an error)

import { createContext, useState } from 'react';

export const fieldContext = createContext("-1");
/*
function fieldContextProvider (){
    //const [field, setField] = useState("-1")

    return <fieldContext.Provider value={field}> </fieldContext.Provider>
}
*/
export default fieldContext;

My setting.tsx view contains a drop box menu. When the value changes from the drop mneu, the function onFieldChange changes the state setField. I wrapped into <fieldContext.Provider value={field}><fieldContext.Provider> the drop menu and what I hope to see when the drop menu changed it value

import { useEffect, useState, useContext } from "react";
import { ActivityIndicator, StyleSheet, Image, Text, View, Pressable, ScrollView } from "react-native";
import { RootView } from "@/components/RootView";
import { Card } from "@/components/Card";
import { ThemedText } from "@/components/ThemedText";
import { useFetchQuery, useInfiniteFetchQuery } from "@/hooks/useFetchQuery";
import { useThemeColors } from "@/hooks/useThemeColors";
import Dropdown from 'react-native-input-select';
import AsyncStorage from '@react-native-async-storage/async-storage';
import css from "@/hooks/styles";
import { imagesHeader } from "@/components/imagesHeader";
import {fieldContext} from "@/hooks/fieldContext";


type Fields = {
  index: number,
  value: string,
  label: string,
}

export default function Setting() {

  const {data, isFetching} = useFetchQuery("/getstation/0/[id]", {id: 0}) // Get all active fields
  const colors = useThemeColors()
  const fiii = useContext(fieldContext);

  const [field, setField] = useState("-1")

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

  function onFieldChange(value:string) {    
    setField(value)
  }

    
  const fields:Fields[] = []
  fields.push({
    index: -1,
    value: "-1",
    label: "Aucun",
    //field_city: f.field_city,
  })
 
  fields.push({
    index: 0,
    value: "0",
    label: "Tous",
    //field_city: f.field_city,
  })
  data?.fields.map((f, index) =>{
    fields.push({
      index: index,
      value: f.id_field.toString(),
      label: f.field_longname + "("+ f.id_field.toString() +")",
      //field_city: f.field_city,
    })
  })

    return (
      <RootView>

        <View style={[{backgroundColor: colors.appHeadBackgroundColor }]}>
          <View style={css.jumbotronContainer}>
            <Image
              style={css.jumbotronImage}
              source={imagesHeader.alarmes.uri}
            />       
          </View>
        </View>
        {isFetching ? <ActivityIndicator style={css.activityIndicator} color={colors.greenDark} />:null}
        <View style={css.container}>
          <Card style={{flex:1, borderWidth:0}}>
            <fieldContext.Provider value={field}>
              <ThemedText style={{marginVertical:10}} variant={"subtitle2"} >Selectionner un terrain à surveiller</ThemedText>
              <Dropdown
                label="Vous recevrez des alarmes uniquement pour le terrain sélectionné."
                placeholder="Select an option..."
                options = {fields}
                selectedValue={field}
                onValueChange={(value:any) => onFieldChange(value)}
                primaryColor={'green'}
                dropdownStyle={{
                  borderWidth: 1, // To remove border, set borderWidth to 0
                  borderColor: colors.grayLight,
                  alignItems: "center",
                }}
              />
              <ThemedText>Field (useState): {field}</ThemedText>
              <ThemedText>Field Context: {fiii}</ThemedText>
            </fieldContext.Provider>
          </Card>
        </View>
      </RootView>
    );
 }

As you can see above, I use those two line to see the ID of the select field

<ThemedText>Field (useState): {field}</ThemedText> <ThemedText>Field Context: {fiii}</ThemedText>

The problem, {fiii} alwas print the default value defined in fieldContext.tsx export const fieldContext = createContext("-1");

dispite the fact that I pass the field value here <fieldContext.Provider value={field}>

What do I expect: From setting.tsx, I need to print the selected field. But I first need to use the selected field in stations.tsx and map.tsx because those two view need to know the selected field to show the stations registered for the selected field. If I later change to another field to watch, the view map.tsx and stations.tsx need to know the new selected field.

I spend a lot of time to look for example with google, I followed some video but none could help me to do it correctly and I would appreciate your help to correct my mistake and what it missed to do.


Solution

  • import React, { createContext, useState, ReactNode } from 'react';
    
    type FieldContextType = {
      field: string;
      setField: (value: string) => void;
    };
    
    export const FieldContext = createContext<FieldContextType | undefined>(undefined);
    
    export const FieldProvider = ({ children }: { children: ReactNode }) => {
      const [field, setField] = useState("-1");
    
      return (
        <FieldContext.Provider value={{ field, setField }}>
          {children}
        </FieldContext.Provider>
      );
    };
    

    and wrap in root of application like below

    import React from 'react';
    import { FieldProvider } from './path/to/fieldContext';
    import MainNavigator from './path/to/MainNavigator'; // Your main navigation component
    
    const App = () => {
      return (
        <FieldProvider>
          <MainNavigator />
        </FieldProvider>
      );
    };
    
    export default App;