I'm new with react-native and redux and I want to know how can I get the state updated after the dispatch...
Follow my code:
/LoginForm.js
function mapStateToProps(state) { return { user: state.userReducer }; }
function mapDispatchToProps(dispatch) {
return {
login: (username, password) => {
dispatch(login(username, password)); // update state loggedIn
}
}
}
const LoginForm = connect(mapStateToProps, mapDispatchToProps)(Login);
export default LoginForm;
/Login.js
---Here I've a button which calls this method loginOnPress()
loginOnPress() {
const { username, password } = this.state;
this.props.login(username, password);
console.log(this.props.user.loggedIn)
}
According my code above, I call first the method 'this.props.login(username, password);' that calls the dispatch and change the state 'loggedIn'.
And after that I try to get the state updated but without success:
console.log(this.props.user.loggedIn)
Note: When I click the second time on this button the state comes updated
Calling dispatch will update the state immediately but your components will be updated a little bit later so you can use componentWillReceiveProps to react to changes in the props, you can have a look here for a better explanation of how state change works in React
The function this.props.login(username, password) dispatches a login action on the redux-state.
Launching store.getState() will indeed immediately get you the redux-state after the update, but usually, you don't really need to do it because of the redux connect function that wraps your component.
The redux connect function updates your component with new props, so what you would usually do is "catch" these changes in one of the following functions of the react lifecycle:
class Greeting extends React.Component {
...
loginOnPress () {
const { username, password } = this.state;
this.props.login(username, password);
}
// before the new props are applied
componentWillReceiveProps (nextProps) {
console.log(nextProps.user.loggedIn)
}
// just before the update
componentWillUpdate (nextProps, nextState) {
console.log(nextProps.user.loggedIn)
}
// immediately after the update
componentDidUpdate (prevProps, prevState) {
console.log(this.props.user.loggedIn)
}
render() {
...
}
}
Related
I'm making authentication in an app, and I'm kind of stuck. I have 2 different navigations. One shows if the user is logged in and another one if not. Basically, a Sign in screen. It's working fine if I change the value manually upon the start. But I can't find a way to change a state when a user signs in, for example. Even though the value in auth module changes, it doesn't update in App.js So how can I update the App.js's state from Sign in screen, for example?
import React, { Component } from 'react';
import { AppRegistry, Platform, StyleSheet, Text, View } from 'react-native';
import DrawerNavigator from './components/DrawerNavigator'
import SignedOutNavigator from './components/SignedOutNavigator'
import auth from './auth'
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props)
this.state = {
isLoggedIn: auth.isLoggedIn
}
}
render() {
return (
(this.state.isLoggedIn) ? <DrawerNavigator /> : <SignedOutNavigator />
);
}
}
AppRegistry.registerComponent('App', () => App)
and my auth module, which is very simple
import { AsyncStorage } from 'react-native';
// try to read from a local file
let api_key
let isLoggedIn = false
function save_user_settings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
isLoggedIn = true
});
}
module.exports.save_user_settings = save_user_settings
module.exports.api_key = api_key
module.exports.isLoggedIn = isLoggedIn
First off, there are loads of ways to approach this problem. Because of this I'm going to try explain to you why what you have now isn't working.
The reason this is happening is because when you assign auth.isLoggedIn to your isLoggedIn state, you are assigning the value once, kind of as a copy. It's not a reference that is stored.
In addition to this, remember, React state is generally only updated with setState(), and that is never being called here, so your state will not update.
The way I would approach this problem without bringing in elements like Redux, which is overkill for this problem by itself, is to look into building an authentication higher order component which handles all the authentication logic and wraps your entire application. From there you can control if you should render the children, or do a redirect.
Auth Component
componentDidMount() {
this._saveUserSettings(settings);
}
_saveUserSettings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
this.setState({isLoggedIn: true});
});
}
render() {
const { isLoggedIn } = this.state;
return isLoggedIn ? this.props.children : null;
}
App.js
render() {
<AuthComponent>
//the rest of authenticated app goes here
</AuthComponent>
}
Here's a really quick, incomplete example. But it should showcase to you how you may want to lay your authentication out. You'll also want to consider error handling and such, however.
I'm trying to pass device token from my App.js to my Login component in react-native.
I'm trying something like this :
Here's my app.js :
const RootStack = createStackNavigator(
{
Login: {
screen: Login,
navigationOptions :{ headerLeft: null}
},
Tab: {
screen: Tab,
navigationOptions :{ headerLeft: null }
}
},
{
initialRouteName: 'LoginScreen'
}
);
const MyApp = createAppContainer(RootStack);
constructor(props) {
super(props);
this.state = {
token: ''
}
}
async componentDidMount() {
this.state.token = await firebase.messaging().getToken().then(token=> { return token;});
this.checkPermission();
this.createNotificationListeners();
}
render() {
return (
<MyApp token={this.state.token}></MyApp>
);
}
And my Login.js :
export default class LoginScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
mail:"",
password: "",
token: this.props.token
}
}
async Login(){
console.log(this.state.token)
}
}
Of course it's not working, I don't know how to pass the token via components, or via stacknavigator without using .navigate(). Besides even if I fill the const with a single string, it's not working, so what am I doind wrong ? And is it going to be different with the token ?
screenProps
You need to use screenProps. You should check the documentation to see more about it.
This is how I would use it in your case.
<MyApp screenProps={{ token: this.state.token}} />
Then you can access it in your screens using this.props.screenProps.token.
Here is a snack that I created that shows passing values through a navigator. https://snack.expo.io/#andypandy/passing-props-through-a-navigator
In your current LoginScreen you are trying to set the value of the token in the state. Bear in mind, your page may be constructed before the token is created so the initial value for the token may not exist so you may prefer to capture the value in componentDidUpdate, see the docs for more.
Alternatively you could store the token in AsyncStorage.
Firebase
When getting your token from Firebase, you are mixing promises and async/await notation. Choose one notation and stick with it.
You are setting your state incorrectly. You are mutating state with your line
this.state.token = await firebase.messaging().getToken().then(token=> { return token;});
You should not mutate state as it can get overwritten, and it can cause side effects that you don't expect. You should use this.setState({token}); to set your token value into state.
This is how I would refactor your componentDidMount
async componentDidMount() {
// await functions can throw so always wrap them in a try/catch
try {
// get the token from firebase https://rnfirebase.io/docs/v5.x.x/messaging/device-token
let token = await firebase.messaging().getToken();
if (token) {
// if we have a token then set the state and use a callback
// once the state has been the callback calls checkPermissions and createNotificationListeners
this.setState({token}, () => {
this.checkPermission();
this.createNotificationListeners();
});
}
} catch (err) {
console.log(err);
}
}
Additional reading
The following articles by Michael Chan are a great introduction into state.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
The following article shows the differences between promises and async/await
https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8
I have a basic component that calls a webservice during the componentDidMount phase and overwrites the contents value in my state:
import React, {Component} from 'react';
import {Text} from "react-native";
class Widget extends Component {
constructor() {
super();
this.state = {
contents: 'Loading...'
}
}
async componentDidMount() {
this.setState(...this.state, {
contents: await this.getSomeContent()
});
}
render() {
return (
<Text>{this.state.contents}</Text>
)
}
async getSomeContent() {
try {
return await (await fetch("http://someurl.com")).text()
} catch (error) {
return "There was an error";
}
}
}
export default Widget;
I would like to use Jest snapshots to capture the state of my component in each one of the following scenarios:
Loading
Success
Error
The problem is that I have to introduce flaky pausing to validate the state of the component.
For example, to see the success state, you must place a small pause after rendering the component to give the setState method a chance to catch up:
test('loading state', async () => {
fetchMock.get('*', 'Some Content');
let widget = renderer.create(<Widget />);
// --- Pause Here ---
await new Promise(resolve => setTimeout(resolve, 100));
expect(widget.toJSON()).toMatchSnapshot();
});
I'm looking for the best way to overcome the asynchronicity in my test cases so that I can properly validate the snapshot of each state.
If you move the asynchronous call out of setState, you can delay setState until the network call has resolved. Then you can use setState's optional callback (which fires after the state change) to capture the state.
So, something like this:
async componentDidMount() {
var result = await this.getSomeContent()
this.setState(...this.state, {
contents: result
},
// setState callback- fires when state changes are complete.
()=>expect(this.toJSON()).toMatchSnapshot()
);
}
UPDATE:
If you want to specify the validation outside of the component, you could create a prop, say, stateValidation to pass in a the validation function:
jest('loading state', async () => {
fetchMock.get('*', 'Some Content');
jestValidation = () => expect(widget.toJSON()).toMatchSnapshot();
let widget = renderer.create(<Widget stateValidaton={jestValidation}/>);
});
then use the prop in the component:
async componentDidMount() {
var result = await this.getSomeContent()
this.setState(...this.state, {
contents: result
},
// setState callback- fires when state changes are complete.
this.props.stateValidaton
);
}
Iam doing an app in react-redux and i want to hold my redux store on page refresh and thought to not make use of predefined libraries and hence i set the redux state to local state and making the api call in componentWillUnmount to restore redux state on page refresh.But i couldnt do that. Is their any approch to acheive this:
And my code is:
componentWillMount(){
this.setState({
activeUser:this.props.activeUser
})
}
componentWillUnmount(){
this.props.loginUser(this.state.activeUser.user);
}
activeUser is my redux state and this.props.loginUser() makes api call.And i tried of using event handlers as:
componentDidMount(){
window.addEventListener('onbeforeunload', this.saveStore)
}
componentWillUnmount(){
window.removeEventListener('beforeunload', this.saveStore)
}
saveStore(){
this.props.loginUser(this.state.activeUser.user);
}
but it didn't worked for me.
My understanding is that what you basically are trying to do is that, you want to persist your app state (user info, etc) between reloads.
One can use the localStorage API to achieve this effect.
I'll give one possible solution down here.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {activeUser: null};
}
componentWillMount() {
let activeUser = localStrorage.getItem("activeUser");
if (activeUser) {
this.props.receivedActiveUser(JSON.parse(activeUser));
}
}
componentWillReceiveProps(nextProps) {
this.setState({activeUser: nextProps.activeUser});
}
componentWillUnmount(){
if (this.state.activeUser) {
localStorage.setItem("activeUser", JSON.stringify(this.state.activeUser));
}
}
}
Ofcourse, you'll have to create a receivedActiveUser action which will update the store appropriately.
I want to implement the new Auth0 Lock 10 in my React/Redux app.
I've checked on the internet, but nothing matches my question. There's a tutorial here, but it uses the Popup mode instead of the Redirect (default now) mode. Another one parses the url, which is useless in Lock 10.
Here's the flow:
The Auth0Lock gets instantiated when my app starts
When the user clicks on the login button, it shows the Lock widget (lock.show()) and dispatches LOGIN_REQUEST
The lock does its authentication on auth0.com (redirects out of my localhost)
Redirect back to my localhost after successful login, the Auth0Lock get instantiated again
I wait for an lock.on('authenticated') event to dispatch LOGIN_SUCCESS
And here is my actions/index.js code:
import Auth0Lock from 'auth0-lock'
export const LOGIN_REQUEST = 'LOGIN_REQUEST'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
export const LOGIN_ERROR = 'LOGIN_ERROR'
function loginRequest() {
return {
type: LOGIN_REQUEST
}
}
function loginSuccess(profile) {
return {
type: LOGIN_SUCCESS,
profile
}
}
function loginError(error) {
return {
type: LOGIN_ERROR,
error
}
}
// import AuthService to deal with all the actions related to auth
const lock = new Auth0Lock('secret', 'secret', {
auth: {
redirectUrl: 'http://localhost:3000/callback',
responseType: 'token'
}
})
lock.on('authenticated', authResult => {
console.log('Im authenticated')
return dispatch => {
return dispatch(loginSuccess({}))
}
})
lock.on('authorization_error', error => {
return dispatch => dispatch(loginError(error))
})
export function login() {
lock.show()
return dispatch => {return dispatch(loginRequest())}
}
Now when I click on the login button, redux logger shows me LOGIN_REQUEST action dispatched, I see the lock widget, I can login, it redirects to auth0.com then back to my localhost:3000/callback with a pretty token. Everything is fine, I see the Im authenticated message in my console, but redux logger doesn't show me that the LOGIN_SUCCESS action has been dispatched.
I'm new to Redux, and I guess I'm missing one thing, but I cannot get grab of it. Thanks!
I finally put in inside actions.js, I created a new function called checkLogin()
// actions.js
const authService = new AuthService(process.env.AUTH0_CLIENT_ID, process.env.AUTH0_DOMAIN)
// Listen to authenticated event from AuthService and get the profile of the user
// Done on every page startup
export function checkLogin() {
return (dispatch) => {
// Add callback for lock's `authenticated` event
authService.lock.on('authenticated', (authResult) => {
authService.lock.getProfile(authResult.idToken, (error, profile) => {
if (error)
return dispatch(loginError(error))
AuthService.setToken(authResult.idToken) // static method
AuthService.setProfile(profile) // static method
return dispatch(loginSuccess(profile))
})
})
// Add callback for lock's `authorization_error` event
authService.lock.on('authorization_error', (error) => dispatch(loginError(error)))
}
}
And in the constructor of my App component, I call it
import React from 'react'
import HeaderContainer from '../../containers/HeaderContainer'
class App extends React.Component {
constructor(props) {
super(props)
this.props.checkLogin() // check is Auth0 lock is authenticating after login callback
}
render() {
return(
<div>
<HeaderContainer />
{this.props.children}
</div>
)
}
}
App.propTypes = {
children: React.PropTypes.element.isRequired,
checkLogin: React.PropTypes.func.isRequired
}
export default App
See here for full source code: https://github.com/amaurymartiny/react-redux-auth0-kit
My Reactjs knowledge is limited, but this was starting to be to long for a comment...
Should you not be calling store.dispatch(...) from the lock events?
Having those events return a function won't do anything unless someone invokes the function that is returned and to my knowledge Lock does not do anything with the return value of the callback function you pass as an event handler.
I think what's happening is auth0 redirects the browser window to the login authority (auth0 itself, Facebook, Google, etc.) then redirects you back to your app, which reloads your page, essentially wiping out all state. So your dispatch is sent, then the page reloads, which wipes out your state. Logging in appears to work if you use localStorage instead of redux state, but I'm not sure how that's going to affect all the other state I will need to put in my app.