0

I need to save the ID of a field from the settings.tsx view. That value need to be used with other view, like stations.tsx, alarms.tsx, map.tsx.

If later, I need to watch another field, I can change the field from a drop menu and the new value must be used from the other field. It meens, I should be able to print the select ID filed from the other View, but it does not!

First I renamed /hook/fieldContext.ts to /hook/filedProvider.tsx. I needed to add tsx to avoid error message at FieldContext.Provider.

I copied a suggestion as the following

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>);

};

Q1: I suppose, the above code is not a hook any more because of tsx. Should I move it to constants or components ?

Secondly: It has been asked to add <FieldProvider></FieldProvider> at the root of my application, where is the navigation. I am confuse.

Let me first introduce to my structure to better understand my probleme

app
- (tab)
-- map
--- layout.tsx
--- [id].tsx
-- settings
--- layout.tsx
--- setting.tsx
--- info.tsx
-- stations.tsx
-- index.tsx
-- alarms.tsx
-- layout.tsx (add here)
- layout.tsx (or add here?)

My navigation look like

app/layout.tsx

import { Stack } from "expo-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient

export default function Layout() {
  
  return (
    <QueryClientProvider client={queryClient}>
      <Stack>
        <Stack.Screen 
          name="(tabs)" 
          options={{ 
            headerShown: false
          }} 
        />
        <Stack.Screen 
          name="+not-found"
          /*
          options={{
            headerShown: false,
          }} 
          */
        />
      </Stack>
    </QueryClientProvider>
    );
}

and

app/(tab)/layout.tsx

I added <FieldProvider>

import FontAwesome from '@expo/vector-icons/FontAwesome6';
import { Tabs} from 'expo-router';
import { StyleSheet } from "react-native";
import { useThemeColors } from '@/hooks/useThemeColors';
import { useNavigation } from '@react-navigation/native';
import { useState } from "react";
import { FieldProvider } from '@/hooks/fieldProvider';


export default function TabLayout() {
  const colors= useThemeColors()
  const navigation = useNavigation();

   const [sortKey, setSortKey] = useState<"id_station" | "station_name">("id_station")
  
  return (
 <FieldProvider>
  <Tabs screenOptions={{ tabBarActiveTintColor: 'green'}}>

    <Tabs.Screen
      name="index"
      options={{
        headerShown: false,
        title: 'Home',
        headerStyle: {backgroundColor: colors.appHeadBackgroundColor, borderColor:'red'},
        headerTintColor: '#000',
        headerTitleStyle: {
          fontWeight: 'normal',
        },

  
        tabBarIcon: ({ color }) => <FontAwesome size={28} name="arrows-to-circle" color={color} />,
    
        tabBarStyle: {
          display:'none',
          backgroundColor: colors.appTabBarBackgroundColor,
          paddingTop:8,
          height:70,
        }
      }}
    />

    <Tabs.Screen
      name="map"
      options={{
        title: 'Map',
        headerShown: false,
        headerStyle: { backgroundColor: colors.appHeadBackgroundColor},
        headerTintColor: '#000',
        headerTitleStyle: {
          fontWeight: 'normal',
        },
        tabBarIcon: ({ color }) => <FontAwesome size={28} name="map-location-dot" color={color} />,
        //headerTitle: props => <LogoTitle />,
        tabBarStyle: {
          backgroundColor: colors.appTabBarBackgroundColor,
          //paddingTop:8,
          height:70,
        }
      }}
    />

    <Tabs.Screen
      name="station"
      options={{
        headerStyle: { 
          backgroundColor: colors.appHeadBackgroundColor
        },
        headerTintColor: '#000',
        headerTitleStyle: {
          fontWeight: 'normal',
        },
        headerShown: false,
        href: null,
        tabBarIcon: ({ color }) => <FontAwesome size={28} name="chart-line" color={color} />,
        tabBarStyle: {
          //backgroundColor: colors.appTabBarBackgroundColor,
          //paddingTop:8,
          height:70,
        }
      }}
    />  
    

    <Tabs.Screen
      name="alarmes"
      options={{
        title: 'Alarmes',
        headerShown: false,
        headerStyle: { backgroundColor: colors.appHeadBackgroundColor},
        headerTintColor: '#000',
        headerTitleStyle: {
          fontWeight: 'normal',
        },
        
        /*
        headerRight: () => (
          <Button onPress={() => router.back()}>Back</Button>
        ),
        */
        tabBarIcon: ({ color }) => <FontAwesome size={28} name="bell" color={color} />,
        //headerTitle: props => <LogoTitle />,
        tabBarStyle: {
          //backgroundColor: colors.appTabBarBackgroundColor,
          //paddingTop:8,
          height:70,
          //borderWidth:30,
        }
      }}
    />

    <Tabs.Screen
            name="stations"
            options={{
              title: 'Stations',
              headerShown: false,
              headerStyle: { backgroundColor: colors.appHeadBackgroundColor},
              headerTintColor: '#000',
              headerTitleStyle: {
                fontWeight: 'normal',
              },
              tabBarIcon: ({ color }) => <FontAwesome size={28} name="map-pin" color={color} />,
              tabBarStyle: {
                //backgroundColor: colors.appTabBarBackgroundColor,
                height:70,
              }
            
            }}
        />

    <Tabs.Screen
          name="settings"
          options={{
            title: 'Paramètres',
            headerShown: false,
            headerStyle: { backgroundColor: colors.appHeadBackgroundColor },
            headerTintColor: '#000',
            headerTitleStyle: {
              fontWeight: 'normal',
            },
            /*
            headerRight: () => (
              <Button onPress={() => router.back()}>Back</Button>
            ),
            */
            tabBarIcon: ({ color }) => <FontAwesome size={28} name="gear" color={color} />,
            //headerTitle: props => <LogoTitle />,
            tabBarStyle: {
              backgroundColor: colors.appTabBarBackgroundColor,
              //paddingTop:8,
              height:70,
            }
          }}
        />
      
  </Tabs>
  </FieldProvider>


  );
}

