ComponentWIllReceiveProps not getting called unless page is refreshed - reactjs

I am building a react native application but I noticed that componentWillReceiveProps is not getting called as soon as I dispatch some actions to the redux store, it only gets called when I refresh the screen.
Component
import React from 'react';
import { connect } from 'react-redux';
import { renderLogin } from '../../components/Auth/Login';
class HomeScreen extends React.Component {
componentWillReceiveProps(props) {
const { navigate } = props.navigation;
if (props.userData.authenticated) {
navigate('dashboard')
}
}
login = () => {
renderLogin()
}
render() {
const { navigate } = this.props.navigation;
return (
<Container style={styles.home}>
// Some data
</container>
)
}
}
function mapStateToProps(state) {
return {
userData: state.auth
}
}
export default connect(mapStateToProps)(HomeScreen)
RenderLogin
export function renderLogin() {
auth0
.webAuth
.authorize({
scope: 'openid email profile',
audience: 'https://siteurl.auth0.com/userinfo'
})
.then(function (credentials) {
loginAction(credentials)
}
)
.catch(error => console.log(error))
}
loginAction
const store = configureStore();
export function loginAction(credentials) {
const decoded = decode(credentials.idToken);
saveItem('token', credentials.idToken)
store.dispatch(setCurrentUser(decoded));
}
export async function saveItem(item, selectedValue) {
try {
await AsyncStorage.setItem(item, JSON.stringify(selectedValue));
const decoded = decode(selectedValue);
} catch (error) {
console.error('AsyncStorage error: ' + error.message);
}
}

I believe your problem has something to do with mapStateToProps, i.e. when you have updated your state in redux but not yet map the new state to your props, therefore props in HomeScreen will remain unchanged and componentWillReceiveProps will only be triggered once.
Have a read on Proper use of react-redux connect and Understanding React-Redux and mapStateToProps.

Related

Can I use a react HOC in this way without future pitfalls

