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.
Related
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
i have a chart component and i'm fetching data from an api through the props data to put in chart but when i'm sending the parent to chart component as props it's not updating the parent state is updated but the child props are not updating and if i log it in
componentWillReceiveProps(props) {
console.log(props);
// it shows updated value but if i do this
this.loadData();
}
loadData = () => {
console.log(props)
// it's value is old and not updated
}
i have check it in parent the state is updated is and i have a click event in parent when i first click it the parent state changes but chart component props will not change and if i click second time chart component props will the state of first time click state
here's my github repo https://github.com/naveenkash/forex
look in chart component props and pages index.js chart in getting props through index.js
because in componentWillReceiveProps props is reference to new props, they are not yet available in other parts of class, so you should pass them to loadData
componentWillReceiveProps(props){
this.loadData(props);
}
loadData=(props)=>{
console.log(props)
}
What Dmitry said is accurate, this.props hasn't changed as of componentWillReceiveProps(props). Therefore when you call this.loadData() you're still calling it with the old props.
Note that componentWillReceiveProps(props) is
unsafe
You should use componentDidUpdate(previousProps), where you can call this.loadData() and it will execute with updated this.props.
It's js scope issue not react event lift cycle issue, considering changing the code as below:
componentWillReceiveProps(props) {
console.log(props);
// it shows updated value but if i do this
this.loadData(props);
}
loadData = (newProps) => {
console.log(newProps)
// it's value is old and not updated
}
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 want to create an app with react and redux. My component subscribed to several states from the redux store, some of the state-data need to be prepared before the rendering can take place. Do I need to put the prepareData function into componentWillReceiveProps and write it to the state afterwards? It seems to create a lot of queries in the componentWillReceiveProps. Is there a best practice?
componentWillReceiveProps(nextProps) {
if (this.props.dataUser !== nextProps.dataUser) {
this.prepareData(nextProps.dataUser);
}
if (this.props.dataProject !== nextProps.dataProject) {
.....
}
if (this.props.dataTasks !== nextProps.dataTasks) {
.....
}
}
As Axnyff suggests, you can do your data preparation in mapStateToProps, this will trigger a render each time your redux state updates (your component can be stateless this way) :
mapStateToProps = (state) => {
const dataUserPrepared = prepareData(state.dataUser);
return { dataUser: dataUserPrepared };
}
If you have a lot of different data to prepare, which updates individually, that can be a loss in performance.
In this case you can use componentWillReceiveProps like in your question, this is fine because the setState in your prepareData() function will be batched with the received props to trigger only one render per prop update.
If you were using an app without redux then the solution would be to prepare your data before you call this.setState().
I believe the same solution applies to when using redux, your can prepare your data inside your action because you return the action object having a type and payload.
You can also prepare your data inside your reducer before returning the state object.
You could even prepare your data inside mapStateToProps of your component.
But in case you want to specific conditions under which component should re-render when state changes, then you do that in shouldComponentUpdate()
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.