I was not really sure if it should go to at app/layout.tsx or at app/(tab)/layout.tsx (I tried both)

Problem: How can I get the value in my view settings.tsx and map.tsx. I tried the following in my view alert.tsx (it need it as well) as that view is very simple at the moment.

Here is what I tried

import { Image, View } from "react-native";
import { useContext, useEffect, useState } from "react";
import { RootView } from "@/components/RootView";
import { ThemedText } from "@/components/ThemedText";
import { Row } from "@/components/Row";
import css from "@/hooks/styles";
import { useThemeColors } from "@/hooks/useThemeColors";
import AsyncStorage from '@react-native-async-storage/async-storage';
import { imagesHeader } from "@/components/imagesHeader";
import { FieldContext, FieldProvider } from "@/hooks/fieldProvider";

export default function Alarms() {

    const colors = useThemeColors()
    
    const field = useContext(FieldContext)
    
    return(
    <RootView>
      
        <View style={[{backgroundColor: colors.appHeadBackgroundColor }]}>
          <View style={css.jumbotronContainer}>
            <Image
              style={css.jumbotronImage}
              source={imagesHeader.alarms.uri}
            />       
          </View>
        </View>
        
        
        <View style={css.container}>
            <Row>
                <ThemedText>Terrain sélectionner: </ThemedText>
           
                <ThemedText>{field}</ThemedText>
  
            </Row>
            <ThemedText style={[css.text,css.paragraphe]}>Aucune station en alarme</ThemedText>
        </View>
    </RootView>
    )
    
}

but {field} does not print anything. It show an error.

and from settings.tsx, how my drop menu can update the value when the function?

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

Many thanks for your help!!

1
  • most probaly u not wrapping context in app(root) with children. React about how to use react context. Commented Jun 1 at 13:37

1 Answer 1

0

I found a solution.

Résumé of the scenario From my setting.tsx, I can select a field to monitor some stations registered for the selected field. If I move to stations.tsx, map.tsx, alarms.tsx, the stations must be filtered with the selected field. If I return to settings.tsx and I select another field, the selected field must be taken in consideration in map.tsx, stations.tsx and alarms.tsx. If I close the application, and I later open it, the selected field must be the same (memory not lost).

