passing text input between custom header using stack navigation - react-native

I wanted to connect the SearchMain, SearchHistoryList, and SearchResult screens using the custom header of the stack navigation.
At first, the SearchMain screen appeared,
and when the TextInput in the custom header is onFocus, the SearchHistory screen appeared,
and the SearchResult screen appeared when the TextInput is onSubmitEditing or when the SearchButton was pressed.
Also, I set the keyboard focus to be maintained when switching screens, and use Keyboard.discuss() when I want to close it.
However, if you enter something at the TextInput in custom header immediately after the screen changes, it will be entered at the TextInput on the previous screen.
I think it's because the three screens don't share a single custom header!! each header is created, and the keyboard focus on the previous screen!!
I want all screens use the same header, or the values of the headers are consistent.
What should I do to solve this problem?
I searched variously such as singleton header, static header, and header value consistent, but there is no good solution.
I recently started studying React Native, so I'm not sure about my method, and a completely different approach is also welcome~~
Below is my code, thanks.
const Stack = createStackNavigator();
function NavigationBar({navigation}){
const [query, setQuery] = React.useState('');
function changeSearchQuery(text : string) : void {
setQuery(text);
}
function deleteSearchQuery() : void {
setQuery("");
}
return(
<InputView>
<BackIcon onPress={() => {navigation.goBack()
Keyboard.dismiss()}}>
<Icon name="arrow-back-ios" size={widthPercentage(24)} color="#666666"/>
</BackIcon>
<InputText
keyboardType="default"
maxLength={100}
onChangeText={(str) => setQuery(str)}
value = {query}
placeholder="검색어 입력"
placeholderTextColor="#E9E9E9"
returnKeyType="search"
onFocus={() => navigation.navigate("SearchHistory", {changeSearchQuery:changeSearchQuery})}
onSubmitEditing={() => {navigation.navigate("SearchResult", {changeSearchQuery:changeSearchQuery})
Keyboard.dismiss()}}
allowFontScaling= {false}
/>
{ query.length > 0 &&
<DeleteIcon onPress={() => deleteSearchQuery()}>
<Icon name="clear" size={widthPercentage(20)} color="#666666"/>
</DeleteIcon>
}
<SearchIcon onPress={() => {navigation.navigate("SearchResult")
Keyboard.dismiss()}}>
<Icon name="search" size={widthPercentage(20)} color="#666666"/>
</SearchIcon>
<Line></Line>
</InputView>
);
}
export const Search = () =>{
return (
<Stack.Navigator initialRouteName="SearchMain" keyboardHandlingEnabled={false} screenOptions={{header:props => <NavigationBar navigation={props.navigation}/>}}>
<Stack.Screen name="SearchMain" component={SearchMain} options={{animationEnabled: false}}/>
<Stack.Screen name="SearchHistory" component={SearchHistory} options={{animationEnabled: false}}/>
<Stack.Screen name="SearchResult" component={SearchResult} options={{animationEnabled: false}}/>
<Stack.Screen name="SearchFilter" component={SearchFilter} options={{headerShown: false}}/>
</Stack.Navigator>
);
}
export default Search;

It's not a good pattern to set the state inside the header, instead you should centralized the state in your component and then use navigation.setOption to pass functions/state value.
In this way your header will look like this:
function NavigationBar({
query,
onFocus,
onChangeText,
onPressBackIcon,
onSubmitEditing,
onDeleteSearch,
onSearch
}){
return(
<InputView>
<BackIcon onPress={onPressBackIcon}>
<Icon name="arrow-back-ios" size={widthPercentage(24)} color="#666666"/>
</BackIcon>
<InputText
keyboardType="default"
maxLength={100}
onChangeText={onChangeText}
value = {query}
placeholder="검색어 입력"
placeholderTextColor="#E9E9E9"
returnKeyType="search"
onFocus={onFocus}
onSubmitEditing={onSubmitEditing}
allowFontScaling= {false}
/>
{ query.length > 0 &&
<DeleteIcon onPress={onDeleteSearch}>
<Icon name="clear" size={widthPercentage(20)} color="#666666"/>
</DeleteIcon>
}
<SearchIcon onPress={onSearch}>
<Icon name="search" size={widthPercentage(20)} color="#666666"/>
</SearchIcon>
<Line></Line>
</InputView>
);
}
Than you will have in SearchMainScreen your state, your functions and your custom header:
// remember headerMode='none' in screen option
const SearchMainScreen = () => {
const [query, setQuery] = React.useState('');
const onFocus = () => {}
...
return (
<View>
<NavigationBar
onFocus={onFocus}
query={query}
...
/>
{
(isFocus && !query) ?
<SearchHistoryComponent/> :
(isFocus && query) ?
<SearchResultComponent/> :
<List/>
}
</View>
)
}

