React component props don't get updated with redux store - javascript

Button.js component
import React from "react"
import "../styles/button.scss"
import store from "../index"
class Button extends React.Component {
constructor(props) {
super(props)
this.buttonTextChanger()
}
buttonTextChanger() {
this.buttonText = "MainPage" === this.props.state.page ? "Az adatokhoz" : "A főoldalra"
}
logger = (actualPage) => {
this.props.onButtonClick(actualPage)
console.log("state from store before textchange", store.getState())
console.log("state from props before textchange", this.props.state)
this.buttonTextChanger()
}
render() {
return (
<div id="container">
<button className="learn-more" onClick = {() => this.logger(this.props.state.page)}>
<span className="circle" aria-hidden="true">
<span className="icon arrow"></span>
</span>
<span className="button-text">{this.buttonText}</span>
</button>
</div>
)
}
}
export default Button
My problem is that the component's props don't seem to be updated with the redux store. The redux store updates to the correct value after the onClick function runs, mapStateToProps also runs with the correct state and still after these if I try to log the state from the prop I get the old value. If I do the same log in the render function before returning the JSX I get the correct state from props and I can't get my head around why it isn't updated immediately after the redux store is.
So if I modify the code to the following it works as expected:
logger = (actualPage) => {
this.props.onButtonClick(actualPage)
console.log("state from store before textchange", store.getState())
}
render() {
console.log("state from props before textchange", this.props.state)
this.buttonTextChanger()
return (
<div id="container">
<button className="learn-more" onClick = {() => this.logger(this.props.state.page)}>
<span className="circle" aria-hidden="true">
<span className="icon arrow"></span>
</span>
<span className="button-text">{this.buttonText}</span>
</button>
</div>
)
}
}
Reducer function
import { combineReducers } from "redux"
export const changePageReducer = (state = {page : "MainPage"}, action) => {
if (action.type === "CHANGE_PAGE")
if (action.payload !== state.page) {
return action.payload
}
return state.page
}
export const combinedReducers = combineReducers({page : changePageReducer})
Button container
import { connect } from "react-redux"
import Button from "../components/Button"
import changePage from "../actions/changePage"
const mapStateToProps = (state) => {
console.log("az injectelt state", state)
return {state}
}
const mapDispatchToProps = (dispatch) => {
return {
onButtonClick : (page) => {
switch (page) {
case "MainPage":
dispatch(changePage("DataPage"))
break
case "DataPage":
dispatch(changePage("MainPage"))
break
default:
dispatch(changePage("MainPage"))
}
}
}
}
const ChangePageContainer = connect(mapStateToProps, mapDispatchToProps)(Button)
export default ChangePageContainer
But I'd like to extract the buttonTextChanger() function call from the render function and call it on click.
TLDR:
the problem:
logger = (actualPage) => {
console.log("prop state value before dispatch", this.props.state)
console.log("store value before dispatch", store.getState())
this.props.onButtonClick(actualPage)
console.log("prop state value after dispatch", this.props.state)
console.log("store value after dispatch", store.getState())
}
also there is a console.log in the mapStateToProps function to see the state that gets passed to props.
This yields:
prop state value before dispatch {page: "MainPage"}
store value before dispatch {page: "MainPage"}
state when mapStateToProps function called {page: "DataPage"}
store value after dispatch {page: "DataPage"}
prop state value after dispatch {page: "MainPage"}
So the prop doesn't get updated.

