Update at the bottom of post
I have a React container component, AppContainer that detects if the user is authenticated. If the user is authenticated, it displays the routes, app, header, etc. If the user is un-authenticated, it displays a Login component.
The AppContainer is a connected component (using react-redux). The mapStateToProps and mapDispatchToProps are as follows:
const mapStateToProps = function(state) {
return {
isAuthenticated: state.Login.isAuthenticated,
}
}
const mapDispatchToProps = function(dispatch, ownProps) {
return {
loginSuccess: (user) => {
console.log("before dispatch")
dispatch(loginSuccess(user))
},
}
}
The loginSuccess function that is being dispatched is an action creator that simply stores the user information in the redux store. The default state of Login.isAuthenticated is false.
In componentDidMount() I check if this.props.isAuthenticated (from the user information in the redux store) is true. If not, I check if the tokenId is in the localStorage. If the token is in localStorage, I dispatch the loginSuccess action to add that information to the redux store.
Then, since that info is in the Redux store, the component will update and show the protected material. This works fine.
My componentDidMount function is as follows:
componentDidMount() {
if (this.props.isAuthenticated) {
console.log("REDUX AUTH'D")
} else {
if (localStorage.getItem("isAuthenticated") && !this.props.isAuthenticated) {
console.log("BROWSER AUTHD, fire redux action")
this.props.loginSuccess({
profileObj: localStorage.getItem("profileObj"),
tokenObj: localStorage.getItem("tokenObj"),
tokenId: localStorage.getItem("tokenId"),
})
}
}
}
The only issue is that I am getting the following warning:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the t component.
Though the error given indicates a problem with setState(), I am not calling setState() anywhere in my entire program, so... But removing the this.props.loginSuccess({ ... in componentDidMount also removes the error.
The log statements in my code print before the error and the component does render the protected information as intended if the auth is present. Why does this error occur if the component seems to be working?
Update:
Looking at the stack trace shows that it is coming from the google-login utility I am using.
This is the code for that component: https://github.com/anthonyjgrove/react-google-login/blob/master/src/google.js
This was a problem with the google-login React component provided by a NPM package. I fixed this by rendering the google-login component conditionally (in its own container component, not featured in the original question) based on the isAuthenticated value in the Redux state.
Related
I made a simple React Redux application that fetches a list of posts from jsonplaceholder and within it is a form that allows users to send a POST request. When I send a post request according to Redux DevTools extension it is added successfully marked as post number 101. Here is its snapshot
but the problem is after clicking the submit button 3 times it shows up on the screen.
The first two clicks show neither the title nor its body but it starts showing up on the third click.
This is Posts.jsx file and here is how I used componentDidUpdate to update the component after post request.
class Posts extends Component {
componentDidMount(){
this.props.fetchPosts();
}
componentDidUpdate(nextProps) {
if (nextProps.newPost) {
this.props.posts.unshift(nextProps.newPost);
}
}
renderPosts(){ // cutted for brevity }
render() {
return (
{this.renderPosts()}
)
}
}
const mapStateToProps = (state) => {
return {
posts: state.posts.items,
newPost: state.posts.item,
}
}
export default connect(mapStateToProps, { fetchPosts })(Posts);
Here is its GitHub link repository.
The only error I am getting is the below error.
index.js:1 Warning: Each child in a list should have a unique "key" prop.
I don't believe this has anything to do with rendering the new post, but I already specified a "key" while looping through components.
What I am doing wrong during the course of this post request? Thank You.
You are using the wrong lifeCycle method. in order to get the nexProps you have to use componentWillReceiveProps instead of componentDidUpdate.
componentDidUpdate will give you the previous Props and previous State.
componentWillReceiveProps(nextProps) {
if (nextProps.newPost) {
this.props.posts.unshift(nextProps.newPost);
}
}
The above snippet should work.
But this method is deprecated. react introduced an alternative (kind of) of this. which is called getDerivedStateFromProps. The problem is it is a static method and you can't access previous props (this.props) inside this method.
If you need it you did something wrong as it is an anti-pattern.
I'm fetching data for GameChart from api and change the redux state. Inside GameChart.jsx file I drawing chart when componentDidUpdate called. But redux state change sometimes don't call componentDidUpdate.
Console logs of GameChart.jsx lifecycles
GameChart.jsx Console logs, when componentDidUpdate didn't called (it happens magically with 50/50 chance...):
Chart mapStateToProps
Chart componentDidMount
Chart mapStateToProps
GameChart.jsx Console logs, when all is OK, and componentDidUpdate called, when redux state changed:
Chart mapStateToProps
Chart componentDidMount
Chart mapStateToProps
Chart mapStateToProps
Chart componentDidUpdate
Chart mapStateToProps
Here is very schematic code:
GameChart.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
class GameChart extends Component {
...
componentDidMount() { console.log("Chart componentDidMount") }
componentDidUpdate() {
console.log("Chart componentDidUpdate");
/* When socket.js call initGame, and redux update state, I need to
render canvas from this state inside GameChart.jsx */
... Drawing chart, using data from this.props.game ...
}
render() {
return (
<canvas ref={...} ... />
);
}
}
const mapStateToProps = state => {
console.log('Chart mapStateToProps');
return { game: state.game }
};
export default connect(mapStateToProps)(GameChart);
gameReducer.js. Action, which initGame called
case INIT_GAME:
return {
status: action.payload.roll !== null ? "rolling" : "betting",
...action.payload
}
socket.js
/* When websocket connected, it sends current game state to client.
Client need to write this state in redux state and re-render
GameChart.jsx, using this new rewrited by dispatch action state */
socket.onmessage = (msg) => {
store.dispatch(initGame)(msg);
}
I checked redux-dev-tools. Diff tab displays, that game inited successfully and state changed to what websocket gave. I checked out for redux state mutations, there is no state mutations. When refreshing page, sometimes magically componentDidUpdate didn't called, but sometimes it called. It could call arbitrarily with the chance of 50/50.
mapStateToProps connects your redux store to your component's props. The component doesn't rerender when your props are updated(unless the props belong to the state of the parent's state).
componentDidUpdate is called when your component's state is updated. A change of props will not affect this.
In short, the props from redux store don't follow the lifecycle hooks of your component.
You will probably need to trigger a rerender your component from where you are calling a dispatch for the redux state.
mapStateToProps is called after dispatch has run.
Then Redux does check if result returned is referentially different from previous one. Only if there are differences component is provided with new props. That runs componentDidUpdate and render.
So you either have referentially same data returned and it's legit reason to miss updating component(and save some time for better performance).
Or your reducer is mutating state(that's no-no for Redux) and you should fix that.
I have a route (using React-Router) with component which it renders. Every time this route opened and its component created I need to reset some part of Redux state (one reducer's state in fact), used in this component. This reducer is shared in some other parts of the app, so I use Redux state and not local component's state. So how can I reset the reducer's state every time my component created? I am wondering about best practice to do this.
I think if I'll dispatch actions in componentDidMount method, there will be blinking of previous state for some second.
Can I dispatch action to reset some reducer's state in component's constructor?
Is there any better approach? Can I somehow to set initial state in connect() function, so component will have resetted state each time it created? I check the docs, but I cannot find some argument for this.
Yes, you can dispatch action in constructor to change reducer state
constructor(prop){
super(prop);
prop.dispatch(action);
}
Another approach you can try is setting default props so that you don't need to call reducer(dispatch action)
ButtonComponent.defaultProps = {
message: defaultValue,
};
One possible solution I can think of...
If you could go with the first approach, you can try to stop the previous state being shown while component is being re-rendered with reset state.
The only phase during which you would see the prevState is during the initial render. How about an instance variable to track the render count.
A rough draft.
import React from "react";
import { connect } from "react-redux";
import { add, reset } from "./actions";
class Topics extends React.Component {
renderCount = 0;
componentDidMount() {
// Dispatch actions to reset the redux state
// When the connected props change, component should re-render
this.props.reset();
}
render() {
this.renderCount++;
if (this.renderCount > 1) {
return (
<div>
{this.props.topics.map(topic => (
<h3 id={topic}>{topic}</h3>
))}
</div>
);
} else {
return "Initializing"; // You can return even null
}
}
}
const mapStateToProps = state => ({ topics: state });
const mapDispatchToProps = (dispatch) => {
return {
add(value){
dispatch(add(value));
},
reset(){
dispatch(reset());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Topics);
Here renderCount is a class variable, that keeps incrementing on component render. Show a fallback UI on first render to avoid previous state from being shown and on second render (due to redux store update), you could display the store data.
A working example added below. I have added an approach to avoid the fallback UI as well. Have a look if it helps.
https://stackblitz.com/edit/react-router-starter-fwxgnl?file=components%2FTopics.js
This is my component's class:
import React, { Component } from 'react';
import {connect} from 'react-redux';
import Button from '../UI/Button/Button';
import * as actions from '../../store/actions';
class Password extends Component {
submitPassword=(e)=>{
e.preventDefault();
this.props.submitPassword(this.state.password, this.props.levelNumber);
}
render() {
<Button clicked={this.submitPassword} >
Submit password
</Button>
}
}
const mapStateToProps = state => {
return {
};
}
const mapDispatchToProps = dispatch => {
return {
submitPassword: (password,levelNumber) => dispatch(actions.submitPassword(password,levelNumber))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Password);
and this is my action:
export const submitPassword = () => {
// HERE ALL MY LOGIC !!!
return {
level:undefined,
type:actions.PASSWORD
}
}
The code working all correctly including params and logic.
I wanna that every time that finish to execute the function submitPassword A third component refresh/reload with the new props. Attention! My third component is parent, not child!
It's possibile to send a command from action to component? How can I do it? I have already tried with:
componentWillReceiveProps() {
console.log("new props");
}
in my component but he can not take the event.
normally a structure my redux store as follows
{
entities: {},
ui: {},
domain:{}
}
so for example when you execute submitPassword you execute all the logic and depending on the result you can dispatch another action to update the ui or the domain part so the componentes that are connected respond to those changes.
The UI holds information about UI changes, like if you are submiting info display a progress bar or if it has to display a dialog.
The domain part holds information related to the whole webapp, like loggin info and notifications.
You don't need always to pass new props for redux state to be accessed.
As redux is having immutable state, you'll always be getting new updated state no matter the previous one. So this will enforce your component to update props to get latest state from redux. This is done by <Provider> wrapper attached on root level.
So hence your props will be having new values whenever redux state gets updated.
The lifecycle you are looking is static getderivedstatefromprops(). This lifecycle gets executed whenever props are changed/updated.
I made visual implementation on fly that can aid you the picture. Here Redux State means Redux Store
I have a higher order component(HOC) which is used for authorizing and refreshing the user sessions.
This is the RequireAuth HOC and it's componentWillMount
componentWillMount() {
console.log('HOC componentWillMount');
// checks with authorizer that token is valid if not refresh token
getUserFromLocalStorage()
// update redux with new refresh token
.then(data => {
console.log('getUserFromLocalStorage')
this.props.setUserToReduxState(data);
console.log(
'user sucess retrieved and user setUserToReduxState: ',
data
);
})
.catch(() => {
logoutUserFromReduxState();
});
}
}
This is my route call
<Route exact path="/devices" component={RequireAuth(Devices)} />
and here is my componentDidMount for the Devices component
componentDidMount() {
console.log('componentDidMount')
// calls api
this.loadData();
}
This child component calls the API which requires a token.
However when the token get's refreshed, it refreshes before the API get's called in the devices component but the redux action that get's returns from the '.then' promise in the HOC doesn't update the redux state before the api call in the child component.
Is there a way to make sure the token has been update/redux state before the child tries to call the API?
I think you can add a condition inside your HOC not to render your "enhanced component" - in your case Devices until the setUserToReduxState is resolved with the user's token. only after the user's token is set your Devices component will render the loadDate method will be triggered.
e.g. - you can map the state of your user to a variable. If the user does not exist - do not render the component you pass to your HOC.
EDIT: (added code snippet)
// In your render method get the current user from redux - see mapStateToProps functoion
render(){
const { user } = this.props;
return (
return (
<div>
{
user && user.token
? <Component {...this.props} />
: null // Here you can replace this with spinner if you want..
}
</div>
)
)
}
const mapStateToProps = (state) => ({
user: state.auth.user
})
Now when the user is set your component will re-render and the Devices component will trigger the loadData method when the token is set for sure.