Navigation v5 React Native - react-native

I have some question on React Native Navigations v5
I have a sub function in my main function. I also have screenOption = (navData) function for customise my navigation and have few header button here which out of my Main function. My Problem now i am not sure how to access the subFunction that located in my mainfunction from my screenOption = (navData). For example whenclick the icon on the header Button and it will trigger the dispatch function to store which located inside the main function.
const mainfunction = (props) =>{
subfunction = async(data) =>{
dispatch(action.delete(data)
}
return ()
}
export const screenOptions = (navData) => {
Item
title="Delete"
iconName={
Platform.OS === "android" ? "md-create" : "ios-trash-outline"
}
onPress={() => {
trigger subfunction here
}}
/>
}

Try creating a custom button and add the component using setOptions
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => <CustomHeaderButton onPress={customOnPress} />
});
}, []);

Related

How to hide element when device keyboard active using hooks?

I wanted to convert a hide element when keyboard active HOC I found to the newer react-native version using hooks (useEffect), the original solution using the older react lifecycle hooks looks like this - https://stackoverflow.com/a/60500043/1829251
So I created a useHideWhenKeyboardOpen function that wraps the child element and should hide that child if the device keyboard is active using useEffect. But on render the child element useHideWhenKeyboardOpen isn't displayed regardless of keyboard displayed.
When I've debugged the app I see the following error which I didn't fully understand,because the useHideWhenKeyboardOpen function does return a <BaseComponent>:
ExceptionsManager.js:179 Warning: Functions are not valid as a React
child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than
return it.
in RCTView (at View.js:34)
Question:
How can you attach keyboard displayed listener to a component in the render?
Example useHideWhenKeyboardOpen function:
import React, { useEffect, useState } from 'react';
import { Keyboard } from 'react-native';
// Wrapper component which hides child node when the device keyboard is open.
const useHideWhenKeyboardOpen = (BaseComponent: any) => (props: any) => {
// todo: finish refactoring.....
const [isKeyboadVisible, setIsKeyboadVisible] = useState(false);
const _keyboardDidShow = () => {
setIsKeyboadVisible(true);
};
const _keyboardDidHide = () => {
setIsKeyboadVisible(false);
};
/**
* Add callbacks to keyboard display events, cleanup in useeffect return.
*/
useEffect(() => {
console.log('isKeyboadVisible: ' + isKeyboadVisible);
Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
return () => {
Keyboard.removeCurrentListener();
};
}, [_keyboardDidHide, _keyboardDidShow]);
return isKeyboadVisible ? null : <BaseComponent {...props}></BaseComponent>;
};
export default useHideWhenKeyboardOpen;
Example Usage:
return(
.
.
.
{useHideWhenKeyboardOpen(
<View style={[styles.buttonContainer]}>
<Button
icon={<Icon name="save" size={16} color="white" />}
title={strings.STOCKS_FEED.submit}
iconRight={true}
onPress={() => {
toggleSettings();
}}
style={styles.submitButton}
raised={true}
/>
</View>,
)}
)
Mindset shift will help: think of hooks as data source rather than JSX factory:
const isKeyboardShown = useKeyboardStatus();
...
{!isKeyboardShown && (...
Accordingly your hook will just return current status(your current version look rather as a HOC):
const useHideWhenKeyboardOpen = () => {
const [isKeyboadVisible, setIsKeyboadVisible] = useState(false);
const _keyboardDidShow = useCallback(() => {
setIsKeyboadVisible(true);
}, []);
const _keyboardDidHide = useCallback(() => {
setIsKeyboadVisible(false);
}, []);
useEffect(() => {
Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
return () => {
Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
};
}, [_keyboardDidHide, _keyboardDidShow]);
return isKeyboadVisible;
};
Note usage of useCallback. Without it your hook will unsubscribe from Keyboard and subscribe again on every render(since _keyboardDidHide would be referentially different each time and would trigger useEffect). And that's definitely redundant.

Prevent an element from rerendering on setState in React Native

I am using setState in order to dynamically update an image's source when a button is pushed in React Native. However, I am also using the TypeWriter library which types out text with a special 'typewriter' animated effect.
When my setState is called to change the element and the page is rerendered, TypeWriter types the text out again. I don't want this. Is there a way to exclude my TypeWriter text from being rerendered?
Code snippet:
export const AccountScreen = ({ navigation }) => {
this.state = {
img1Src: require('../assets/img/token.png')
}
const [state, setState] = useState(this.state);
changeImgSrc = () =>{
setState({
img1Src: require('../assets/img/X2.png')
})
}
return (
<TypeWriter> //I don't want this to re-render on setState
<Text>My Account</Text>
</TypeWriter>
<Animatable.Image source={state.img1Src}/>
<Button onPress={this.changeImgSrc}>
Click me!
</Button>
etc...//
export const NewComponent = ({ navigation }) => {
const [state, setState] = useState({
img1Src: require('../assets/img/token.png')
});
changeImgSrc = () =>{
setState({
img1Src: require('../assets/img/X2.png')
})
}
return (
<Animatable.Image source={state.img1Src}/>
<Button onPress={this.changeImgSrc}>
Click me!
</Button>
etc...//
You can make a new component is called NewComponent. Then,
export const AccountScreen = ({ navigation }) => {
return (
<TypeWriter> //I don't want this to re-render on setState
<Text>My Account</Text>
</TypeWriter>
<NewComponent />
etc...//
Also, you cannot use this in the functional component. In addition, if you want to call a function in functional component, you must use it with useCallback.

React Native Chat App, Flatlist useRef is null

Im building a chat app with React Native using Expo and I use a Flatlist as child of KeyboardAvoidingView to render the list of messages, the problem is I want to scroll to the bottom when the keyboard is triggered
So I used the Flatlist method ( scrollToEnd ) with useRef hook and my code looks like this :
const ChatBody = ({ messages }) => {
const listRef = useRef(null);
useEffect(() => {
Keyboard.addListener("keyboardWillShow", () => {
setTimeout(() => listRef.current.scrollToEnd({ animated: true }), 100);
});
return () => Keyboard.removeListener("keyboardWillShow");
}, []);
return (
<FlatList
ref={listRef}
keyboardDismissMode="on-drag"
data={messages}
keyExtractor={(item) => item.id || String(Math.random())}
renderItem={({ item }) => <Message {...item} />}
/>
}
The code works just fine at the first render, but when I leave the screen and get back again and trigger the keyboard I get this error :
TypeError : null in not an object (evaluating 'listRef.current.scrollToEnd')
*The reason I added setTimout was because the scrollToEnd for some reason does not work when the keyboard event is triggered. adding setTimeout solved that issue.
The component tree is kinda like this :
StackNavigatorScreen => KeyboardAvoidingView => FlatList
You need to pass your event handler as a second parameter to Keyboard.removeListener. Since you're only passing in the first argument, your handler is run anyway and before your ref could be set.
const ChatBody = ({ messages }) => {
const listRef = useRef(null);
useEffect(() => {
Keyboard.addListener("keyboardWillShow", onKeyboardWillShow);
return () => Keyboard.removeListener("keyboardWillShow", onKeyboardWillShow);
}, []);
function onKeyboardWillShow() {
setTimeout(() => {
listRef.current.scrollToEnd();
}, 100);
}
return (
<FlatList
ref={listRef}
keyboardDismissMode="on-drag"
data={messages}
keyExtractor={(item) => item.id || String(Math.random())}
renderItem={({ item }) => <Message {...item} />}
/>
)
}
const listRef = useRef(null); <-- this line is the one causing the problem.
You need to assign an object, null in this case cannot be put there as it is not an object.

react-native-navigation: how to hide bottomTabs when keyboard is shown?

I am using the bottomTabs navigator from react-native-navigaton to navigation within my app. However, each time I have for example a TextInput field, the bottomTabs are being pushed up.
What is a possible way to hide the bottomTabs whenever the keyboard is being shown?
just add tabBarHideOnKeyboard: true to screenOptions
Add the following to your android manifest in android/app/src/AndroidManifest.xml
in your activity tag add/replace this attribute
the bottom tab should be hidden now.
android:windowSoftInputMode="stateUnspecified|adjustPan"
You can programmatically hide the bottom tab when the keyboard is open using React hooks
const _keyboardDidShow = useCallback(() => {
navigation.setOptions({
tabBarVisible: false,
});
}, [navigation]);
const _keyboardDidHide = useCallback(() => {
navigation.setOptions({
tabBarVisible: true,
});
}, [navigation]);
useEffect(() => {
Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
// cleanup function
return () => {
Keyboard.removeListener('keyboardDidShow', _keyboardDidShow);
Keyboard.removeListener('keyboardDidHide', _keyboardDidHide);
};
}, [_keyboardDidHide, _keyboardDidShow]);
Answer for React Navigation V6 with or without a Custom tabBar
I'm using react navigation V6 since i'm using a custom tabBar the tabBarHideOnKeyboard: true prop not working but when i change the custom tabBar to default tab bar that prop works but i don't like the behavior of that prop on android, so i used keyboard from react-native to check if the keyboard is active or not and setting the css display prop to 'none'
import { Keyboard, View } from "react-native";
const [keyboardStatus, setKeyboardStatus] = useState<boolean>();
useEffect(() => {
const showSubscription = Keyboard.addListener('keyboardDidShow', () => {
setKeyboardStatus(true);
});
const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {
setKeyboardStatus(false);
});
return () => {
showSubscription.remove();
hideSubscription.remove();
};
}, []);
so on the custom tab bar component now we can do like this.
<View style={
[
styles.mainContainer,
keyboardStatus ? styles.hideTabNavigation : null,
]
}
>
// your code here
</View>
Hope this will help someone until they fix this issue!

How to update the header while the component is still rendered using React Navigation?

I'm writing a React Native app and I'm using React Navigation (V2) with it. I want to update the navigationOptions and add a new button, after my component has updated. Here is the code with which I tried it:
static navigationOptions = ({ navigation }) => {
const options = {
headerTitle: SCREEN_TEXT_MENU_HEADER,
headerStyle: {
borderBottomWidth: 0,
marginBottom: -5
}
};
if (navigation.getParam("drawer", true)) {
options["headerLeft"] = (
<HeaderIconButton
onClick={() => {
navigation.openDrawer();
}}
icon={require("../../assets/icons/burgerMenu.png")}
/>
);
}
if (navigation.getParam("renderBillButton", false)) {
options["headerRight"] = (
<HeaderIconButton
onClick={() => {
navigation.navigate("BillScreen");
}}
type="primary"
icon={require("../../assets/icons/euro.png")}
/>
);
}
return options;
};
componentDidUpdate = prevProps => {
const { navigation, orders } = this.props;
if (prevProps.orders.length !== orders.length) {
navigation.setParams({
renderBillButton: orders.length > 0
});
}
};
The problem with this approach is, that the navigationOptions do not get reset after componentDidUpdate(). How can I dynamically adjust the header with React Navigation?
You can use this.props.navigation.setParams() function to update the navigation state params.
Reference: https://reactnavigation.org/docs/en/headers.html#updating-navigationoptions-with-setparams
Okay here is what went wrong: I also had to call the same code within componentDidMount(), otherwise it would not affect the page upon loading. So in addition to the code of my question I added:
componentDidMount = () => {
const { navigation, order } = this.props;
navigation.setParams({
renderBillButton: orders.length > 0
});
}