So, you can't wrap your head around why this.props.state isn't updated even after calling the dispatcher?
You see,
Redux is entirely built on functional programming, and now with hooks, React is also fully moving towards Functional Programming, hell even JavaScript was originally built as Functional Programming.
One thing that's entirely different from OOP and the coolest thing too is that, There are no mutations in Functional Programming. There isn't. Vanilla Redux is fully FP having only pure functions - functions without side effects. That's why you need Redux Saga or other library for making API calls - unpure functions.
Cutting to the chase,
logger = (actualPage) => {
// here,
// actualPage => new value
// this.props.state => old value
// they will remain as such throughout this function
console.log("prop state value before dispatch", this.props.state)
console.log("store value before dispatch", store.getState())
this.props.onButtonClick(actualPage)
// even if you update the Redux Store,
// `this.props.state` will still have the old value
// since this value will not be mutated
console.log("prop state value after dispatch", this.props.state)
console.log("store value after dispatch", store.getState())
}
Then what about store.getState(), their values are updated, you may ask.
Notice how it's not store.getState but instead store.getState()? Yes it is a function instead of a value. Whenever you call them, they return the latest value and there isn't any mutations in them. In your case, the second time you are calling after dispatching the action, hence you get the latest value. It's not mutation, store.getState() just grabbed the latest value there is and returned it to you. logger() will get the new values once that first call is over, then the cycle repeats again.
Action dispatchers will create a new state from the old state, that's why you can time travel in Redux DevTools.
Again recapping
logger = (actualPage) => {
// old value
console.log("prop state value before dispatch", this.props.state)
// old value
console.log("store value before dispatch", store.getState())
// new value
this.props.onButtonClick(actualPage)
// redux would be updated with new values by now
// but still old value - no mutation
console.log("prop state value after dispatch", this.props.state)
// new value
console.log("store value after dispatch", store.getState())
}

Related

local state values cant be equal to redux state values

hi I am using redux in the react and I have a form and the form data (specially the value of the form elements when user types something) is stored inside the local state of my react component. and at the same time I have a dispatch incrementing a counter by one and I call it when onchanged function is called on the form elements. and I show the counter data taken from redux state. so the data stored in redux is the number of keys pressed.
the issue is the value of counter cannot be entered into the form inputs. for example if i press any key (for example type a letter ) my redux counter value would be 1 and now I cant type number 1 in the inputs. the local state does not change.
here is my code:
import * as React from 'react';
import {Box} from "#material-ui/core";
import {FormElements} from "../forms/formElements";
import Typography from "#material-ui/core/Typography";
import {NavLink} from "react-router-dom";
import {connect} from "react-redux";
import ClickAwayListener from "#material-ui/core/ClickAwayListener";
class Login extends React.Component {
state = {
counter: 0,
comps: {
Lusername: {
required: true,
label: "username",
id: "Lusername",
type: "string",
value: ""
},
Lpassword: {
required: true,
type: "password",
id: "Lpassword",
label: "password",
value: ""
}
}
}
handleChange = (event) => {
this.props.onInc(); //redux dispatch
let {id, value} = event.target //local state
let comps = this.state.comps
comps[id].value = value
this.setState({comps: comps})
}
render() {
return (
<ClickAwayListener onClickAway={this.props.onclickAway}>
<Box m={2}>
<p>{this.props.ctr}</p>
<FormElements comps={this.state.comps} handleChange={this.handleChange}
handleSubmit={this.handleSubmit}></FormElements>
<Box mt={2}>
<NavLink to={"/signup"} style={{textDecoration: "none", color: "#0e404c"}}>
<Typography component={"h5"}>don't have an account? signUp</Typography>
</NavLink>
</Box>
</Box>
</ClickAwayListener>
);
};
};
const MapStateToProps = state => {
return {ctr: state.counter}
}
const MapDispatchToProps = dispatch => {
return {
onInc: () => dispatch({type: "INC"})
}
}
export default connect(MapStateToProps, MapDispatchToProps)(Login)
I see 2 problems here. First, you have a local state also called counter. it looks you are confused about redux state. React's local state is not the same as Redux's state, is a total different state. you better remove counter from your local state, there is no point if you use redux for counter state imo.
Second, these lines:
let comps = this.state.comps
comps[id].value = value
this.setState({comps: comps})
comps is an object, reference based, which means at 2nd line you are mutating state directly, which is bad practice and can lead to weird behaviors. keep in mind, comps has also nested object so a shallow destructuring like const comps = { ...this.state.comps } wont be enough. you either need to use a deepClone function from a helper lib or you do something like below to create a whole new object:
const oldComps = this.state.comps
const Lusername = { ...oldComps.Lusername }
const Lpassword = { ...oldComps.Lpassword }
const comps = { Lusername, Lpassword }
comps[id].value = value
That way you are not mutating state directly, and can manipulate it safety.
update
edit as the following:
handleChange = (event) => {
event.persist()
// ...comps logic
this.setState({comps: comps}, this.props.onInc)
}
event is react's synthetic event. It can be nullified for reuse as react docs say. it seems that's the case here.
the second change is for consistency. Increment should be triggered after comps state is updated imho. You can pass your onInc function as second argument, which will be triggered after state is updated.

