My Component not re-render when the store is updated - javascript

I've googled but nothing has helped.
I know when in the redux I return the same object, the react Component not re-render. whatever I'm not mutating the store, I'm using connect from 'react-redux' and over other components works right
Thanks :D
//I'm updating a attribute value from this object:
{name: 'some name', score: 0,}
//to
{name: 'some name', score: 1,}
//just change the score
//Component:
class ListTeam extends Component {
render() {
return (
<ListContainer onNewTeam={this.props.onNewTeam}>
{this.props.listTeam.map((item) =>
<Item
name={item.name}
score={item.score}
key={item.toString()}
/>
)}
</ListContainer>
)
}
}
const mapToProps = (store, props) => {
return {
listTeam: store.listTeam
}
}
export default connect(mapToProps)(ListTeam)
REDUCER:
case 'ADD_POINT':{
let newstate = Object.assign({},state); //new state from current state
let current = newstate.currentPlayer //get current player
let listTeam = newstate.listTeam; //getListTeam
listTeam[current].score++; //+1 to score
return {
...state,
listTeam
}
}

What you miss is that in connect provided by react-redux, it only does shallow comparing the current state with previous state. Since you only pass currentPlayer and listTeam from store to your component, and after the action ADD_POINT, the object listTeam is retained with all its keys unchanged, connect logic determined that no update should be made. Here is a small example in redux's github issue thread that's closed to your implementation.
One simple solution is to clone your listTeam in store everytime you update, so that it will be recognized as new object:
case 'ADD_POINT':{
let newstate = Object.assign({},state); //new state from current state
let current = newstate.currentPlayer //get current player
let listTeam = newstate.listTeam; //getListTeam
listTeam[current].score++; //+1 to score
return {
...state,
listTeam: JSON.parse(JSON.stringify(listTeam)
}
}
My simple Codesandbox for above solution: https://codesandbox.io/s/xjp77jpyro
You can also look into Immutable as it's recommended by Redux to handle immutable state.
Another solution is to customize the connect function to force it to recognize change in list: https://react-redux.js.org/api/connect#options-object. But I don't think you need to go that far.

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.

React Redux - Confusion on Using Functional Component with useDispatch vs Class Component and mapStateToProps

I am trying to have a user be able to click an item from a list of all possible items and have a modal open to display data about that item (including the current quantity they have) and buttons to increment/decrement that amount.
To my understanding since I am just showing data that is being passed in and then dispatching an action to update the store I should be using a functional component to display the data and useDispatch to call the store action.
Currently when I update the store I see the change in Redux debugging tools but the change is not reflected in the modal until I reopen it. While I have been looking for answers to this I see many similar questions but they all use Class Components and mapStateToProps (such as this post). I thought best practices was to use functional components unless needed. Am I wrong to think that if I am getting a value from the store in a functional component it should update on change?
Code Snippets
Dialog
export default function ItemDialog({
...
selectedItem,
}) {
const dispatch = useDispatch()
const inventory = useSelector(
state => state.user.inventory
)
let userItem = inventory.find(
userItem => userItem.name === selectedItem.name
)
const changeItemCount = (item, change) => {
item.change = change
dispatch({
type: "USER_INVENTORY_UPDATED",
payload: item
})
}
const showQuantity = userItem => {
return userItem.quantity > 0 ? `(${userItem.quantity})` : ""
}
...
render(
<p className="text-xl text-center font-semibold">
{selectedItem.name}
</p>
<p className="text-center font-light">
{showQuantity(userItem)}
</p>
...
<AddBoxIcon
onClick={() => changeItemCount(selectedItem, 1)}
/>
)
Store
const userReducer = (state = InitialUserState, action) => {
let inventoryCopy = { ...state.inventory }
switch (action.type) {
case "USER_INVENTORY_UPDATED":
let category = action.payload.category
let updatedItemIndex = inventoryCopy[category].findIndex(
item => item.name === action.payload.name.toUpperCase()
)
// If item is already there
if (updatedItemIndex >= 0) {
inventoryCopy[category][updatedItemIndex].quantity +=
action.payload.change
} else {
// If item needs to be added to inventory category
let newItem = {
name: action.payload.name,
quantity: action.payload.change
}
inventoryCopy[category].push(newItem)
}
return {
...state,
inventory: inventoryCopy
}
...
default:
return state
}
}
Check your spread operator when you return your updated state. You may need to deep clone the old state depending on how many nested objects it has.
The docs have more information on shallow cloning objects.
Deeply cloning your state object will help you get rid of:
let inventoryCopy = { ...state.inventory }

In React / Redux, how to call the same fetch twice in componentDidMount, setting 2 state variables with results

The title is wordy, however a short / simple example will go a long ways in explaining my question. I have the following start to a component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchGames } from '../../path-to-action';
class TeamsApp extends Component {
constructor(props) {
super(props);
this.state = {
oldGames: [],
newGames: []
};
}
componentDidMount() {
this.props.dispatch(fetchGames('1617'));
this.setState({ oldGames: this.props.teamGameData });
this.props.dispatch(fetchGames('1718'));
this.setState({ newGames: this.props.teamGameData });
}
...
...
}
function mapStateToProps(reduxState) {
return {
teamGameData: reduxState.GamesReducer.sportsData
};
}
export default connect(mapStateToProps)(TeamsApp);
I would like the action / reducer that corresponds with fetchGames() and gamesReducer to be called twice when the component mounts. This action / reducer grabs some sports data, and I am trying to grab data for two separate seasons (the '1617' season and the '1718' season). The fetchGames() is built correctly to handle the season parameter.
With the current setup, the states aren't being set, and my linter is throwing an error Do not use setState in componentDidMount.
Can I pass a callback to this.props.dispatch that takes the results of the fetchGames() (the teamGameData prop), and sets the oldGames / newGames states equal to this object?
Any help with this is appreciated!
Edit: if i simply remove the this.setState()'s, then my teamGameData prop simply gets overridden with the second this.props.dispatch() call...
Edit 2: I'm not 100% sure at all if having the 2 state variables (oldGames, newGames) is the best approach. I just need to call this.props.dispatch(fetchGames('seasonid')) twice when the component loads, and have the results as two separate objects that the rest of the component can use.
Edit 3: I have the following part of my action:
export const fetchSportsDataSuccess = (sportsData, season) => ({
type: FETCH_NBA_TEAM_GAME_SUCCESS,
payload: { sportsData, season }
});
and the following case in my reducer:
case FETCH_NBA_TEAM_GAME_SUCCESS:
console.log('payload', action.payload);
return {
...state,
loading: false,
sportsData: action.payload.sportsData
};
and the console.log() looks like this now:
payload
{ sportsData: Array(2624), season: "1718" }
but i am not sure how to use the season ID to create a key in the return with this season's data....
Edit 4: found solution to edit 3 - Use a variable as an object key in reducer - thanks all for help on this, should be able to take it from here!
Copying data from the redux store to one's component state is an anti-pattern
Instead, you should modify your redux store, for example using an object to store data, so you'll be able to store datas for multiples seasons :
sportsData: {
'1617': { ... },
'1718': { ... },
}
This way you'll be able to fetch both seasons in the same time :
componentDidMount() {
const seasons = ['1718', '1617'];
const promises = seasons.map(fetchGames);
Promise.all(promises).catch(…);
}
And connect them both :
// you can use props here too
const mapStateToProps = (reduxState, props) => ({
// hardcoded like you did
oldGames: reduxState.GamesReducer.sportsData['1617'],
// or using some props value, why not
newGames: reduxState.GamesReducer.sportsData[props.newSeason],
};
Or connect the store as usual and go for the keys:
const mapStateToProps = (reduxState, props) => ({
games: reduxState.GamesReducer.sportsData,
};
…
render() {
const oldGame = this.props.games[1718];
const newGame = this.props.games[1718];
…
}
Redux is you single source of truth, always find a way to put everything you need in Redux instead of copying data in components

How to setState entire object?

My app has a user control panel and when the page is loaded it fetch data from the server using Redux.
In construction the component create an initial state like:
const { profile } = this.props;
this.state = {
prop1: profile.prop1 || '',
prop2: profile.prop2 || '',
nested: {
nestedProp1: profile.nested.nestedProp1 || '',
}
...etc...
}
On componentWillMount I have this:
componentWillMount() {
const { user, getProfile } = this.props;
if (user.profile_id) {
getProfile(user.profile_id);
}
}
What I don't understand are 2 things:
Is the approach correct? I'm using state to handle form inputs.
How can I update the state when fetched? There are plenty of properties in this profile object and I was wondering to update all the states in a very simple way, and not one by one...
1.If you are using redux,I think there is no need to use state to manage date, instead you can use props(redux) to handle all the date in your project.
Then,if you want to update the date, you should create action to update the globally unique date that stored in redux.
2.About how to handle the input, when the user have input value, you can create an action, create a copy with the initial state then update state with your input action.
function updateInput(state = initialState, action) {
switch (action.type) {
case 'INPUT':
return Object.assign({}, state, {
profile_id: action.profile
})
return state;
}
}
You could use static getDerivedStateFromProps(props, state) -method to update your component state from props whenever your Redux store updates. It has two params props and state.
class App extends React.Component {
// https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
static getDerivedStateFromProps(props, state){
// Compare and update state only if props differs from state
if(JSON.stringify(state) !== JSON.stringify(props.profile)){
return { ...props.profile }
}
// If it doesn't differ do not update state
return null
}
// Do an state initialization
state = { ...this.props.profile }
// Prefer componentDidMount -for doing fetch and updating component state
componentDidMount(){
const { user, getProfile } = this.props;
if (user.profile_id) {
getProfile(user.profile_id);
}
}
render(){
return (
<div className="App">
{/** Render content */}
</div>
);
}
}
Rest spread operator, what is used to fill up state is ES6 syntax.
If you use Babel you might need to add rest spread operator -plugin to your .babelrc -config. https://babeljs.io/docs/en/babel-plugin-proposal-object-rest-spread

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