Search code examples
javascriptreact-nativereact-navigationreact-context

React Navigation and React Context


In our App we use a tab navigation and a stack navigation for each tab. We want an array of devices where we could add and delete devices. The array should be available on every tab.

This is our provider

import React from 'react'
const DevicesContext = React.createContext('')
export default DevicesContext

This is our app.js

import React, {useState} from 'react';
import uuid from 'react-native-uuid';
import { NavigationContainer } from '@react-navigation/native';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Feather } from '@expo/vector-icons';
import { MaterialIcons } from '@expo/vector-icons';

import HomeStackScreen from "./components/home/HomeStackScreen";
import ConnectStackScreen from "./components/connect/ConnectStackScreen";
import SettingsStackScreen from "./components/settings/SettingsStackScreen";

import DevicesContext from "./components/context/DevicesContext";

const Tab = createMaterialBottomTabNavigator();

const deleteItem = (id) => {
    setDevices(prevDevice => {
        return prevDevice.filter(device => device.id != id)
    })
    console.log(devices)
}

const addItem = (device) => {
    setDevices(prevDevices => {
        return [{id: uuid.v4(), name:device}, ...prevDevices];
    })
}
function MyTabs() {
    return (
        <Tab.Navigator
            initialRouteName="Home"
            activeColor="#E4E4E4"
            inactiveColor="#000000"
            shifting={true}
            labelStyle={{ fontSize: 12 }}
            barStyle={{ backgroundColor: '#8DFFBB' }}
        >

            <Tab.Screen
                name="Devices"
                component={ConnectStackScreen}
                options={{
                    tabBarLabel: 'Geräte',
                    tabBarIcon: ({ color }) => (
                        <MaterialIcons name="devices" size={24} color={color} />
                    ),
                }}
            />
            <Tab.Screen
                name="Home"
                component={HomeStackScreen}
                options={{
                    tabBarLabel: 'Home',
                    tabBarIcon: ({ color }) => (
                        <MaterialCommunityIcons name="home" color={color} size={26} />
                    ),
                }}
            />
            <Tab.Screen
                name="Settings"
                component={SettingsStackScreen}
                options={{
                    tabBarLabel: 'Einstellungen',
                    tabBarIcon: ({ color }) => (
                        <Feather name="settings" size={24} color={color} />
                    ),
                }}
            />
        </Tab.Navigator>
    );
}

export default function App() {
    const [devices, setDevices] = useState([
        {id: uuid.v4(), name: 'thing 1', ip: 5},
        {id: uuid.v4(), name: 'thing 2', ip: 2},
        {id: uuid.v4(), name: 'thing 3', ip: 6},
        {id: uuid.v4(), name: 'thing 4', ip: 10},
    ])
    return (
        <DevicesContext.Provider value={devices}>
        <NavigationContainer>

                <MyTabs />

        </NavigationContainer>
        </DevicesContext.Provider>
    );
}

this is our connect screen where we can add devices

import React, {useContext, useState} from 'react';
import {Text, View, Button, FlatList, StyleSheet, TouchableOpacity, Image} from 'react-native';
import uuid from 'react-native-uuid';
import ListItem from "../shared/ListItem";
import AddItem from "../shared/AddItem";
import DevicesContext from "../context/DevicesContext";

function ConnectScreen( {navigation}) {
    const [devices, setDevices] = useState(useContext(DevicesContext));

    const deleteItem = (id) => {
        setDevices(prevDevice => {
            return prevDevice.filter(device => device.id != id)
        })
        console.log(devices)
    }

    const addItem = (device) => {
        setDevices(prevDevices => {
            return [{id: uuid.v4(), name:device}, ...prevDevices];
        })
    }
    return (
        <View style={{padding: 10, flex: 1, justifyContent: 'center'}}>
            <View style={styles.AddNormal}>
                <AddItem addItem={addItem}></AddItem>
                <FlatList style={styles.List} data={devices} renderItem={({item}) => (
                    <ListItem item={item} deleteItem={deleteItem}></ListItem>
                )}/>
            </View>
            <View style={styles.AddQr}>
                <Image source={require('../../img/qr-code-url.png')}  style={{ width: 150, height: 150, marginBottom: 10 }} />
                <Text style={{ textAlign: 'center', marginBottom: 10 }}>Du kannst außerdem ein Gerät durch das scannen eines Qr-Code hinzufügen</Text>
                <TouchableOpacity onPress={() => navigation.navigate('QrCode')}style={styles.btn}>
                <Text style={styles.btnText}>Qr-Code scannen</Text>
            </TouchableOpacity>

            </View>
        </View>
    );
}
const styles = StyleSheet.create({
    List: {
        backgroundColor: '#E4E4E4',
    },
    AddNormal: {
        padding: 10, flex: 1,
    },
    AddQr: {
        backgroundColor: '#E4E4E4',
        padding: 30,
        flex: 1,
        marginTop: 20,
        marginBottom: 20,
        alignItems: 'center'
    },
    btn: {
        backgroundColor: '#8DFFBB',
        padding: 9,
        margin: 10,
    },
    btnText: {
        color: '#000',
        fontSize: 20,
        textAlign: 'center',
    }
});