State updates may be asynchronous, what is exactly this.props?

state = {
persons:[
{id:"cbhc", name:"surya",age:26,sex:"male"},
{id:"rgt", name:"sachin",age:36,sex:"male"},
{id:"cbcchc", name:"rahul",age:46,sex:"male"}
],
showdetails:false,
**counter:0**,
};
The above was the state of data in my application:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
What was exactly here: props.increment ????
My piece of code:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
I want to know what is props.increment ??
my skeleton of component:
import React from "react";
//import mystyles from "./person.module.css";
//import Studentoutput from "./cockpit/cockpit";
const Singlestudent = props => {
console.log("child component skeleton rendering...");
return (
<div>
<p onClick={props.click}>{props.name}</p>
<p>{props.age}</p>
<p>{props.id}</p>
**<p>{props.increment}</p>**
<input type="text" onChange={props.update} value={props.name} />
</div>
);
};
export default Singlestudent;
since my state data is embedded inside with nested array and object, using map method to structure my skeleton comp data as below:
// import React from "react";
// //import mystyles from "./person.module.css";
// const Studentoutput = props => <input type="text" value={props.name} />;
// export default Studentoutput;
import React from "react";
import Singlestudent from "./student/singlestudent";
const Studentinfo = props => {
console.log("child component details rendering...");
return props.details.map((studentinfo, index) => {
return (
<Singlestudent
key={studentinfo.id}
name={studentinfo.name}
age={studentinfo.age}
**increment={props.increment}**
update={props.updated(studentinfo.id)}
click={props.clicked(studentinfo.id)}
/>
);
});
};
export default Studentinfo;
i passed increment={1} , hardcoded it.
now finally passing the above to my main parent which renders on browser
return (
<div className={mystyles.parent}>
<Studentinfo
details={this.state.details}
updated={this.updateStudentHandler}
clicked={this.deleteStudentHandler}
**increment={1}**
/>
</div>
);
from the above code snippet i'm changing my counter value through updateStudentHandler
updateStudentHandler = id => event => {
//debugger;
const studentIndex = this.state.details.findIndex(d => d.id === id);
console.log(studentIndex);
let duplicate = {
...this.state.details[studentIndex]
};
duplicate.name = event.target.value;
const dd = [...this.state.details];
dd[studentIndex] = duplicate;
this.setState({
details: dd
});
this.setState((referencetoprevState, Props) => {
return {
counter: referencetoprevState.counter + Props.increment
};
});
};
as soon as i change the text in input box, my counter should update but it returns NaN, why ????
refer to below screenshot attached
output of counter variable
but if i change the below code
this.setState((state, props) => {
return { counter: state.counter + props.increment };
});
with a value (9000) instead of props.increment results in updating the counter value as expected.
this.setState((state, props) => {
return { counter: state.counter + 9000 };
});
why i need to provide explicitly value not just like props.increment similar to state.counter because state.counter is taking its value as 0 from the previous state but props.increment not taking the value 1 from increment={1} from jsx of user defined component which is Studentinfo comp ??
Any limitations/reasons ??
As the React documentation states:
When React sees an element representing a user-defined component, it passes JSX attributes to this component as a single object. We call this object “props”.
I suggest to read further the official docs, especially Rendering a Component part.
Additionally setState() one here explains further:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with state.
In summary:
Basically you are using setState() function's updater argument in your code which takes two parameters, state and props:
state: is a reference to the component's state - mentioned above - at the time the change is being applied aka previous state.
props: current properties of the component.
You can think of like having the previous state and current properties of the component in two different arguments.
I hope this helps!
Yes i got , just like setting values to an object inside state, we provide values to props in the element tag which is jsx, so from my above code i figured out few things.
As per my understanding i believe that updater function for setState has two params, which basically sits in order as the first param would be previous state and the second is the current property of component.
Here my main component which renders on page with all other child components is .
so, props.increment is coming from Appl which extends Component from "react".
removing increment prop from my skeleton and body of skeleton i.e, from Singlestudent and Studentinfo comps
so finally :
this.setState((referencetoprevState, Props) => {
return {
counter: referencetoprevState.counter + Props.increment
};
});
would be:
this.setState((referencetoprevState, Props) => {
return {
counter: referencetoprevState.counter + 1
};
});