I learn ReactJs and have a design Composition question about ReactJs higher order component (HOC).
In the code below App.jsx I use this withAuthentication HOC that initializes app core processes. This HOC value is not used in the App.js. Therefore I must suppress all withAuthentication HOC render callbaks and I do that in the shouldComponentUpdate by returning false.
(I use this HOC in many other places to the get HOC's value but not in App.jsx)
File App.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { getAlbumData } from './redux/albumData/albumData.actions';
import { getMetaData } from './redux/albumMetaData/albumMetaData.actions';
import Header from './components/structure/Header';
import Content from './components/structure/Content';
import Footer from './components/structure/Footer';
import { withAuthentication } from './session';
import './styles/index.css';
class App extends Component {
componentDidMount() {
const { getMeta, getAlbum } = this.props;
getMeta();
getAlbum();
}
shouldComponentUpdate() {
// suppress render for now boilerplate, since withAuthentication
// wrapper is only used for initialization. App don't need the value
return false;
}
render() {
return (
<div>
<Header />
<Content />
<Footer />
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
getMeta: () => dispatch(getMetaData()),
getAlbum: () => dispatch(getAlbumData()),
});
export default compose(connect(null, mapDispatchToProps), withAuthentication)(App);
The HOC rwapper WithAuthentication below is a standard HOC that render Component(App) when changes are made to Firebase user Document, like user-role changes, user auth-state changes..
File WithAuthentication .jsx
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import AuthUserContext from './context';
import { withFirebase } from '../firebase';
import * as ROLES from '../constants/roles';
import { setCurrentUser, startUserListener } from '../redux/userData/user.actions';
import { selectUserSlice } from '../redux/userData/user.selectors';
const WithAuthentication = Component => {
class withAuthentication extends React.Component {
constructor() {
super();
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
componentDidMount() {
const { firebase, setUser, startUserListen } = this.props;
this.authListener = firebase.onAuthUserListener(
authUser => {
this.setState({ authUser });
setUser(authUser);
startUserListen();
},
() => {
localStorage.removeItem('authUser');
this.setState({ authUser: null });
const roles = [];
roles.push(ROLES.ANON);
firebase
.doSignInAnonymously()
.then(authUser => {
if (process.env.NODE_ENV !== 'production')
console.log(`Sucessfully signed in to Firebase Anonymously with UID: ${firebase.getCurrentUserUid()}`);
firebase.doLogEvent('login', { method: 'Anonymous' });
firebase
.userDoc(authUser.user.uid)
.set({
displayName: `User-${authUser.user.uid.substring(0, 6)}`,
roles,
date: firebase.fieldValue.serverTimestamp(),
})
.then(() => {
console.log('New user saved to Firestore!');
})
.catch(error => {
console.log(`Could not save user to Firestore! ${error.code}`);
});
})
.catch(error => {
console.error(`Failed to sign in to Firebase: ${error.code} - ${error.message}`);
});
},
);
}
componentWillUnmount() {
this.authListener();
}
render() {
const { currentUser } = this.props;
let { authUser } = this.state;
// ALl changes to user object will trigger an update
if (currentUser) authUser = currentUser;
return (
<AuthUserContext.Provider value={authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
withAuthentication.whyDidYouRender = true;
const mapDispatchToProps = dispatch => ({
setUser: authUser => dispatch(setCurrentUser(authUser)),
startUserListen: () => dispatch(startUserListener()),
});
const mapStateToProps = state => {
return {
currentUser: selectUserSlice(state),
};
};
return compose(connect(mapStateToProps, mapDispatchToProps), withFirebase)(withAuthentication);
};
export default WithAuthentication;
My question is will this hit me later with problems or is this ok to do it like this?
I know a HOC is not suppose to be used like this. The WithAuthentication is taking care of Authentication against Firebase and then render on all user object changes both local and from Firestore listener snapshot.
This HOC is used in many other places correctly but App.jsx only need to initialize the HOC and never use it's service.
My question is will this hit me later with problems or is this ok to do it like this?

Why can't I call this.state in my redux reducer?

I made a reducer that fetches admins, and I want it to display certain admins when I call it in my reducer but I am getting Undefined.
I am still very new to redux so apologies for my mistakes.
I tried to include all the relevant folders:
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../store/actions';
class App extends Component {
async componentDidMount() {
fetch(constants.adminUrl + '/admins/data', {
method: 'GET'
}).then((res) => {
return res.json()
}).then(async (res) => {
this.props.setAdminsInColumns(res.admins)
}).catch((error) => {
toast.error(error.message)
})
}
render() {
return (
{/* SOME CODE */}
);
}
}
let app = connect(null, actions)(App);
export default app;
columnsReducer.js
import { FETCH_ADMINS } from '../actions/types'
import { Link } from 'react-router-dom'
import constants from '../../static/global/index'
import React from 'react';
import { toast } from 'react-toastify'
const initialState = {
admins: [],
{
Header: "Responsible",
accessor: "responsibleAdmin",
style: { textAlign: "center" },
// Place where I want to fetch certain admins and get undefined
Cell: props => <span>{props.value && this.state.admins.name ? this.state.admins.find(admin => admin.id === props.value).name : props.value}</span>
}
}
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_ADMINS:
return { ...state, admins: action.admins}
default:
return state
}
}
index.js
import { FETCH_ADMINS } from "./types"
/**
* starts loader for setting admin
*/
export const setAdminsInColumns = (admins) => async dispatch => {
dispatch({ type: FETCH_ADMINS, admins })
}
types.js
export const FETCH_ADMINS = 'fetch_admins'
When I console.log(action.admins) inside the switch case FETCH_ADMINS in the columnsReducer.js file, I can see all the admin information I want, is there a way to make the state global in the columnsReducer.js file so I can read it?
Any help is appreciated!
use mapStateToProps in the connect method. like below
let mapStateToProps = (state)=>{
return {
admins :[yourcolumnsReducer].admins
}
}
let app = connect(mapStateToProps, actions)(App);
//you can use this.props.admins inside your component
MapStateToProps reference

Redirect automatically from one component to another one after few seconds - react

I try to redirect the user from one component to another after a few seconds.
The user lands on a page and after few second he is automatically redirect to another page.
I thought to redirect in an action but I am not sure if it is the best idea (if you have easier way to do it I am interested).
My code so far:
a basic component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import { redirectToProfile } from "../../actions/searchActions";
class Search extends Component {
componentDidMount() {
setTimeout(this.props.redirectToProfile(this.props.history), 3000);
}
render() {
return (
<div>
<h1>search page</h1>
</div>
);
}
}
export default connect(
null,
{ redirectToProfile }
)(withRouter(Search));
and the action:
export const redirectToProfile = history => {
history.push("/");
};
So far I have an error message:
Actions must be plain objects. Use custom middleware for async actions.
After some research I see that some people resolve the problem with the middleware thunk but I am already using it so I don't know what to do.
Thank you for your help.
Why not use the <Redirect/> component that react-router provides? I think that's clearer and more in keeping with React's declarative model, rather than hiding away the logic in an imperative thunk/action.
class Foo extends Component {
state = {
redirect: false
}
componentDidMount() {
this.id = setTimeout(() => this.setState({ redirect: true }), 1000)
}
componentWillUnmount() {
clearTimeout(this.id)
}
render() {
return this.state.redirect
? <Redirect to="/bar" />
: <div>Content</div>
}
}
state = {
redirect: false // add a redirect flag
};
componentDidMount() {
// only change the redirect flag after 5 seconds if user is not logged in
if (!auth) {
this.timeout = setTimeout(() => this.setState({ redirect: true }), 5000);
}
}
componentWillUnmount() {
// clear the timeer just in case
clearTimeout(this.timeout);
}
render() {
// this is the key:
// 1. when this is first invoked, redirect flag isn't set to true until 5 seconds later
// 2. so it will step into the first else block
// 3. display content based on auth status, NOT based on redirect flag
// 4. 5 seconds later, redirect flag is set to true, this is invoked again
// 5. this time, it will get into the redirect block to go to the sign in page
if (this.state.redirect) {
return <Redirect to="/signin" />;
} else {
if (!auth) {
return (
<div className="center">
<h5>You need to login first to register a course</h5>
</div>
);
} else {
return <div>Registration Page</div>;
}
}
}
If you are already using redux thunk and it's included in your project, you can create the action as following.
export const redirectToProfile = history => {
return (dispatch, setState) => {
history.push('/');
}
};
// shorter like this.
export const redirectToProfile = history => () => {
history.push('/');
}
// and even shorter...
export const redirectToProfile = history => () => history.push('/');
Alternative:
You can also call history.push('/'); right in the component if you adjust you default export of the Search component. This is preferred as you don't have overhead of creating an additional action and dispatching it through redux.
Change your export to...
export default withRouter(connect(mapStateToProps)(Search));
Then in your component use it as following...
componentDidMount() {
setTimeout(this.props.history.push('/'), 3000);
}
//This is an example with functional commponent.
import React, {useEffect, useState} from 'react'
import { useNavigate } from "react-router-dom";
function Splash() {
let navigate = useNavigate();
const [time, setTime] = useState(false);
useEffect(() => {
setTimeout(() => {
setTime(true)
}, 2000)
setTime(false);
}, []);
return time ? navigate('/dashboard') : navigate('/account');
}
export default Splash;

React-native Redux action not dispatching

I am in the process of migrating an app from React to React Native and am running into an issue with Redux not dispatching the action to Reducer.
My root component looks like this:
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux';
import Main from '../main/main';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
class App extends Component {
render() {
console.log('Rendering root.js component');
console.log(this.props);
const { dispatch, isAuthenticated, errorMessage, game, communication } = this.props
return (
<View style={styles.appBody}>
<Main
dispatch={dispatch}
game={game}
communication={communication}
/>
</View>
)
}
}
App.propTypes = {
dispatch: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool.isRequired,
errorMessage: PropTypes.string,
}
function mapStateToProps(state) {
const { auth } = state
const { game } = state
const { communication } = state
const { isAuthenticated, errorMessage } = auth
return {
isAuthenticated,
errorMessage,
game,
communication
}
}
const styles = StyleSheet.create({
appBody: {
}
});
export default connect(mapStateToProps)(App)
Then a 'lobby' subcomponent has the dispatch function from Redux as a prop passed to it. This component connects to a seperate javascript file, and passes the props to it so that that seperate file has access to the dispatch function:
componentWillMount() {
coreClient.init(this);
}
In that file I do this:
const init = function(view) {
socket.on('connectToLobby', (data) => {
console.log('Lobby connected!');
console.log(data);
console.log(view.props) // shows the dispatch function just fine.
view.props.dispatch(connectLobbyAction(data));
});
}
The action itself also shows a console log I put there, just that it never dispatches.
export const LOBBY_CONNECT_SUCCESS = 'LOBBY_CONNECT_SUCCESS';
export function connectLobbyAction(data) {
console.log('Action on connected to lobby!')
return {
type: LOBBY_CONNECT_SUCCESS,
payload: data
}
}
I feel a bit lost, would appreciate some feedback :)
EDIT: Reducer snippet:
var Symbol = require('es6-symbol');
import {
LOBBY_CONNECT_SUCCESS
} from './../actions/actions'
function game(state = {
//the state, cut to keep things clear.
}, action) {
switch (action.type) {
case LOBBY_CONNECT_SUCCESS:
console.log('reducer connect lobby')
return Object.assign({}, state, {
...state,
user : {
...state.user,
id : action.payload.id,
connected : action.payload.connected
},
match : {
...state.match,
queuePosition : action.payload.position,
players : action.payload.playerList,
room : 'lobby'
},
isFetching: false,
})
default:
return state
}
}
const app = combineReducers({
game,
//etc.
})

How to keep React components state after browser refresh

Thank you reading my first question.
I trying to auth With Shared Root use react, react-router and firebase.
So, I want to keep App.js 's user state. but when I tried to refresh the browser, user state was not found.
I've tried to save to localstorage. But is there a way to keep state on component after browser refresh without localStorage?
App.js
import React, { Component, PropTypes } from 'react'
import Rebase from 're-base'
import auth from './config/auth'
const base = Rebase.createClass('https://myapp.firebaseio.com')
export default class App extends Component {
constructor (props) {
super(props)
this.state = {
loggedIn: auth.loggedIn(),
user: {}
}
}
_updateAuth (loggedIn, user) {
this.setState({
loggedIn: !!loggedIn,
user: user
})
}
componentWillMount () {
auth.onChange = this._updateAuth.bind(this)
auth.login() // save localStorage
}
render () {
return (
<div>
{ this.props.children &&
React.cloneElement(this.props.children, {
user: this.state.user
})
}
</div>
)
}
}
App.propTypes = {
children: PropTypes.any
}
auth.js
import Rebase from 're-base'
const base = Rebase.createClass('https://myapp.firebaseio.com')
export default {
loggedIn () {
return !!base.getAuth()
},
login (providers, cb) {
if (Boolean(base.getAuth())) {
this.onChange(true, this.getUser())
return
}
// I think this is weird...
if (!providers) {
return
}
base.authWithOAuthPopup(providers, (err, authData) => {
if (err) {
console.log('Login Failed!', err)
} else {
console.log('Authenticated successfully with payload: ', authData)
localStorage.setItem('user', JSON.stringify({
name: base.getAuth()[providers].displayName,
icon: base.getAuth()[providers].profileImageURL
}))
this.onChange(true, this.getUser())
if (cb) { cb() }
}
})
},
logout (cb) {
base.unauth()
localStorage.clear()
this.onChange(false, null)
if (cb) { cb() }
},
onChange () {},
getUser: function () { return JSON.parse(localStorage.getItem('user')) }
}
Login.js
import React, { Component } from 'react'
import auth from './config/auth.js'
export default class Login extends Component {
constructor (props, context) {
super(props)
}
_login (authType) {
auth.login(authType, data => {
this.context.router.replace('/authenticated')
})
}
render () {
return (
<div className='login'>
<button onClick={this._login.bind(this, 'twitter')}>Login with Twitter account</button>
<button onClick={this._login.bind(this, 'facebook')}>Login with Facebook account</button>
</div>
)
}
}
Login.contextTypes = {
router: React.PropTypes.object.isRequired
}
If you reload the page through a browser refresh, your component tree and state will reset to initial state.
To restore a previous state after a page reload in browser, you have to
save state locally (localstorage/IndexedDB)
and/ or at server side to reload.
And build your page in such a way that on initialisation, a check is made for previously saved local state and/or server state, and if found, the previous state will be restored.

Resources