Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expo - Unable to set default text for header search bar using ref #2392

Open
Rezrazi opened this issue Oct 7, 2024 · 2 comments
Open

Expo - Unable to set default text for header search bar using ref #2392

Rezrazi opened this issue Oct 7, 2024 · 2 comments
Assignees
Labels
Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided

Comments

@Rezrazi
Copy link

Rezrazi commented Oct 7, 2024

Description

When trying to imperatively set the text of the search bar input using a ref, it doesn't work as ref is always null on render.

Steps to reproduce

import { Stack } from "expo-router";
import { useEffect, useRef } from "react";
import { SearchBarCommands } from "react-native-screens";

export default function HomeScreen() {
  const ref = useRef<SearchBarCommands>(null);

  useEffect(() => {
    // > null
    console.log(ref.current);

    ref.current?.setText("hey");
  }, []);

  return (
    <>
      <Stack.Screen
        options={{
          title: "Hello world",
          headerShown: true,
          headerSearchBarOptions: {
            placeholder: "Search...",
            placement: "stacked",
            hideNavigationBar: false,
            autoFocus: true,
            hideWhenScrolling: false,
            autoCapitalize: "none",
            inputType: "text",
            ref,
          },
        }}
      />
    </>
  );
}

Snack or a link to a repository

https://github.com/Rezrazi/react-native-search-bar-minimal

Screens version

3.34.0

React Native version

0.75.4

Platforms

iOS

JavaScript runtime

None

Workflow

Expo managed workflow

Architecture

None

Build type

Debug mode

Device

iOS simulator

Device model

iPhone 16 Pro

Acknowledgements

Yes

@github-actions github-actions bot added Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided labels Oct 7, 2024
@maciekstosio
Copy link
Contributor

Hi! Thanks for reporting the issue. Unfortunately, currently, I can only suggest using setTimeout with 40ms+ (this might be flaky depending on you're app size and device) as a workaround. We're talking internally about how to solve this issue. If you're interested in more details:
There are two problems:

  1. setOptions which is used by the expo-router when providing the options prop, changes the state internally, which causes HeaderConfig to rerender (specifically, it renders with SearchBar component), only once the SearchBar component is rendered the ref becomes set. The screen content isn't rerendered when HeaderConfig changes, which makes sense, but because of that, the developer is not able to react to those changes - there is no way of knowing when ref becomes available to the developer.
  2. once the ref is filled (which can be achieved using setTimeout or forcing rerender i.e. by setting a state in useEffect), calling it might not cause any effect, that's because the native component might not be created yet, thus the 40ms+ timeout

On a side note - Screen component in the context of react-navigation (and expo-router) is a template on how to create a specific instance of a given screen, which can be pushed multiple times to the stack. So, passing ref to it might cause unexpected errors. I would suggest using:

const navigation = useNavigation();

useEffect/useLayoutEffect(() => {
navigation.setOptions({
headerSearchBarOptions: {...},
});
}, []);

inside screen content. I don't have much experience in using expo-router, but it looks like if you'd like to set some options on the screen like in your reproduction, you should do it in _layouts.tsx. Keep in mind that useLayouEffect delays the native render of the elements, but useEffect that will be fired immediately after it won't have ref set because the SearchBar component is not rendered yet.

@maciekstosio
Copy link
Contributor

maciekstosio commented Oct 21, 2024

@Rezrazi i come up with another workaround, that could be better for your use-case:

export default function HomeScreen() {
  const navigation = useNavigation();
  const ref = useRef<SearchBarCommands>(null);

  useEffect(() => {
    navigation.setOptions({
      title: "Hello world",
      headerShown: true,
      headerSearchBarOptions: {
        placeholder: "Search...",
        placement: "stacked",
        hideNavigationBar: false,
        autoFocus: true,
        hideWhenScrolling: false,
        autoCapitalize: "none",
        inputType: "text",
        ref,
      },
    });

    const listener = navigation.addListener('transitionEnd', (event) => {
      if (event.data?.closing === false) {
        console.log(ref.current);
        ref.current?.setText('text')
      }
    });

    return () => {
      navigation.removeListener('transitionEnd', listener);
    }
  }, []);

  return (
    <View>
      <Text>Test</Text>
    </View>
  );
}

Let me know if that solves your problem!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided
Projects
None yet
Development

No branches or pull requests

2 participants