useState is using the wrong value with a custom hook - javascript

In my React component, I have the following code:
const [additionalGuestAmounts] = useState<RatePlan['additionalGuestAmounts']>(
useAdditionalGuestAmounts(roomType, occupantsForBaseRate)
)
And here is my custom hook:
const useAdditionalGuestAmounts = (
roomType: RoomType,
occupantsForBaseRate: number
): RatePlan['additionalGuestAmounts'] => {
console.log('executing useAdditionalGuestAmounts hook')
if (!roomType) {
return []
}
console.log('returning array')
return [
{
foo: 'bar'
},
]
}
When I modify roomType from null to a value, it logs 'executing useAdditionalGuestAmounts hook' and then 'returning array', however the value of additionalGuestAmounts is not updated; it stays as an empty array. Why is the value not being updated if the custom hook is being executed?

The value that is passed in the useState hook is only the initial value of the state as explained on the documentation of the useState hook: https://reactjs.org/docs/hooks-state.html, to make sure that the state is updated whenever the useAdditionalGuestAmounts hook is called we will have to call setAdditionalGuestAmounts with the new value. This would then look like:
// Initial state
const [additionalGuestAmounts, setAdditionalGuestAmounts] = useState<RatePlan['additionalGuestAmounts']>([])
const useAdditionalGuestAmounts = (
roomType: RoomType,
occupantsForBaseRate: number
) => {
if (!roomType) {
return []
}
// Update the state, this causes the page to re-render
setAdditionalGuestAmounts( [
{
foo: 'bar'
},
])
}
If you need the useAdditionalGuestAmounts to be called on page load you can use the useEffect hook for this (https://reactjs.org/docs/hooks-effect.html)

The value of the additionalGuestAmounts state variable is not being updated when you modify roomTypeId because the useAdditionalGuestAmounts hook is not being called again. This is because the useAdditionalGuestAmounts hook is only called when the component is first rendered, and is not called again.
As stated previously by rowan, you can a useEffect hook inside the useAdditionalGuestAmounts hook.
const useAdditionalGuestAmounts = (
roomType: RoomType,
occupantsForBaseRate: number
): RatePlan['additionalGuestAmounts'] => {
console.log('executing useAdditionalGuestAmounts hook')
const [additionalGuestAmounts, setAdditionalGuestAmounts] = useState<
RatePlan['additionalGuestAmounts']
>([]);
useEffect(() => {
if (!roomType) {
return;
}
console.log('returning array');
setAdditionalGuestAmounts([
{
foo: 'bar',
},
{
foo: 'bar',
},
{
foo: 'bar',
},
{
foo: 'bar',
},
{
foo: 'bar',
},
]);
}, [roomTypeId, occupantsForBaseRate]);
return additionalGuestAmounts;
};
You also need to add the occupantsForBaseRate argument to the dependencies array, because the useAdditionalGuestAmounts hook depends on it.
Hope this helps.

Related

React : parent props changes, but children (with map) update one step behind

I pass a prop data which contain a field worktypeData witch has a field that changes.
When I console log I can see data updated, but the console log in the map is one step behind late.
I tried with a useEffect that set a new state with **data **in the dependencies, but same result.
Tried this too, but same result.
// this is the type of the field in OfficeOccupancyCardData that I want to update
interface WorkTypeData {
selected: WorkType;
onClick: (worktype: WorkType) => void;
buttonLink: string;
}
interface DashboardNextDaysProps {
data: OfficeOccupancyCardData[];
}
const DashboardNextDays: React.FunctionComponent<DashboardNextDaysProps> = ({ data }) => {
// console log here will show data with the new value
return (
<HorizontalGridSlide className="DashboardNextDays" colSize={286} gap={16} pageSlide>
{data.length > 0 &&
data.map((day, i) => {
// console log here will show data with the value not updated
return <OfficeOccupancyCard key={generateKey(i)} data={day} />;
})}
</HorizontalGridSlide>
);
};
EDIT: I found a solution, if someone can explain to me why this works.
In the parent of DashboardNextDays I have a useEffect to set the new data :
useEffect(() => {
setNextDaysData((prev) => {
const newNextDaysData = prev;
if (newNextDaysData && nextDayWorktypeSelected)
newNextDaysData[nextDayWorktypeSelected.dayIndex] = {
...newNextDaysData?.[nextDayWorktypeSelected.dayIndex],
worktypeData: {
...newNextDaysData?.[nextDayWorktypeSelected.dayIndex].worktypeData,
selected: nextDayWorktypeSelected.worktype,
},
};
return newNextDaysData ? newNextDaysData : [];
});
}, [nextDayWorktypeSelected]);
And I just changed
return newNextDaysData ? newNextDaysData : [];
to
return newNextDaysData ? [...newNextDaysData] : [];
To see the state changes immediately, you need to use useEffect and add newData in the dependency change.
Since setState is asynchronous you can not check state updates in console.log synchronously.
useEffect is the hook that manages side-effects in functional components:
useEffect(callback, dependencies). The callback argument is a function to put the side-effect logic in place. Dependencies is a list of your side effect's dependencies, whether they are props or state values.
import { useEffect } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState();
useEffect(() => {
// Side-effect uses `prop` and `state`
}, [prop, state]);
return <div>....</div>;
}