Solution I have created (thanks to Khurshid) a hook (I still don't know if it should be a hook, a constant or a component)

fieldProvider.tsx

import React, { createContext, useState, ReactNode } from 'react';
import { Text } from "react-native";

type FieldContextType = {
  idField: string;
  setIdField: (value: string) => void;
};

export const FieldContext = createContext<FieldContextType | undefined>(undefined);

export const FieldProvider = ({ children }: { children: ReactNode }) => {
  const [idField, setIdField] = useState("-1");

  return (<FieldContext.Provider value={{ idField, setIdField }}>
    {children}
  </FieldContext.Provider>);

};

I wrapped my menu between <FieldProvider> in app/layout.tsx

import { Stack } from "expo-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { FieldProvider } from "@/hooks/fieldProvider";

const queryClient = new QueryClient

export default function Layout() {
  
  return (
    <FieldProvider>
      <QueryClientProvider client={queryClient}>
        <Stack>
          <Stack.Screen 
            name="(tabs)" 
            options={{ 
              headerShown: false
            }} 
          />
          <Stack.Screen 
            name="+not-found"
            /*
            options={{
              headerShown: false,
            }} 
            */
          />
        </Stack>
      </QueryClientProvider>
    </FieldProvider>
    );
}

In my alarms.tsx, I added const field = useContext(FieldContext) and <ThemedText>{field?.idField}</ThemedText> to print the recorded field

import { Image, View } from "react-native";
import { useContext, useEffect, useState } from "react";
import { RootView } from "@/components/RootView";
import { ThemedText } from "@/components/ThemedText";
import { Row } from "@/components/Row";
import css from "@/hooks/styles";
import { useThemeColors } from "@/hooks/useThemeColors";
import AsyncStorage from '@react-native-async-storage/async-storage';
import { imagesHeader } from "@/components/imagesHeader";
import { FieldContext, FieldProvider } from "@/hooks/fieldProvider";

export default function Alarms() {

    const colors = useThemeColors()
    
    const field = useContext(FieldContext)
    
    return(
    <RootView>
      <FieldContext.Provider value={field}>
      <View style={[{backgroundColor: colors.appHeadBackgroundColor }]}>
        <View style={css.jumbotronContainer}>
          <Image
            style={css.jumbotronImage}
            source={imagesHeader.alarms.uri}
          />       
        </View>
      </View>

      <View style={css.container}>
        <Row>
          <ThemedText>Terrain sélectionner: </ThemedText> 
 
          <ThemedText>{field?.idField}</ThemedText>
        </Row>
        <ThemedText style={[css.text,css.paragraphe]}>Aucune station en alarme</ThemedText>
      </View>
      </FieldContext.Provider>
    </RootView>
    )
}

At this point, it prints

-1

in setting.tsx I added const field = useContext(FieldContext) and field?.setIdField(value). Here is the full code

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, FieldProvider } from "@/hooks/fieldProvider";


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 field = useContext(FieldContext)

  // https://stackoverflow.com/questions/62380874/what-is-the-right-place-to-call-a-function-before-render-in-react
  useEffect(()=>{
 
  }, [])

  function onFieldChange(value:string) {    
    field?.setIdField(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.alarms.uri}
            />       
          </View>
        </View>
        
        {isFetching ? <ActivityIndicator style={css.activityIndicator} color={colors.greenDark} />:null}
        
        <View style={css.container}>
          <Card style={{flex:1, borderWidth:0}}>
       
              <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?.idField}
                onValueChange={(value:any) => onFieldChange(value)}
                primaryColor={'green'}
                dropdownStyle={{
                  borderWidth: 1, // To remove border, set borderWidth to 0
                  borderColor: colors.grayLight,
                  alignItems: "center",
                }}
              />
              <ThemedText>Field (useContext): {field?.idField}</ThemedText>
          </Card>
        </View>
      </RootView>
    );
  }

As you can see above, I used field?.idField to select the value of drop menu. When you change the value of the drop menu, to select another field to be used while filtering the station, the function onFieldChange is called and I save the select field into the context

function onFieldChange(value:string) {    
  field?.setIdField(value)
}

Now, if I navigate from settings.tsx, stations.tsx or alarms.tsx the field ID is the same as the latest selection of the drop menu.

It works fine for now.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.