Introduction
State in the app refers to the current status or condition of the app. It can be changed by user inputs or by downloading new data from the backend. The state is dynamic and changes over time due to user interactions or system events like notifications.
If I were to compare the state of an app to something, I'd compare it to a magic box that keeps important information about our application, such as whether there are any notifications, if the user is logged in, or something as simple as information about the type of theme.
Local / Component state
Component state is local to each component and can be shared only from parent to child components. It manages specific data for those components, such as form values or information fetched from the backend. This state is contained within the component, making it ideal for handling local data and UI changes.
For example, we can check if a button was pressed:
// App.tsx
import { useState } from 'react';
import { Pressable, Text, View } from 'react-native';
const App = () => {
const [pressed, setPressed] = useState(false);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{/*
Pressable component used as a button
It gets disabled once pressed
'onPress' updates the 'pressed' state to true when the button is pressed
*/}
<Pressable
disabled={pressed}
onPress={() => setPressed(true)}
style={{
borderRadius: 5,
paddingVertical: 8,
paddingHorizontal: 16,
backgroundColor: pressed ? 'grey' : 'blue',
}}>
<Text style={{ color: 'white' }}>{pressed ? 'Pressed' : 'Press'}</Text>
</Pressable>
</View>
);
};
export default App;
In this example, we check if the button has already been pressed. To practice this, try modifying the code above. Instead of checking if the button was pressed, change it to count how many times the user presses the button. You can display the count above the button or on the button itself.
Global state
The global state goes beyond individual components and is accessible throughout the entire application. Unlike the local state, which is confined to a specific component, the global state allows data to be shared and managed across multiple components. This is particularly useful for data that needs to be accessible by many parts of your application, such as user authentication status, application settings, etc.
The global state is not tied to any particular component's lifecycle and persists across the entire app. This makes it ideal for managing data and states that impact multiple areas of the application, promoting efficient data flow and synchronization between components.
As an example, we can create a Language Provider for our application that will inform all of our components about the current language:
// LanguageProvider.tsx
import React, { createContext, useContext, useState } from 'react';
// Type for our languages
type Language = 'en' | 'pl';
// Type for our context
type LanguageContextType =
| undefined
| {
language: Language;
changeLanguage: (lang: Language) => void;
};
// Creating the context
const LanguageContext = createContext<LanguageContextType>(undefined);
// Provider component that wraps your app and provides the language state
export const LanguageProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const [language, setLanguage] = useState<'en' | 'pl'>('en'); // Default language is English
const changeLanguage = (lang: Language) => setLanguage(lang);
return <LanguageContext.Provider value={{ language, changeLanguage }}>{children}</LanguageContext.Provider>;
};
// Custom hook to use the language context
export const useLanguage = () => {
const context = useContext(LanguageContext);
// If the context is undefined, it means this hook is being used outside a LanguageProvider.
// In such a case, we throw an error.
if (context === undefined) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};
At this moment, we have created our Language Provider. Let's see how we can use it:
// App.tsx
import React from 'react';
import { Pressable, Text, View } from 'react-native';
// Import our provider and hook
import { LanguageProvider, useLanguage } from './LanguageProvider';
// Create simple language selector component
const LanguageSelector = () => {
const { language, changeLanguage } = useLanguage();
return (
<View style={{ flexDirection: 'row', gap: 12 }}>
<Pressable
onPress={() => changeLanguage('en')}
style={{
borderRadius: 5,
paddingVertical: 8,
paddingHorizontal: 16,
backgroundColor: language === 'en' ? 'blue' : 'grey',
}}>
<Text style={{ color: 'white' }}>English</Text>
</Pressable>
<Pressable
onPress={() => changeLanguage('pl')}
style={{
borderRadius: 5,
paddingVertical: 8,
paddingHorizontal: 16,
backgroundColor: language === 'pl' ? 'blue' : 'grey',
}}>
<Text style={{ color: 'white' }}>Polski</Text>
</Pressable>
</View>
);
};
// Create component that will show greeting message
const Greeting = () => {
const { language } = useLanguage();
return <Text style={{ fontSize: 24, margin: 30 }}>{language === 'en' ? 'Hello!' : 'Cześć!'}</Text>;
};
// Setup everything in our app
const App = () => {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<LanguageProvider>
<Greeting />
<LanguageSelector />
</LanguageProvider>
</View>
);
};
export default App;
At this moment, you should see on the display a greeting with two buttons for changing the greeting language. Try moving the Greeting component outside the provider and wait for the results. If you move a component that uses the provider outside of the provider, you will see an error on your device.
Conclusion
At this point, you have explored local and global state management in React and React Native. This knowledge will assist you in managing local state using the useState hook, which is ideal for component-specific scenarios like form states or a component's UI state. On the other hand, global state is crucial for sharing data across the entire app or specific modules, such as settings or informations about the user.