Why does my state not appear to be updating in my component?

I am new to Redux, though I have done a bit of work with React before.
I'm using a tutorial to test using Actions, Action Creators, and Reducers in my application, and so far I think I'm about 90% of the way there.
componentDidMount() {
this.props.levelUp();
this.props.levelUp();
this.props.levelUp();
this.props.levelUp();
this.props.levelUp();
console.log("final prop");
console.log(this.props);
}
const mapStateToProps = (state) => {
console.log("state general");
console.log(state.general);
return {
general: state.general,
};
};
Both of the console logs get triggered here, and they increment with each level up operation or decrement with every level down operation.
function mapDispatchToProps(dispatch) {
return bindActionCreators(generalActions, dispatch);
}
This is in my reducer file:
export default (state = 1, action) => {
console.log(state);
switch (action.type) {
case 'LEVEL_UP':
console.log(action.type);
return state + 1;
case 'LEVEL_DOWN':
return state - 1;
}
return state;
};
My console logs here seem to be capturing the right increment - the value in the reducer goes up one every time I call this.props.levelUp()
However when I do the final logging of the props in componentDidMount(), the value is 1.
Why is this? Am I not persistently saving the data? Is there some other reason why I'm not returning state the way I am envisioning?
componentDidMount will be fired once the component did mount. Afterwards your actions are fired hence why you should do your console.log() statements inside of something like componentDidUpdate() or static getDerivedStateFromProps().
More about lifecycles in react: https://reactjs.org/docs/state-and-lifecycle.html
Greetings

Can not get new props after action dispatching

I have a React component:
class Board extends React.Component {
// ...
compareLastPair() {
const {gameplay} = this.props;
console.log('after dispatching, before compare', gameplay.clickedTiles); //-> [1]
// i got old state as before dispatching an action, but expected refreshed props
// thus, condition below never executes
if(gameplay.board.length === gameplay.pairedTiles.length + gameplay.clickedTiles.length) {
this.compareTiles();
}
}
handleClick(id) {
const {gameplay, dispatch} = this.props;
// ...some code
else if(!gameplay.clickedTiles.includes(id) && gameplay.clickedTiles.length < 2) {
console.log('before dispatching', gameplay.clickedTiles); // -> [1]
dispatch(clickTile(id));
this.compareLastPair();
}
}
//...
}
My reducer dispatches sync action:
const gameplay = (state = {board: [], clickedTiles: [], pairedTiles: [], round: 0}, action) => {
switch(action.type) {
case 'CLICK_TILE':
return {...state, ...{clickedTiles: state.clickedTiles.concat(action.id)}}
}
}
My question is: why my compareLastPair function gets the same props as before dispatching in handleClick function, despite the fact that the state was updated by Redux(you can see it in Redux-logger at the image) and clickedTiles array should be concantenated by reducer.
Even if your dispatch action is synchronous (but we don't know... you didn't shared the code), props update in the React component follow the normal asynchronous lifecycle, while you are explicitly calling compareLastPair after the dispatch.
React/Redux do not work this way: new props will be received by your component after your call.
For your test, I suggest you to call compareLastPair inside the componentDidUpdate lifecycle method, which is called after prop changes.

