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.
Related
I use react-native with apollo-client3.
In my App.js file that starts my project, I configure if I log in go to LoggedInNav else LoggedOutNav.
<ApolloProvider client={client}>
<NavigationContainer>
<View onLayout={onLayoutRootView}></View>
{isLoggedIn ? <LoggedInNav /> : <LoggedoutNav />}
</NavigationContainer>
</ApolloProvider>
And the state whether I log in or not is tracked from useReactiveVar.
const isLoggedIn = useReactiveVar(isLoggedInVar);
In my LogIn.js file, I do log in and if it succeeds, I update my isLoggedInVar state made from makeVar of apollo/client from false to true.
export const isLoggedInVar = makeVar(false);
export const tokenVar = makeVar("");
export const logUserIn = async (token) => {
try {
await AsyncStorage.setItem("token", JSON.stringify(token));
isLoggedInVar(true);
tokenVar(token);
} catch (e) {
console.error(e);
}
};
So I update my state of makeVar in another screen, but can App.js listen to this update and navigate from <LoggedInNav/> to <LoggedOutNav/>?
I think App.js file is the bottom of all screens of my app, it should work.
But in my app project, I feel sometimes it might works but sometimes it throws Network error.
And in this case How can I make switch between two Navs according to Login state? Please help me.
I'm using react-admin. I would need to be able to access a value in the redux store in the App.js file. Based on this value, I pass different props to the Resources components included in Admin.
If I access the store in a classic way I get an error like this:
could not find react-redux context value; please ensure the component is wrapped in a
This is normal because the App is not wrapped by the provider. But do I have a way to do it by leaning on another component? Or some other way?
Thank you
const App = () => {
const theme = useSelector(state => state.theme);
useEffect(() => {
loadReCaptcha();
console.log('theme', theme);
}, []);
return (
<Admin
title="Atena"
dataProvider={dataProvider}
customReducers={{ theme: themeReducer }}
....
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()
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?
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
});
}