Vue 3 Pina trying to test a pina store that has a dependency on another store

I basically have a store that depends on another store and I don't see a way to only mock the dependent store.
example pseudo code vue 3 ish:
// the store I want to mock
export const useStore1 = defineStore({
id: 'store1',
state: (): State => ({
someName:'blarg', // I know this is static but lets pretend it can change.
}),
// getter I want to mock
getters: {
name: (state) => state.someName,
}
}
// store I want to test
export const useStoreTwo = defineStore({
id: 'store2',
state: (): State => ({
someValue:'bar'
}),
getters: {
value: (state) => {
const store1 = useStore1() // dependency here
return `${store1.name} state.someValue`
},
}
}
test
it('should return something' () => {
//****
someplace I would mock useStateOne and have it return
a stub store with the getter name that returns 'foo'
***//
const store2 = useStoreTwo();
expect(store2.value).toBe('foo bar');
})
It is helpful to you.
https://pinia.vuejs.org/cookbook/testing.html#mocking-getters
By default, any getter will be computed like regular usage but you can
manually force a value by setting the getter to anything you want
import { createTestingPinia } from '#pinia/testing';
const pinia = createTestingPinia()
it('should return something' () => {
const store1 = useStore1(pinia);
store1.name = "foobar";
const store2 = useStoreTwo();
expect(store2.value).toBe('foobar');
})

How to use lifeCyle and setState in react?

this is app.js
class App extends Component {
constructor() {
super();
this.state = {
todo_lists: [
{ id: 1, name: "Hoc React" },
{ id: 2, name: "Hoc HTML" },
{ id: 3, name: "Hoc Jquery" },
{ id: 4, name: "Hoc CSS" }
],
showList : []
};
}
componentDidMount(){
let {showList, todo_lists} = this.state
this.setState({
showList : [...todo_lists]
})
console.log(showList)
}
}
when console.log(showList) on browser it return empty array like this [], clearly I assigned showList : [...todo_lists] in setState. help me
From Reactjs.org
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
https://reactjs.org/docs/react-component.html#setstate
So what you wrote will NOT happen immediately, and your console.log will NOT get the right values immediately afterwards.
Issue :
// this.setState is async method
this.setState({
showList : [...todo_lists]
});
console.log(showList) // <--- this will not give you updated value right after
you can use this.setState callback method for that, it is being called right after the state is set
this.setState({
// showList : [...todo_lists]
showList : this.state.todo_lists.map(todo => ({ ...todo}))
},() => {
console.log(this.state.showList) //<------ Check here
})
Suggestion :
constructor() {
super();
this.todos = [
{ id: 1, name: "Hoc React" },
{ id: 2, name: "Hoc HTML" },
{ id: 3, name: "Hoc Jquery" },
{ id: 4, name: "Hoc CSS" }
]
this.state = {
todo_lists: this.todos.map(todo => ({ ...todo})),
showList : this.todos.map(todo => ({...todo}))
};
}
The problem here is that React's setState is an async method, so it may delay the update in favor of perceived improvement in performance.
What you could do, is something like this (if you don't want to use the callback provided by the setState):
componentDidMount() {
const {showList, todo_lists} = this.state,
nextShowList = [...todo_lists];
this.setState({
showList: nextShowList
});
console.log(nextShowList); // executes immediatelly
}
If you want to use the callback provided by the setState method, simply use it like this:
componentDidMount() {
const {showList, todo_lists} = this.state;
this.setState({
showList: [...todo_lists]
}, () => console.log(this.state.showList)); // MAY NOT execute immediately
}
Also, as an additional tip (if I may), use the first argument as a function, or you may have problems setting your todo_list!
componentDidMount() {
this.setState(prevState => {
return {
showList: [...prevState.todo_lists]
};
}, () => console.log(this.state.showList));
}
Again, for the same reason: Since setState is async, you can't guarantee that the this.state outside of the setState is up-to-date!

useSelector not updating when store has changed in Reducer. ReactJS Redux

I am changing the state in reducer. On debug I checked that the state was really changed. But the component is not updating.
Component:
function Cliente(props) {
const dispatch = useDispatch()
const cliente = useSelector(({ erpCliente }) => erpCliente.cliente)
const { form, handleChange, setForm } = useForm(null)
...
function searchCepChangeFields() {
//This call the action and change the store on reducer
dispatch(Actions.searchCep(form.Cep))
.then(() => {
// This function is only taking values ​​from the old state.
// The useSelector hook is not updating with store
setForm(form => _.setIn({...form}, 'Endereco', cliente.data.Endereco))
setForm(form => _.setIn({...form}, 'Uf', cliente.data.Uf))
setForm(form => _.setIn({...form}, 'Cidade', cliente.data.Cidade))
setForm(form => _.setIn({...form}, 'Bairro', cliente.data.Bairro))
})
}
Reducer:
case Actions.SEARCH_CEP:
{
return {
...state,
data: {
...state.data,
Endereco: action.payload.logradouro,
Bairro: action.payload.bairro,
UF: action.payload.uf,
Cidade: action.payload.cidade
}
};
}
NOTE: you better start using redux-toolkit to prevent references
in you code its a better and almost a must way for using redux
the problem your facing is very common when handling with objects,
the props do not change because you're changing an object property but the object itself does not change from the react side.
even when you're giving it a whole new object
react doesn't see the property object change because the reference stays the same.
you need to create a new reference like this:
Object.assign(state.data,data);
return {
...state,
data: {
...state.data,
Endereco: action.payload.logradouro,
Bairro: action.payload.bairro,
UF: action.payload.uf,
Cidade: action.payload.cidade
}
}
to add more you can learn about the Immer library that solves this
problem.
It's not necessary to
Object.assign(state.data, data);
always when changing data of arrays or objects
return(
object: {...state.object, a: 1, b: 2},
array: [...state.array, 1, 2, 3]
)
this 3 dots (...) ensure that you create a new object. On redux you have to always create a new object, not just update the state. This way redux won't verify that your data has changed.
When having nesting objects or arrays, is the same thing
Just have attention to:
initialState = {
object: {
...object,
anotherObject:{
...anotherObject,
a: 1,
b: 2
}
}
}
Somehow, the Object.assgin is not recognize
Update with ES6 syntax.
updatedConnectors = state.connectors
This will create a reference to the current state. In ES6, that introduce the ... to make new reference.
updatedConnectors = { ...state.connectors }
.....
return {
...state,
connectors: updatedConnectors
};
use this to extract and copy new reference. That will trigger state change too
Update Sep/27/20
I've wrote some utils function to handle this, Let try this
//Utils
export const getStateSection = ({ state, sectionName }) => {
const updatedState = { ...state }
const updatedSection = updatedState[sectionName]
return updatedSection
}
export const mergeUpdatedSection = ({ state, sectionName, updatedValue }) => {
const updatedState = { ...state }
updatedState[sectionName] = updatedValue
return updatedState
}
Then In any reducer, It should use like this:
//reducer
const initState = {
scheduleDetail: null,
timeSlots: null,
planDetail: {
timeSlots: [],
modifedTimeSlots: [],
id: 0
}
}
.....
const handlePickTimeSlot = (state, action) => {
let planDetail = getStateSection({ state, sectionName: 'planDetail' })
// do stuff update section
return mergeUpdatedSection({ state, sectionName: 'planDetail', updatedValue: planDetail })
}
Since the edit queue for elab BA is full.
The accepted answer here is what he meant by data being there
case MYCASE:
let newDataObject = Object.assign(state.data, {...action.payload});
// or
// let newDataObject = Object.assign(state.data, {key: 'value', 'key2': 'value2' ...otherPropertiesObject);
return {
...state,
...newDataObject
}
There is an interesting edge case that can happen when modifying the file where you create your Store.
If the file where you have your redux store Provider component (usually App.tsx) does not get reloaded by React's hot module reloader (HMR) but the redux store file gets modified and therefore reloaded by HMR, a new store is created and the store Provider in your App.tsx can actually end up passing an old instance of your redux store to useSelector.
I have left the following comment in my setup-store.ts file:
/**
* Note! When making changes to this file in development, perform a hard refresh. This is because
* when changes to this file are made, the react hot module reloading will re-run this file, as
* expected. But, this causes the store object to be re-initialized. HMR only reloads files that
* have changed, so the Store Provider in App.tsx *will not* be reloaded. That means useSelector
* values will be querying THE WRONG STORE.
*/
It is not a problem, you should understand how the React is working. It is expected behavior
In your case you are invoking
dispatch(Actions.searchCep(form.Cep))
.then(() => {...some logic...}
But all of this work in ONE render, and changed store you will get only in the NEXT render. And in then you are getting props from the first render, not from the next with updated values. Look for example below:
import React from 'react';
import { useSelector, useDispatch } from "react-redux";
import { selectCount, incrementAsync } from "./redux";
import "./styles.css";
export default function App() {
const value = useSelector(selectCount);
const dispatch = useDispatch();
const incrementThen = () => {
console.log("value before dispatch", value);
dispatch(incrementAsync(1)).then(() =>
console.log("value inside then", value)
);
};
console.log("render: value", value);
return (
<div className="App">
<p>{value}</p>
<button onClick={incrementThen}>incrementThen</button>
</div>
);
}
And output
value before dispatch 9
render: value 10
value inside then 9

Why does it matter if I am mutating state in React useState hook?

I read everywhere, that mutating state in React can cause problem, for example the component won't re-render on state update, but the following code is working perfectly fine. The component re-renders every time there is a the state updates.
import React, { useState, useEffect } from "react";
const Mutator = () => {
const [checks, setChecks] = useState({ 66: false });
const rows = [{ id: 23 }, { id: 33 }, { id: 44 }, { id: 55 }];
useEffect(() => {
console.log("check updates", checks);
}, [checks]);
const mutateMyCheck = () => {
rows.forEach((row, i) => {
checks[row.id] = checks[row.id] ? checks[row.id] + i : 1;
});
setChecks({ ...checks });
};
return (
<div>
<button onClick={mutateMyCheck}>Check</button>
<div>{JSON.stringify(checks)}</div>
</div>
);
};
What is the point of not mutating the state then ? Or when does it really have an effect?
You are mutating state directly but you're also returning a new object via object spreading:
setChecks({ ...checks });
Since this creates a new object the component re-renders.
mutateMyCheck(); mutateMyCheck();
That won't increase the checks twice.
In this case you should use a reducer.

Categories