Related

How do i skip the animation only replace with react-navigation

i am using #react-navigation/stack^5.14.4 and #react-navigation/native^5.9.4, to handle the scene transition between Home, Login and Profile pages.
there are 2 transition cases i need to handle:
Home -> Profile (should have animation enabled)
Login -> Profile (need to skip the animation)
the reason why to skip the animation is that I am using a loading skeleton similar to the layout of Profile page during the login process. It is weird to have a transition between the profile page content and the skeleton itself. However I want to keep the awesome animation if pushed from Home page.
Is there a simple solution like
navigation.replace("Profile"); // with animation
navigation.replace("Profile", { animationEnabled: false }); // skip animation
You can use options to get the route property and check weather there is any parameter to disable the screen
snak: https://snack.expo.dev/#ashwith00/react-navigation-5-boilerplate
heres is an example
function TestScreen({ navigation }) {
return (
<View style={styles.content}>
<Button
mode="contained"
onPress={() => navigation.push('Test1', {disabledAnimation: true})}
style={styles.button}>
Push screen Without Animation
</Button>
<Button
mode="contained"
onPress={() => navigation.push('Test1')}
style={styles.button}>
Push new screen
</Button>
{navigation.canGoBack() ? (
<Button
mode="outlined"
onPress={() => navigation.pop()}
style={styles.button}>
Go back
</Button>
) : null}
</View>
);
}
const Stack = createStackNavigator();
function MyStack() {
return (
<Stack.Navigator>
<Stack.Screen
name="Test"
component={TestScreen}
options={{ title: 'Custom animation' }}
/>
<Stack.Screen
name="Test1"
component={TestScreen}
options={({ route }) => ({
title: 'Custom animation',
cardStyleInterpolator: !route.params?.disabledAnimation
? undefined
: CardStyleInterpolators.forNoAnimation,
})}
/>
</Stack.Navigator>
);
}
i find a more simple way to make it:
firstly, export the Stack.Screen node from the router file.
export const ProfileStack = (
<Stack.Screen name={'Profile'} component={Profile} />
);
export default Router = () => (
<Stack.Navigator>
<Stack.Screen name={'Home'} component={Home} />
<Stack.Screen name={'Login'} component={Login} />
{ProfileStack}
</Stack.Navigator>
);
then I can execute like this to disable the animation once.
with animation:
<Button onClick={() => {
navigation.replace('Profile');
}} />
without animation
<Button onClick={() => {
ProfileStack.props.options.animationEnabled = false;
navigation.replace('Profile');
ProfileStack.props.options.animationEnabled = true;
}} />

Passing state via route.params with React Navigation returning undefined