In Redux, where does the state actually get stored?

I searched a bit about this question but found very vague answers. In redux, we know that the state is stored as an object. But where is this state stored actually? Is it somehow saved as a file which can be accessed by us later on? What I know is that it does not store it in a cookie format or in the browser's local storage.
The state in Redux is stored in memory, in the Redux store.
This means that, if you refresh the page, that state gets wiped out.
You can imagine that store looking something like this:
function createStore(reducer, initialState) {
let state = initialState // <-- state is just stored in a variable that lives in memory
function getState() {
return state
}
function dispatch(action) {
state = reducer(state, action) // <-- state gets updated using the returned value from the reducer
return action
}
return {
getState,
dispatch
}
}
The state in redux is just a variable that persists in memory because it is referenced (via closure) by all redux functions.
Here's a simplified example of what is going on:
function example() {
let variableAvailableViaClosure = 0
function incrementTheClosureVariable() {
variableAvailableViaClosure += 1
}
function getTheClosureVariable() {
return variableAvailableViaClosure
}
return {
incrementTheClosureVariable,
getTheClosureVariable
}
}
let data = example()
// at this point example is finished
// but the functions it returned
// still have access to the (internal) variable via closure
console.log(
data.getTheClosureVariable() // 0
)
data.incrementTheClosureVariable()
console.log(
data.getTheClosureVariable() // 1
)
Furthermore, the statement
In redux, we know that the state is stored as an object.
isn't correct. State in redux can be any valid javascript value, not just an object. It just usually makes the most sense for it to be an object (or a special object like an array) because that allows for a more flexible data structure (but you could make the state just be a number for example, if you wanted to).
Check out the actual Redux implementation for more details.
If you want the state to persist in a cookie or localStorage, you would enhance the store such that, on top of updating the state in memory, it will save to your desired storage as well (and load from that storage when the store is initialized)
States are stored in redux-store. Redux Store is a global store which can be accessed anywhere/any components.
Let consider an example of getting Index of data using third party API. The following snippet uses componentWillMount which will trigger a fetch call using redux action.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchDataFromUrl } from '../actions/index.js';
class Indexdata extends Component {
constructor(props){
super(props);
this.state = {
text: ''
}
}
componentWillMount(){
let thisVal = this;
thisVal.props.fetchIndexofData()
}
componentWillReceiveProps(nextProps){
this.setstate({
text: nextProps.indexData.text
})
}
render(){
return(
<div>
<Navbar />
<h2 className="prescription-index-title">Index of Data</h2>
</div>
)
}
}
function mapStateToProps(state){
return{
indexData: state.fetchedData
}
}
function mapDisptachToProps(dispatch){
return {
fetchIndexofData: () => dispatch(fetchDataFromUrl(access_token))
};
};
export default connect(mapStateToProps, mapDisptachToProps)(IndexData);
The above snippet will fetch index of data using a redux action. The below code is a redux action,
export function fetchDataFromUrl(){
return(dispatch) => {
const base_url = "https://api_serving_url.com"
fetch(base_url, {
method: 'GET'
})
.then(response => response.json())
.then(data => {
dispatch({
type: "INDEX_DATA",
data: data
})
})
}
}
Redux action will dispatch data to reducer, where state will be initialized in redux store. The following code snippet is redux-reducer
export function fetchedData(state = [], action) {
switch(action.type) {
case "INDEX_DATA":
return action.data;
default:
return state;
}
}
State stored in redux store will be mapped using function mapStateToProps, implemented in the above component. Now you can access the state using props in the respective component. Lifecyclehook componentWillReceiveProps will be able to fetch the state stored redux store.
You can access the State by means of using store.getState() in any component.The only drawback of using reducer state, is that it will reset the state when you refresh the component/application. Go through Reducer Store , for more information.

Categories