From Novice to Pro: Master React Navigation and Context API - Level 2
Learn how to use react navigation and context api in react native with typescript
We are gonna explore two things in this article.
Navigation between app screens and global state management.
For global state management, we will be using Context API.
Context API comes with the React library.
Global state management is basically keeping a central data store.
It also manages functions that work on the data to change its state.
This data and functions are then available from any screens.
Note that we are still not persisting data.
That is, when app restarts you won't get past data.
Goals
The goals are:
-
How react native apps navigate from one screen to another (React Navigation)
-
How data is accessed in different screens from a central store (Context API)
Design Plan
You should be creating wireframes first (hand-drawn or digital).
Refer to the wire framing section of past blog.
Here lets start with the high fidelity designs built with figma.
We will be needing three screens:
- Todo List Screen
- Add Todo Screen
- Todo Details Screen
Below they are shown.



React Navigation
React Navigation is the favourite library to navigate between screens.
Each screen is a component.
React navigation uses a stack-based navigation model since version 4.
The screen components are stored in a stack.
We have three different ways to display react navigation screens.
1. Stack Navigator
Stack navigator is the basic navigator which provides us with a raw navigation stack.
We get all the basic functions like
-
navigation between screens
-
exchanging data between screens
-
basic navigation functions like, goBack(), pop() etc.
2. Drawer Navigator
Drawer Navigator has all features of Stack Navigator and some more.
In app sometimes you see drawers transitioning from the side,
showing a list of different screens.
This is the drawer.
So this provides functions specific to playing with drawer
3. Tab Navigator
In Tab orientation you get the screen options in tabs at the bottom or top.
The rest of the different screens is displayed with it.
In this app, we are going to use Stack Navigator.
Starting Code and Final Code
We have learned how to make a basic todo app with local state management.
We had used react hooks to manage state locally.
MUI in react native paper was used to skip design thinking.
So this will be our starting code.
To know more about how we arrived here, go over past article.
The final code is here.
Implementing Navigator
Install the essential libraries
If your computer is not resource deficient, do use React Native Cli, not Expo.
You will be more confident.
If you know Cli, you will need no time learning to use Expo.
Follow installation instructions for React Navigation for React Native Cli.
Planning the screens
We need three screens.
- Screen to add or edit todo
- Screen to view full todo details
- Screen to list all todos
List screen won't need any data from other screens except global data.
We will use Context API for global state management.
Global state management is essential for react native navigation.
Otherwise there will be too much prop drilling.
Still a little data will have to be transferred from one screen to another.
The Add/Edit screen will need nothing if todo is added.
It will need the Task and Index of the task from List screen while editing.
Task will have type 'Task' defined as follow:
export interface Task {
pending: boolean;
description: string;
}And Index will have type number.
The View screen will need the same from List screen, as for Edit.
Because the view will be able to update todo status.
And the update status function will need the Task and its Index.
So our navigator type will be as follows:
export type RootStackParamList = {
List: undefined;
Details: {task: Task; index: number};
AddEdit: undefined | {task: Task; index: number};
};All of these will be in the types.tsx file in the root of the directory.
Now let's create the file: ./src/navigation/AppNavigator and define the navigator.
const AppNavigator = () => {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="List" component={TodoList} />
<Stack.Screen name="Details" component={ViewTodo} />
<Stack.Screen name="AddEdit" component={AddTodo} />
</Stack.Navigator>
</NavigationContainer>
);
};The screen "name"s defined above will be screen identifiers.
We will call them to navigate to a screen.
The components are the screens.
The components are made more reusable following Atomic Design here.
This is because there is component reuse.
Defining Navigation and Route Types:
The Navigation type has to be defined for screens that uses navigation functions.
The Route types has to be defined for screen that accept data from other screens.
We need the following types:
// Navigation Type for TodoList screen
export type ListScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList,
'List'
>;
// Navigation Type for AddEdit screen
export type AddEditScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList,
'AddEdit'
>;
// Routing Type for ViewTodo screen
export type ViewScreenRouteProp = RouteProp<RootStackParamList, 'Details'>;
// Routing Type for AddEdit screen
export type AddEditScreenRouteProp = RouteProp<RootStackParamList, 'AddEdit'>;Context API
Context API lets any component within the Context Provider work with global data and functions.
The TodoProvider function is created in the src/todoContext.tsx file.
const TodoProvider = ({children}: {children: ReactNode}) => {
const [tasks, setTasks] = useState<Task[]>([
{
pending: true,
description: 'Be a pro in React Native',
},
]);
// functions
const addTodo = (todo: string) => {
let task = {
pending: true,
description: todo,
};
setTasks([...tasks, task]);
};
const updateTodoStatus = (index: number) => {
const updatedTasks = [...tasks];
updatedTasks[index].pending = !updatedTasks[index].pending;
setTasks(updatedTasks);
};
const updateTodoDetails = (index: number, description: string) => {
const updatedTasks = [...tasks];
updatedTasks[index].description = description;
setTasks(updatedTasks);
};
const deleteTodo = (index: number) => {
const updatedTasks = [...tasks];
updatedTasks.splice(index, 1);
setTasks(updatedTasks);
};
return (
<TodoContext.Provider
value={{
tasks,
addTodo,
updateTodoDetails,
updateTodoStatus,
deleteTodo,
}}>
{children}
</TodoContext.Provider>
);
};This functions encapsulates the App Navigator in App.tsx.
<TodoProvider>
<AppNavigator />
</TodoProvider>To get access to the data and functions, we call the context in the component we need.
const {tasks} = useContext(TodoContext) as TodoContextType;
const {updateTodoStatus} = useContext(TodoContext) as TodoContextType;This is the type it needs.
export type TodoContextType = {
tasks: Task[];
addTodo: (todo: string) => void;
updateTodoDetails: (index: number, description: string) => void;
updateTodoStatus: (index: number) => void;
deleteTodo: (index: number) => void;
};Component optimisations
Now that we have a central store of data, we can make smaller reusable components.
Like...
const CustomCheckBox = ({item, index}: {item: Task; index: number}) => {
const {updateTodoStatus} = useContext(TodoContext) as TodoContextType;
return (
<Checkbox.Android
status={!item.pending ? 'checked' : 'unchecked'}
onPress={() => updateTodoStatus(index)}
color="#4F4F4F"
/>
);
};Next Steps
In this problem we are using many functions in Context API to manage the data.
This is not maintainable.
We can optimise it using a reducer function.
Follow this guide to modify it.