I'm attempting to pass a 'passcode' state as params in my React Native app.
I'm doing this by passing params into a 'navigation.navigate' call.
However, every time I navigate to the next screen, it's returning 'undefined' for 'route.params'.
For reference, here is my component I'm passing data FROM:
const SignUpPasscodeScreen = ({ navigation }) => {
const [passcode, setPasscode] = useState(0)
return (
<View>
<View style={styles.mainView}>
<SubLogo />
<Heading title="Set passcode" />
<SubHeading content="You'll need this anytime you need to access your account." />
<Input inputText={ text => setPasscode(text) } inputValue={passcode} />
</View>
<View style={styles.subView}>
<CtaButton text="Continue" onPressFunction={ () => navigation.navigate({ routeName: 'SignUpLegalName', params: { passcode } } ) } />
</View>
</View>
)
}
And here's the component I'm passing data to, and where the error occurs upon navigation:
const SignUpLegalName = ({ route, navigation }) => {
const { passcode } = route.params
return (
<View>
<View style={styles.mainView}>
<SubLogo />
<Heading title="Tell us your name" />
<SubHeading content="This needs to be the same as what's on your passport, or any other form of recognised ID." />
<Input />
<Input />
</View>
<View style={styles.subView}>
<CtaButton text="Continue" onPressFunction={ () => navigation.navigate('SignUpLink')} />
</View>
</View>
)
}
I've tried two forms of passing the props through:
Passing it in as a second argument
Passing it in as a 'params' object as shown above
Both should work according to the documentation - link here
For reference, this is my route structure:
const switchNavigator = createSwitchNavigator({
loginFlow: createStackNavigator({
SignUpPasscode: SignUpPasscodeScreen,
SignUpLegalName: SignUpLegalName,
})
});
The above structure doesn't say to me that it's a nested structure which therefore requires any additional work to pass it through...
Can someone help? It'd be appreciated as it's giving me a headache!
Have a try with below code in the button press event:
<CtaButton
text="Continue"
onPressFunction={() => navigation.navigate('SignUpLegalName',{ passcode })}
/>

React Native StackNavigator: Passing props from a screen to the header of the next screen

