Reload Webview when onMessage callback setting state - react-native

I am using React native Webview to load my web app. I am posting message from my web app using window.postMessage and I have implemented onMessage callback
render(){
return <View>
<Text>{this.state.messageFromWebview}</Text>
<WebView onMessage={this.onWebViewMessage} source={{uri: webAppUri}} />
</View>;
}
In onWebViewMessage function, in doing
onWebViewMessage=(event)=>{
this.setState({
messageFromWebview: event.nativeEvent.data
})
}
Above code is going into infinite loop. While setting state, Webview rerendering and calling the post message that triggering the setState.
Is there any alternative or am I missing anything here. Is it possible to set the <Text/> to the message from the webview without rerendering the Webview.

I think the infinite loop is because of calling the event without const request. All you need to do is add const variable before setState. The following code works for me.
onWebViewMessage = (event) => {
// set const data
const WishData = event.nativeEvent.data;
// then excuting setState the constant data here
this.setState({
messageFromWebview: WishData
});
}

Related

Submitting Formik form in React Native

I am building a React Native app that uses Formik. When I submit the form I call handleSubmit
<Formik
onSubmit={values => {
handleSubmit(values)
}}>
I define this before the return on my form:
const handleSubmit = (values) => {
const { status, data } = usePostRequest("/api/holidays-request", {
dateFrom: "2023-02-01",
dateTo: "2023-02-28",
fromHalf: 0,
toHalf: 0,
});
};
I have hard coded some values here for testing.
My usePostRequest is a custom hook I wrote to actually send the data to my API.
When I submit my form then handleSubmit is triggered but I get an erorr:
Warning: An unhandled error was caught from submitForm() [Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
It must be reason 2 that I am failing but I am unsure how to use my usePostRequest to submit the values from the form.
I would recommend you take a look at the custom hooks documentation
In this particular case you should write the post logic as a function and use it here directly.
Do not call hooks in event handlers.
const handleSubmit = (values) => {
const { status, data } = usePostRequest("/api/holidays-request", {
dateFrom: "2023-02-01",
dateTo: "2023-02-28",
fromHalf: 0,
toHalf: 0,
});
};
See Invalid Hook Call Warning for details.

Condition rendering failing in React Native Redux App

I'm trying to conditionally render my redux app based on if the user is logged in. The relevant & condensed version of my code is below:
let isLoggedIn = false;
export default function App() {
console.log('App executing...');
console.log('isLoggedIn: ', isLoggedIn);
return (
<Provider store={store}>
<NavigationContainer>
{isLoggedIn ? ContactsTab() : Login()}
</NavigationContainer>
</Provider>
);
}
store.subscribe(() => {
// Set isLoggedIn to true if token is received and reinvoke App()
if (store.getState().user.token) {
isLoggedIn = true;
App();
}
});
The app starts with console logging isLoggedIn: false and displaying Login()(as expected). When I login on my phone using the correct credentials, App() is re-invoked console logging isLoggedIn: true(as expected) but it's still displaying Login(). If I set isLoggedIn = true inside the app function, the app successfully starts displaying the ContactsTab().
What is happening here? Why is my app not moving to ContactsTab() when the value of isLoggedIn successfully changes to true? How can I fix this?
Thank you for reading along. I have been trying to debug this for the past 2 days with no success so any help would be greatly appreciated!
You need to use useState here like this, the useState will automatically renders when the state changes
export default function App() {
const [isLoggedIn, setLoggedIn] = useState(false);
console.log('App Executing...');
console.log('isLoggedIn: ', isLoggedIn);
store.subscribe(() => {
// Set isLoggedIn to true if token is received and reinvoke App()
if (store.getState().user.token) {
setLoggedIn(true);
}
});
return (
<Provider store={store}>
<NavigationContainer>
{isLoggedIn ? ContactsTab() : Login()}
</NavigationContainer>
</Provider>
);
}
Hope this helps!
What is happening here? Why is my app not moving to ContactsTab() when the value of isLoggedIn successfully changes to true? How can I fix this?
Re-invoking app doesn't necessarily re-renders your screen. Your conditional invoking is not working properly because your render method is only called once, to fix it you need to change the state of your App component. You are just changing the state through your reducer but you are not listening to that change in your app component. You must listen to that change and upon that change, you need to set login state to true and then your component will perform rendering for you.
Read more about state here.
Read more about how you can use redux to make your components listen to the change in state of your application here.

React Native Arabic (RTL) without forceRTL

In RN my bilingual app (English - Arabic), I have used I18nManager (views) and I18n (for translations)
When I am changing app language to Arabic, the whole app gets reloaded again from the splash-screen using this code:
I18nManager.forceRTL(true)
Ideally, it should not restart the app from start and it should continue with the current screen with Arabic data.
Currently, it is not happening, only translation elements are getting converted using I18n.t('keyword') but for views Arabic alignment, it's not proper.
Still looking for a better solution, let me know if anyone achieved it.
Thanks
Sopo !!
you should put this code in the top component in your project
import RNRestart from "react-native-restart";
I18nManager.forceRTL(true);
if (!I18nManager.isRTL) RNRestart.Restart();
If you guys wants to store stack state after reloading(because there is no other option without reloading) and want stack state back you can follow this link also you can check my code.
Link: React navigation state persist
Any Component
AsyncStorage.setItem('navigation_state', JSON.stringify(navigation.dangerouslyGetState()));
My App.js
const App = () => {
const [initialState, setInitialState] = useState();
const [isReady, setIsReady] = useState(false);
useEffect(() => {
restoreState();
}, []);
const restoreState = async () => {
try {
const savedStateString = await AsyncStorage.getItem('navigation_state');
const state = savedStateString ? JSON.parse(savedStateString) : undefined;
if (state !== undefined) {
AsyncStorage.removeItem('navigation_state');
setInitialState(state);
}
} finally {
setIsReady(true);
}
};
if (!isReady) {
return null;
}
return (
<Provider store={store}>
<NavigationContainer
initialState={initialState}
ref={rootNavigationRef}>
<Root>
<AppNavigator />
</Root>
</NavigationContainer>
</Provider>
);
};
I working on a project which has two languages, Arabic and English.i use redux for handling app language. I put all styles on redux and handle app style with redux. and when user change language all styles on my app change to that language . also all text handled with redux too. with this way, my app does not reload and app language changed immediately.
If your app is an android hybrid app, you can try this:
import com.facebook.react.modules.i18nmanager.I18nUtil;
I18nUtil i18nUtil = I18nUtil.getInstance();
i18nUtil.forceRTL(context, forceRtl);
i18nUtil.allowRTL(context, true);
value 'forceRtl' is a boolean.
for iOS,I think you can find the same method.
In Expo use
import {Updates} from "expo"
Updates.reload()

webview onNavigationStateChange returns url of previous page

In react native webview, when I navigate to a page the onNavigationStateChange returns url of the previous page? I think its an issue and posted it to github react native issues.
<WebView
ref={r => this.webview = r}
style = {{marginTop : 0}}
onNavigationStateChange=
{this._onNavigationStateChange.bind(this)}
startInLoadingState = {true}
source = {{uri: 'https://www.youtube.com' }}
/>
_onNavigationStateChange(webViewState)
{
this.setState(
{
youtube_video_url: webViewState.url
})
}
When I print this youtube_video_url, it is of the previous page or previous browsed video.
Sadly, all the webview methods like onNavigationStateChange, onLoad, onLoadStart etc are not working as expected in cases of single page application websites or websites that don't trigger a window.load event.
Normaly onNavigationStateChange gets triggered in the beginning of a request with {loading: true, url: "someUrl", ...} and once after the loading of the url with {loading: false, url: "someUrl", ...}.
There is no global workaround (to my knowledge) that will work correctly for all websites and for both iOs/Android but maybe this can help in your case:
https://snack.expo.io/H1idX8vpM
Basically you inject a custom javascript in the webview that notifies the WebView which is the loaded page. You can enhance it if you want to catch custom window.history manipulation events.

How to reset state of redux store while using react-navigation when react-navigation reset does not immediately unmount screens from stack?

I'm trying to do auth sign-out with react-native and am experiencing an issue where I want to reset the state of the redux store but, because I am using react-navigation, I have a bunch of redux-connected screens that are still mounted that re-render when the state tree is reset to it's initialState causing a bunch of exception errors. I tried to unmount them on sign-out with a react-navigation reset which redirects the user to the signup/login screen but I have no way of knowing when these screens are actually unmounted in order to call the RESET_STATE action. Initially I was dispatching the action via saga.
sagas/logout.js
import { LOGOUT, RESET_STATE } from 'Actions/user';
// clear localstorage once user logs out.
const clearData = function* clearData(action) {
AsyncStorage.removeItem('user');
yield put(
NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'SignedOut' })
],
})
);
// causes re-renders, screens still mounted
yield put({type: RESET_STATE});
}
export default function* logoutSaga () {
yield all([
yield takeEvery(LOGOUT, clearData),
]);
}
I also tried to reset once user reaches the SignedOut screen in it's componentDidMount cycle but unfortunately the screens unmount at some point well after componentDidMount is triggered:
screens/SignedOut.js
import { resetState } from 'Actions/user';
import ActionButton from 'Components/FormElements/ActionButton';
class SignedOut extends Component {
// screens are still mounted, causing screens from
// previous screens to throw exception errors
componentDidMount() {
this.props.dispatch(resetState());
}
componentWillUnmount() {
// never called
}
handleSignup = () => {
this.props.navigation.navigate('Signup');
}
handleLogin = () => {
this.props.navigation.navigate('Login');
}
render() {
return(
<Container>
<ActionButton
text="Sign Up"
handleButtonPress={this.handleSignup}
/>
<ActionButton
text="Log In"
handleButtonPress={this.handleLogin}
/>
</Container>
);
}
}
export default connect()(SignedOut);
My question is, can anyone think of a way to reset state of redux store after all of my screens have finally unmounted by the react-navigation reset action?
The issue is you're using navigate to navigate to the login/signup screen which leaves all your other components mounted, you should probably use back or reset to unmount all the components and show the login screen.
After thinking about this for a long time, I figured out that maybe I should have been focusing on the errors thrown instead of why I was getting errors. (I did learn a lot though).
Although figuring out how to listen for when all the screens are completely unmounted after calling a reset would have been super helpful, it would have just been a shortcut to bypass the real issue, the initialState for certain branches of my redux state was wrong. After correcting this no more errors, no matter when react-navigation decides to unmount the old screens.
As i have no idea how ur state looks like, which is always the issue here, why not try to use componentWillUnmount on all those components, to set a state variable and check that when you want to reset navigation?