export default ConnectScreen;

and this is our main screen

import React, {useState, useContext, useEffect} from 'react';
import {Button, FlatList, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity, View} from "react-native";
import {ServerOnOffSwitch, SendMessage} from "./network";
import DevicesContext from "../context/DevicesContext";


const Item = ({ item, onPress, backgroundColor, textColor }) => (
    <TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}>
        <Text style={[styles.title, textColor]}>{item.name}</Text>
    </TouchableOpacity>
);

function HomeScreen (){
    const [devices, setDevices] = useState(useContext(DevicesContext));

    const deleteItem = (id) => {
        setDevices(prevDevice => {
            return prevDevice.filter(device => device.id != id)
        })
    }
    const [selectedId, setSelectedId] = useState(null);

    const renderItem = ({ item }) => {
        const backgroundColor = item.id === selectedId ? "#b5b5b5" : "#ededed";
        const color = item.id === selectedId ? 'white' : 'black';

        return (
            <Item
                item={item}
                onPress={() => setSelectedId(item.id)}
                backgroundColor={{ backgroundColor }}
                textColor={{ color }}
            />
        );
    };

    return (
        <View style={{padding: 10, flex: 1, justifyContent: 'center'}}>

            <View style={{padding: 10, flex: 1}}>
                <Text style={styles.DeviceHeader}>Gerät auswählen</Text>
                <FlatList
                    data={devices}
                    renderItem={renderItem}
                    keyExtractor={(item) => item.id}
                    extraData={selectedId}
                />
            </View>

        <View style={{padding: 10, flex: 1, justifyContent: 'center', alignItems: 'center'}}>
            <SendMessage item={selectedId}></SendMessage>
            <ServerOnOffSwitch></ServerOnOffSwitch>
        </View>
        </View>
    );
}

const styles = StyleSheet.create({
    DeviceHeader: {
        fontSize: 22,
        paddingBottom: 10,
    },
    item: {
        padding: 10,
        backgroundColor: '#f8f8f8',
        borderBottomWidth: 1,
        borderColor: '#eee',
    },
    title: {
        fontSize: 18,
    },
});

export default HomeScreen;

If we add devices in our Connect screen they are getting updated there but not on the homescreen. Thanks for your help:)


Solution

  • to update context from nested component you must pass the methode setDevices tha will update it.
    to pass it do the following steps :

    your context shoud be

    import React from 'react'
    const DevicesContext = React.createContext({
        devices: [],
        setDevices: () => {}, //methode will update context value
        
    })
    export default DevicesContext
    

    App.js should be

    
    //define state
    const [devices, setDevices] = React.useState([])
    //define constexValue
    //we will pass `devices` and also `setDevices` that will update it.
    const DevicesContextValue = React.useMemo(() => ({ devices, setDevices}), [devices]);
    
    return (
      <DevicesContext.Provider value={DevicesContextValue}>
           ...
      </DevicesContext.Provider>
    );
    

    ConnectScreen.js should be

    function ConnectScreen(){
        const {devices, setDevices} = useContext(DevicesContext);
        //call setDevices will update context
        ....
    }
    

    HomeScreen.js should be

    function HomeScreen (){
        const {devices, setDevices} = useContext(DevicesContext);
        //use devices from context in your flatlist and when the context update the result will show in flatlist
        ....
    }