I have a React Native StackNavigator like so:
const AppStack = () => {
return (
<NavigationContainer theme={{ colors: { background: "white" }}}>
<Stack.Navigator headerMode="screen">
<Stack.Screen name="Master" component={ Master } />
<Stack.Screen
name="Details"
component={ Details }
options={{ headerTitle: props => <Header {...props} /> }} // <-- how can I pass props from Master to the Header here
/>
</Stack.Navigator>
</NavigationContainer>
)
}
The navigation works fine. When I'm on Master, if I press a TouchableOpacity, it brings up Details, with the header component Header.
However, what I want is to pass props from Master to the Header component in Details.
Something like this:
{/* What I want is that onPress, I want to pass someones_name and someones_photo_url to
the Details' Header component */ }
const Master = () => {
const someones_name = "Steve";
const someones_photo_url = "http://somephoto.com/001.jpg";
return (
<TouchableOpacity onPress={() => navigation.navigate("Details")}>
<Text>{ someones_name }</Text>
<Image source={{ uri: someones_photo_url }}>
</TouchableOpacity>
)
}
Is this possible?
You can pass values to other screens when navigating by doing the following:
Assume I am on screen "Feed" and click on someones name thus I want to go to screen "Profile" where it will show that user name that I clicked on.
In screen Feed
props.navigation.navigate('Profile, { userName: 'Shane' })`
In screen Profile I can grab that value passed in via navigate with:
const { userName } = props.route.params
To set this up inside a header assuming your screen options are outside the screen component and cannot access the props. Look into using setOptions to configure your header title dynamically.
props.navigation.setOptions({
headerTitle: ...
})

Refreshing React Navigation Tab Navigator Icon

My app is using the latest hook-based implementation of React Navigation (v5.4.0). It has a tab bar navigator that has a tab representing mail messages, with the obligatory number of messages shown in the corner of the icon. The tab code looks something like:
var messageCount = 0;
export function UpdateMessageCount(newnum:number)
{
messageCount = newnum;
}
function MessageIcon (color:string, size:number)
{
return (messageCount < 1)? (<FontAwesome style={{color:color, fontSize:size}} icon={LightIcons.envelope} pro={true} />):
(<View>
<FontAwesome style={{color:color, fontSize:size}} icon={LightIcons.envelope} pro={true} />
<View style={{position:'absolute',borderRadius:20, right:-10,top:0,backgroundColor:'red',minWidth:15, height:15, justifyContent:'center', alignItems:'center'}}>
<Text style={{fontSize:11, color:'white', fontFamily:'Roboto-Medium',marginLeft:2,marginRight:2}}>{messageCount}</Text>
</View>
</View>)
}
and
const MainTab = createBottomTabNavigator();
function MainTabNavigator() {
return (
<MainTab.Navigator
...
<MainTab.Screen name="Messages"
component={ MessageListNavigator }
options={{
tabBarIcon: ({ color, size }) => (
MessageIcon(color, size)
),
}}/>
I want the icon/tab navigator to refresh when the number of mail messages has changed (which happens asynchronously via a sync). Setting a fake option (as shown here) on the navigator will cause a tab navigator refresh, but is relying on an undocumented side effect:
UpdateMessageCount(count);
let op = this.props.navigation.dangerouslyGetParent();
op.setOptions({tickle:""})
1) Are there alternative ways of telling the tab navigator/icon to refresh? (I'm not using redux)
2) Is there a way to get the handle to a nested tab navigator from the main navigator object?
Thanks in advance for suggestions and comments.
I solved this by emitting an event when the message count changes, and catching the event in the tab navigator hook. Slightly revised code looks like:
function MessageIcon (messageCount:number, color:string, size:number)
{
return (messageCount < 1)? (<FontAwesome style={{color:color, fontSize:size}} icon={LightIcons.envelope} pro={true} />):
(<View>
<FontAwesome style={{color:color, fontSize:size}} icon={LightIcons.envelope} pro={true} />
<View style={{position:'absolute',borderRadius:20, right:-10,top:0,backgroundColor:'red',minWidth:15, height:15, justifyContent:'center', alignItems:'center'}}>
<Text style={{fontSize:11, color:'white', fontFamily:'Roboto-Medium',marginLeft:2,marginRight:2}}>{messageCount}</Text>
</View>
</View>)
}
const MainTab = createBottomTabNavigator();
function MainTabNavigator() {
const [messageCount, setMessageCount] = useState(0);
const messageCountListener = MyEvents.addListener(MyEventMessageCountUpdated,
(count)=>{
console.log("message count update: ", count)
setMessageCount(count);
});
useEffect(()=> {
return function cleanup() {
messageCountListener.remove();
}
});
return (
<MainTab.Navigator
...
<MainTab.Screen name="Messages"
component={ MessageListNavigator }
options={{
tabBarIcon: ({ color, size }) => (
MessageIcon(messageCount, color, size)
),
}}/>

How to open keyboard on button press in react native?

I have a button and when it is pressed I would like it to open the keyboard and focus on a text input component.
For a minimal code example what I am trying to do is this-
<View>
<AddSomething
textChange={textInput => this.setState({ textInput })}
addNewItem={this.addItem.bind(this)}
textInput={this.state.textInput}
ref={not sure what goes here} //passing these as props to a text input
/>
<FloatingButton tapToAddHandler={this.onFloatingButtonPress.bind(this)} />
</View>
then some helper function where I handle the button press (this.onFloatingButtonPress)
First declare your AddSomething as below :
const AddSomething = React.forwardRef((props, ref) => (
<TextInput
ref={ref}
//your other code
/>
));
Now you can use ref and able to focus your AddSomething component as below:
<View>
<AddSomething
textChange={textInput => this.setState({ textInput })}
addNewItem={this.addItem.bind(this)}
textInput={this.state.textInput}
ref={(ref) => { this.textInputField = ref }}
/>
<FloatingButton tapToAddHandler={this.onFloatingButtonPress.bind(this)} />
</View>
Here is your onFloatingButtonPress method :
onFloatingButtonPress() {
this.textInputField.focus();
}
Proposal a react-hook version :
const inputRef = React.useRef()
return (
<TextInput ref={inputRef} />
<TouchableOpacity onPress={() => inputRef.current.focus()}>
<Text>Press</Text>
</TouchableOpacity>
)