Tristan Heilman
Tristan Heilman

Reputation: 328

How to implement a shared Search Bar input across stack/tab navigators using react-navigation?

Snapchat's UI is currently setup with a floating SearchBar header that appears to be shared across a few screens/tabs. I'd like to replicate the shared SearchBar header using react-navigation. I currently have a half working solution...

Half working solution

Currently even though I have the headerTitle set on the StackNavigator, it appears that the header is rendering a brand new SearchBar (you can see the slight flicker indicating its rendering) upon navigation to the search results screen.

Here is the setup I have currently for one of the Stacks inside my TabNavigator.

function NetworkStack({ route, navigation }) {
    return (
        <Network.Navigator
            initialRouteName="NetworkEventList"
            screenOptions={({ navigation, route }) => ({
                headerTitle: () => <Search navigation={navigation} route={route} stackName={"NetworkStack"}/>,
            })}>
            <Network.Screen
                name="NetworkSearchResults"
                component={SearchResults}
                options={({ navigation, route }) => ({
                    //headerTitle: () => <Search navigation={navigation} route={route} focused={true} stackName={"NetworkStack"}/>,
                    headerBackImage: () => <BackButton navigation={navigation} shouldPop={true}/>,
                    headerBackTitleVisible: false,
                    gestureEnabled: true
                })}/>
            <Network.Screen
                name="NetworkEventList"
                component={NetworkEventList}
                options={({ navigation, route }) => ({
                    headerLeft: () => <ProfileSidebarButton navigation={navigation}/>,
                    //headerTitle: () => <Search navigation={navigation} focused={false} stackName={"NetworkStack"}/>,
                    headerRight: () => <CommunityButton navigation={navigation} stackName={"NetworkStack"}/>
                })}/>
        </Network.Navigator>
    )
}

Below is my TabNavigator.

function TabNavigator({ navigation, route }) {
    return (
        <Tab.Navigator
            initialRouteName="NetworkStack"
            tabBar={props => <TabBar {...props}/>}>
            <Tab.Screen
                name="CheckInStack"
                component={CheckInStack}/>
            <Tab.Screen
                name="NetworkStack"
                component={NetworkStack}/>
            <Tab.Screen
                name="MapStack"
                component={MapStack}/>
        </Tab.Navigator>
    );
}

The logic that navigates to the search results component is inside the onFocus listener of the input. Here is the code for that...

const searchBarFocus = () => {
        switch(props.stackName) {
            case "MapStack":
                var searchType = props.searchGoogle ? "AddEstablishment" : "ViewingEstablishments";
                props.navigation.navigate('MapSearchResults', {searchType: searchType});
                break;
            case "NetworkStack":
                props.addingMarkers(false);
                var searchType = props.searchForPosting ? "ViewingEstablishments" : "ViewingUsers";
                let index = null;
                let routeState = props.route.state;
                if(routeState) index = routeState.index;
                if(index !== 1) {
                    console.log(props.navigation);
                    props.navigation.navigate('NetworkSearchResults', {searchType: searchType});
                }
                break;
            case "CheckInStack":
                props.addingMarkers(false);
                props.navigation.navigate('CheckInSearchResults', {searchType: "ViewingUsers"});
                break;
        }
    }

How would I go about configuring my navigation elements so that I have a singular SearchBar element that mounts one time? You can see in the gif I uploaded that the searchbar also loses focus upon navigation, this is also due to the second rendering/mounting of my Search component. Any suggestions would be much appreciated!

Upvotes: 4

Views: 5576

Answers (2)

ndotie
ndotie

Reputation: 2160

I think, the good way is to create just a separate component with intention to be a searching component, then when searching happens the results is stored on the global state, as context or redux store

//search.js

const SearchBar = (props) => {...}; //which have access to the global state

//then on your routes 

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{
          headerTitle: props => <LogoTitle {...props} />,
          headerRight: () => (
            <SearchBar/>
          ),
        }}
      />

    <Stack.Screen
        name="Users"
        component={UsersScreen}
        options={{
          headerTitle: props => <LogoTitle {...props} />,
          headerRight: () => (
            <SearchBar/>
          ),
        }}
      />
    </Stack.Navigator>
  );
}

Upvotes: 0

Tristan Heilman
Tristan Heilman

Reputation: 328

I was able to find a solution to my question. Feel free to comment or supply a better answer. I use the react-native-elements Search Bar to create my searching/input element at the header of my navigation stacks. Originally this was a View wrapping the SearchBar component. I changed this to a TouchableOpacity so that I could listen for onPress event. This is the new element I constructed. I kept the original navigation configuration that was supplied in the question.

return (
        <TouchableOpacity style={[styles.mainContainer, {width: SEARCH_BAR_WIDTH_UNFOCUSED}]} onPress={() => searchBarPress()}>
            <SearchBar
                id={"searchBar-"+ props.stackName}
                platform="ios"
                ref={search => searchRef = search}
                value={searchInput}
                containerStyle={styles.searchInputContainerWrapper}
                inputContainerStyle={styles.searchInputContainer}
                inputStyle={{color: styles.inputContainer.color}}
                round={true}
                autoFocus={props.route.params ? true : false}
                pointerEvents={props.route.params ? "auto" : "none"}
                cancelButtonTitle="Cancel"
                cancelButtonProps={{color: '#707070'}}
                onChangeText={updateSearch}
                //onFocus={searchBarFocus}
                onCancel={() => console.log("Cancel")}
            />
        </TouchableOpacity>
    )

The key parts of creating the snapchat like search bar header is the autoFocus and pointerEvents properties. The property values needs to be dependent upon which screen the user is currently on.

enter image description here

Upvotes: 0

Related Questions