I have a method called sendResults() that makes an API call and does some array manipulation. When I call the method using a "normal" TouchableOpacity button, everything works fine. However, when I call it using a button I have placed in the App Stack header, the method does not run correctly. It feels like an async issue (?) but not sure...
Here is the code for the two buttons.
<TouchableOpacity
onPress={() => {
sendResults(); // works fine
}}
style={styles.buttonStyle}
>
<Text>Save</Text>
</TouchableOpacity>
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<TouchableOpacity
onPress={() => {
sendResults(); // doesn't work
}}
style={styles.buttonStyle}
>
<Text>Save</Text>
</TouchableOpacity>
),
});
}, []);
Edit: sendResults() code
// Shows alert confirming user wants to send results to API
const sendResults = () => {
Alert.alert("Save Results", "Alert", [
{
text: "Save & Quit",
onPress: () => postNumsAndNavigate(),
style: "destructive",
},
{ text: "Cancel", onPress: () => console.log("") },
]);
};
// Save Results button
const postNumsAndNavigate = async () => {
if (bibNums.length == 0) {
alert("You have not recorded any results. Please try again.");
} else if (bibNums.filter((entry) => entry == "").length > 0) {
alert("Blank");
} else {
console.log("\n" + bibNums);
await postNums();
AsyncStorage.setItem(`done`, "true");
navigation.navigate("Home Screen");
}
};
postNums() does an API call.
Edit 2: bibNums declaration
const [bibNums, setBibNums] = useState([]);
You set the handler of the navigation button only once because your useEffect doesn't have any dependency; it runs only when the component is mounted, it captures an old reference of sendResults. sendResults changes every time postNumsAndNavigate and bibNums change. Add sendResults to the dependency array to update the navigation button handler every time sendResults changes.
useEffect(() => {
...
}, [sendResults])
It works correctly for the TouchableOpacity because you are assigning the handler on every render.
onPress={() => {sendResults()}}
Related
I'm trying to open a simple page with React Native WebView.
It's a single page web, and when you do a search, it prints out some information about your search.
After that, if you want to search again, press the back button on the device to move to the search box.
Because it is a single page, I cannot use goBack, so I created a function called cancel.
The problem is that when I click the device's back button, the function called cancel defined on the web is not executed.
The cancel function deletes the searched information and returns to the search window.
I will upload my code.
Please advise.
export default function App() {
const webviewRef = useRef(null);
const backAction = () => {
setBackTapping((prev) => prev += 1);
webviewRef.current.injectJavaScript('window.cancel()')
return true;
}
useEffect(() => {
const timer = setInterval(() => {
setBackTapping(0)
}, 1000)
return () => clearInterval(timer);
}, [])
useEffect(() => {
const backHandler = BackHandler.addEventListener('hardwareBackPress',backAction);
return () => backHandler.remove()
}, [])
useEffect(() => {
if(backTapping >= 2){
return BackHandler.exitApp();
}
},[backTapping])
return (
<KeyboardAvoidingView style={{ flex: 1 }}>
<StatusBar hidden />
<WebView
ref={webviewRef}
textZoom={100}
originWhitelist={['*']}
javaScriptEnabled
source={{ uri: 'myhome.com'}}
startInLoadingState={true}
/>
</KeyboardAvoidingView>
);
}
Expected behavior:
The cancel function is executed, all open windows are closed, and you are returned to the search window.
in my case, calling is wrong.
instead of :
webviewRef.current.injectJavaScript('window.cancel()')
use :
const generateOnMessageFunction = (data) => `
(function(){
window.dispatchEvent(new MessageEvent('message',{data: ${JSON.stringify(data)}}));
})()
`;
webviewRef.current.injectJavaScript(generateOnMessageFunction('cancel'));
detail referance :
https://github.com/react-native-webview/react-native-webview/issues/809
In my React Native project I want to add edit, go back and save button in the header of my form screen.
To manage my form, I use react-hook-form.
The header come from react-navigation and I use the navigation.setOptions function to add my buttons.
This work well for the edit or go back button but save button don't fire handleSubmit function provide by react-hook-form.
If I put the same button in another place in my page, that work well.
const MemberScreen = (navigation: any) => {
const { control, handleSubmit, formState: { errors } } = useForm();
const [editMode, setEditMode] = useState(false);
useLayoutEffect(() => {
let title = "";
let headerRight: any;
let headerLeft: any;
if (editMode) {
title = "edit form"
headerRight = () => (<TouchableOpacity onPress={() => { save() }}><MaterialCommunityIcons name="content-save" color={AppConst.primaryColor} size={32} style={styles.iconItem} /></TouchableOpacity>)
headerLeft = () => (<TouchableOpacity onPress={() => { toggleEdit() }}><MaterialCommunityIcons name="close" color={AppConst.primaryColor} size={32} style={styles.iconItem} /></TouchableOpacity>)
} else {
headerRight = () => (<TouchableOpacity onPress={() => { toggleEdit() }}><MaterialCommunityIcons name="pencil" color={AppConst.primaryColor} size={32} style={styles.iconItem} /></TouchableOpacity>)
headerLeft = () => headerLeftWithBack(navigation);
}
navigation.navigation.setOptions({ title: title, headerRight: headerRight, headerLeft: headerLeft });
}, [navigation, editMode])
const toggleEdit = () => {
setEditMode(!editMode);
}
const save = () => {
handleSubmit((data) => {
onSubmit(data)
})
}
const onSubmit = async (data: any) => {
let body = { id: member.id, ...data }
// ...
}
return // ...
}
Do you have any idea or solution to fix this problem ?
This fix my problem because i miss parentheses :
const save = () => {
handleSubmit((data) => {
onSubmit(data)
})()
}
I have 4 screens in bottom navigation.
The first screen consists of a map with the timer of 10s . As soon as the timer get executed the api is hit.
But when a user switch the screen with bottom navigation tab item . The timer still works in background and due to which the other api start having lag.
How to make sure the timer only works when that screen is focused?
I tried updating the name of screen user is navigating using useContext however when the timer execute it do not return the update name of the screen. every time it returns the older screen name.
This code is in all the 4 bottom navigation screens. As I have observed useEffect only works once. and whenever user clicks it second time this hook do not get trigger.
HOME SCREEN
const {activeScreenFun, activeScreen, previousScreen} = useNavigationCustom();
React.useEffect(() => {
const unsubscribe = navigation.addListener('tabPress', e => {
activeScreenFun('Home');
});
return unsubscribe;
}, [navigation]);
useEffect(() => {
activeScreenFun('Home');
}, []);
Timer
useEffect(() => {
if (timer) {
let interval = setInterval(() => {
getAPiData();
}, 10000);
return () => {
clearInterval(interval);
};
}
}, [timer]);
NavigationCustomProvider Context
export function NavigationCustomProvider({children}) {
const [activeScreen, setActiveScreen] = useState('');
const [previousScreen, setPreviousScreen] = useState('');
const activeScreenFun = useCallback(async function (activeScreenSelected) {
setPreviousScreen(activeScreen);
setActiveScreen(activeScreenSelected);
});
const getActiveScreenFun = useCallback(() => {
return activeScreen;
});
Bottom Navigation Code
export default function MainScreen() {
return (
<NavigationCustomProvider>
<MainLayout>
<MainLayoutScreen
name={HOME_ROUTE}
icon={TrackItIcon}
activeIcon={TrackItActiveIcon}
component={HomeScreen}
/>
<MainLayoutScreen
name={ATTENDACE_ROUTE}
icon={AttendanceIcon}
activeIcon={AttendanceActiveIcon}
component={AttendanceScreen}
/>
<MainLayoutScreen
name={NOTIFICATION_ROUTE}
icon={NotificationIcon}
activeIcon={NotificationActiveIcon}
component={NotificationScreen}
/>
<MainLayoutScreen
name={MY_ACCOUNT_ROUTE}
icon={AccountIcon}
activeIcon={AccountActiveIcon}
component={ProfileScreen}
/>
</MainLayout>
</NavigationCustomProvider>
);
}
TAB BAR CODE
routes = children.map(x => ({
name: x.props.name,
icon: x.props.icon,
activeIcon: x.props.activeIcon,
component: x.props.component,
}));
<Tab.Navigator
barStyle={{backgroundColor: theme.colors.white}}
activeColor={theme.colors.primary}
shifting={true}
labeled={true}>
{routes.map(x => {
let Icon = x.icon;
let ActiveIcon = x.activeIcon;
return (
<Tab.Screen
key={x.name}
name={x.name}
component={x.component}
options={{
tabBrColor: theme.colors.white,
tabBarIcon: ({focused}) =>
focused ? <ActiveIcon /> : <Icon />,
}}
/>
);
})}
</Tab.Navigator>
A new timer instance is created for each new component rerender.
Even if you clear an instance of the timer when the component unmounts, previously created instances are still running in the background.
you need to persist a single instance of timer across all component rerender cycles.
React provide hook useRef to persist value for all component render cycle.
let interval = React.useRef(null);
useEffect(() => {
if (timer) {
// Assign and persist Timer value with ref
interval.current = setInterval(() => {
getAPiData();
}, 10000);
return () => {
if (interval.current) {
clearInterval(interval);
}
};
}
}, [timer]);
How do I make the return button on the mobile keyboard send the message instead of creating a new line? I tried using onSubmitEditing in the textInputProps but couldn't get it to work.
You need to implement your own ChatComposer and pass the onSubmitEditing prop in the textInputProps in there. In order to prevent keyboard dismiss you also need to set blurOnSubmit to false.
const [messages, setMessages] = useState([])
const onSend = useCallback((messages = []) => {
setMessages((previousMessages) => GiftedChat.append(previousMessages, messages))
}, [])
const ChatComposer = (
props: ComposerProps & {
onSend: SendProps<IMessage>["onSend"]
text: SendProps<IMessage>["text"]
}
) => {
return (
<Composer
{...props}
textInputProps={{
...props.textInputProps,
blurOnSubmit: false,
multiline: false,
onSubmitEditing: () => {
if (props.text && props.onSend) {
props.onSend({ text: props.text.trim() }, true)
}
},
}}
/>
)
}
return (
<GiftedChat messages={messages} onSend={onSend} renderComposer={ChatComposer} />
)
If you want to remove the default send button from the text input field on the right, you need to pass a custom renderSend button, which could be empty, e.g.
renderSend={() => {}}
Notice, that I have tested all of the above on iOS only. Android might behave differently.
code is working when i placed this line of code just below onpress()
but inside success callback not working
check handle success
<TouchableOpacity
style={styles.item}
onPress={() => {
RazorpayCheckout.open({
...
theme: { color: "#f7b500" }
})
.then(data => {
***// handle success***
console.log(`Success: ${data.razorpay_payment_id}`);
console.log(reference)
**reference.navigation.navigate("ThankyouScreen", {
AvailablePoints: creditScorePoints - item.cash
});**
})
.catch(error => {
...
});
}}
>
What is reference? if it is this.props then Make sure it is defined globally.
Try to change your code with:
//on top of class
var reference = null;
//In componentDidMount
reference = this.props;