2026-08-31 · Q&A guide

Dynamic React Native Search Header with React Navigation

Implement a state-driven search bar in React Navigation headers by replacing static navigationOptions with dynamic components.

Why Static Options Fail

The core issue in your snippet is using a module-level variable `var search = false`. React Navigation does not re-render the header when external variables change because it lacks a dependency tracking mechanism for plain variables.

To make the header reactive, the state controlling the UI must live within the component tree. You need to lift the state to the screen component or use a context provider if the state is shared across multiple screens.

Lifting State to the Component

Move the `search` boolean into the state of your screen component. Use `useState` for functional components or `this.setState` for class components.

Pass this state value into the `navigationOptions` function. Since `navigationOptions` is a function, it re-executes when the component re-renders due to state changes, allowing the header to update dynamically.

Implementing the Dynamic Header

Define the header elements conditionally based on the state. When `isSearching` is true, swap the menu icon for a back arrow and replace the text title with a `TextInput`.

Ensure the `TextInput` handles focus and blur events to toggle the state back to false when the user dismisses the search or presses cancel.

Code Implementation

Here is a functional component example using React Navigation v5+ syntax. Note that `navigationOptions` is now a function that receives the `route` and `navigation` props, but we can also use the `useNavigation` hook or pass state via the route params if needed. However, the cleanest way is to define `navigationOptions` as a function property that accesses the component's state via a closure or by defining it inside the component.

Actually, in modern React Navigation, it is better to use the `navigation.setOptions` API inside `useEffect` or event handlers to update the header dynamically without relying on static prop definitions.

import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet } from 'react-native';
import { useNavigation } from '@react-navigation/native';

const MyScreen = () => {
  const [isSearching, setIsSearching] = useState(false);
  const navigation = useNavigation();

  useEffect(() => {
    if (isSearching) {
      navigation.setOptions({
        headerTitle: () => (
          <TextInput
            style={{ flex: 1 }}
            placeholder="Search..."
            autoFocus
          />
        ),
        headerLeft: () => (
          <TouchableOpacity onPress={() => setIsSearching(false)}>
            <Text>Back</Text>
          </TouchableOpacity>
        ),
      });
    } else {
      navigation.setOptions({
        headerTitle: 'My Title',
        headerLeft: () => (
          <TouchableOpacity onPress={() => {}}>
            <Text>Menu</Text>
          </TouchableOpacity>
        ),
      });
    }
  }, [isSearching, navigation]);

  return (
    <View style={styles.container}>
      <TouchableOpacity onPress={() => setIsSearching(true)}>
        <Text>Search Icon</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});

Handling Focus and Blur

Attach `onFocus` and `onBlur` handlers to the `TextInput`. If the user taps outside or presses the keyboard's return key, trigger the state change to reset the header.

You can also add a 'Cancel' button in the `headerRight` when in search mode to provide an explicit exit path, mimicking native iOS behavior.

Takeaway: Use navigation.setOptions inside a useEffect hook to dynamically update header elements based on component state.

People also ask

Can I use context for this?

Yes, if multiple screens need to trigger the same search state, a React Context provider is more scalable than passing props.

Why not use navigationOptions property?

Static navigationOptions do not re-evaluate on state changes. Dynamic updates require the imperative setOptions API or function-based options tied to re-renders.

Inspired by a public discussion on Stack Overflow. This article is an original explanation for learners.

← All posts