I' m using the useContext hook to manage the state, i have tigger the handleChange method from the other component's input slider field, this changes the state object and that state are used to filter the data
this is the context API hook component
handleChange is tigger by the other component input price slider
const handleChange = (event) => {
const target = event.target;
const name = target.name;
const value = target.value;
setState(
{
...state,
[name]: value,
},
**Part 1
filterBooks()
);
};
const filterBooks = () => {
let { books, maxPrice, minPrice, type, category, price } = state;
let tempBook = [...books];
price = parseInt(price);
// console.log(typeof price);
// console.log(tempBook, price);
if (type !== "all") {
tempBook = tempBook.filter((book) => book.type === type);
}
tempBook = tempBook.filter((book) => book.price < price);
**Part 2 **
*code works as expected till here*
setState({
...state,
sortedBook: tempBook,
});
// how to update state in this scenario;
// as per the docs we can't update state in nested function
};
return (
<ProductContext.Provider value={{ state, handleChange }}>
{props.children}
</ProductContext.Provider>
);
};
export default ProviderContext;```
Not sure why you're filtering in the setState callback, why not just update the filter in the initial setState?
Also, unless your state is deeply nested you shouldn't need to pass the full state to the setState, and if it is nested - I'd consider a redux implementation
I refactor the code and it worked
const handleChange = (event) => {
const target = event.target;
const name = target.name;
const value = target.value;
setFlag(false);
setState({
...state,
[name]: value,
sortedBook: [],
});
setFlag(true);
};
useEffect(() => {
if (!flag) {
// console.log("effect called without change - by default");
} else {
// console.log("effect called with change ");
const filterBooks = () => {
let { books, maxPrice, minPrice, type, category, price } = state;
let tempBook = [...books];
if (type !== "all") {
tempBook = tempBook.filter((book) => book.type === type);
}
tempBook = tempBook.filter((book) => book.price < parseInt(price));
// console.log("from tempbook", tempBook);
setState({
...state,
sortedBook: tempBook,
});
};
filterBooks();
}
// console.log(state);
}, [state.price, flag]);
Related
I am using useState for this object 'selected', i want to get the updated value in this function verifyActive
const [selected, setSelected] = useState({ a: '', b: '' })
and i have a function that is active since a button
const setActive = (value) => {
setSelected({ ...selected, b: value })
verifyActive()
}
const verifyActive=()=>{
console.log('selected', selected) // is not updated
}
...
console.log(selected) //here is value is updated
return (
<button
onchange={(value) => { setActive(value) }}
/>
The local const selected is never going to change, but what you can do is save the new state to a variable, and pass it in to verifyActive:
const setActive = (value) => {
const newState = { ...selected, b: value };
setSelected(newState);
verifyActive(newState);
}
const verifyActive = (value) => {
console.log('selected', value);
}
change your setActive to this way
const setActive = (value) => {
setSelected((prevSelected) => { ...prevSelected, b: value })
verifyActive()
}
I think this is easier to explain using a codesandbox link. This follows on from a previous question of mine, which may help provide more overall context. Currently, when interacting with the child elements (i.e. inputs), the state updates to {"values":{"0":{"Text1":"test"},"1":{"bool":true}}}. The issue is that if you interact with the other inputs within a Parent component, e.g. Text2 in the Parent component with id 0, it will overwrite the value already in the state, which makes it look like this {"values":{"0":{"Text2":"test"},"1":{"bool":true}}}. I want it to look like {"values":{"0":{"Text1":"test", "Text2":"test"},"1":{"bool":true}}}.
This is my try with your problem. I would like to have childIndex instead of number like you. It would be easier to work with other components later.
Here is my codesandbox
import { useEffect, useState } from "react"
import Parent from "./Parent"
const id1 = 0
const id2 = 1
interface Boo {
childIndex: number
value: {
[name: string]: string | boolean
}
}
const GrandParent: React.FC = () => {
const [values, setValues] = useState<Boo[]>([])
const valuesChange = (e: React.ChangeEvent<HTMLInputElement>, id: number) => {
console.log("change event")
const name = e.target.name
let value: any
if (name === "bool") {
value = e.target.checked
} else {
value = e.target.value
}
setValues((prev) => {
// Update new value to values state if input already there
let updateBoo = prev.find((boo) => boo.childIndex === id)
if (updateBoo) {
// Update Values
const valKeys = Object.keys(updateBoo.value)
const valIndex = valKeys.find((val) => val === name)
if (valIndex) {
updateBoo.value[valIndex] = value
} else {
updateBoo.value = { ...updateBoo.value, [name]: value }
}
} else {
// Create new if not added
updateBoo = {
childIndex: id,
value: { [name]: value }
}
}
return [...prev.filter((boo) => boo.childIndex !== id), updateBoo]
})
}
useEffect(() => {
console.log("Render", { values })
})
return (
<>
<div>{JSON.stringify({ values }, undefined, 4)}</div>
<br />
<br />
<Parent valueChange={(e) => valuesChange(e, id1)} key={id1} />
<Parent valueChange={(e) => valuesChange(e, id2)} key={id2} />
</>
)
}
export default GrandParent
The trick is you should return the previous state of the property too:
setValues((prev) => {
return {
...prev,
[id]: {
...(prev && (prev[id] as {})), // <= return the previous property state
[name]: value
}
}
})
I'm not very good at typescript but I tried my best to solve some types' errors
you can see an example below
I have an array of objects in a useState hook with which i rendered input elements.
The state doesn't get updated when the input element changes.
Here Is My Code.
ProceduralResponseAnswerCreate Component
const ProceduralResponseAnswerCreate = () => {
const [proceduralAnswers, setProceduralAnswers] = useState([{ "value": "One" }, { "value": "two" }])
return <>
<ol>
{proceduralAnswers.map((answer, answer_index) => <input key={answer_index} style={inputStyle} onChange={(event) => updateAnswerValue({ event, setProceduralAnswers, answer_index })} value={answer.value} />)}
</ol>
</>
}
export default ProceduralResponseAnswerCreate
updateAnswerValue function
export const updateAnswerValue = ({ event, setProceduralAnswers, answer_index }) => {
setProceduralAnswers(state => {
var newState = state;
var currentAnswer = newState.filter((state_1, index) => index === answer_index)[0]
currentAnswer.value = event.target.value
return newState
})
}
I think it doesn't work because the next state has the same Reference as the previous state, you only change the value of one element. The useState hook will trigger a rerender, if the reference changes. So your Callback must look like this.
setProceduralAnswers(state => {
let newState = state;
let currentAnswer = newState.filter((state_1, index) => index === answer_index)[0]
currentAnswer.value = event.target.value
return [...newState]
})
try this
export const updateAnswerValue = ({ event, setProceduralAnswers, answer_index }) => {
setProceduralAnswers(state => {
var newState = state;
newState[answer_index].value = event.target.value
return newState
})
}
You are returning the newState instead of the currentAnswer.
First off, if you're only trying to find one item use [].find not filter.
var newState = state; does not give you a fresh copy, change newState, and you'll be changing state too, you need to do var newState = [...state]; (warning: this is a shallow copy only).
It looks like you think currentAnswer shares a reference with newState in some way, which it doesn't. Whenever you run map, find, filter or reduce you're getting a fresh copy of the array so there will not be a link between the 2.
A good bet here is to just map the state
export const updateAnswerValue = ({
event,
setProceduralAnswers,
answer_index
}) => {
setProceduralAnswers((state) => state.map((state_1, index) => {
if (index === answer_index) {
return {
...state_1,
value: event.target.value
}
}
return state_1;
}));
};
I have a warning on this nested setState pointing a "bad setState call" because I am calling a setState inside a setState, so I understand I must avoid that and take out the children setState from there, the problem is that the children is using the parent prevState for a conditional so I don't know how to solve this.
This is my code:
const setStateImageAndIndex = (state, image) => {
setSketchState(prevState => {
if (state.index !== undefined && prevState.index !== state.index) {
setCurrentSketchIndex(state.index);
}
const new_state = {
...prevState,
...state,
image: image
};
return new_state;
});
};
The warning is pointing the setCurrentSketchIndex(state.index); explicitly, which is the function I want to take out from setSketchState. Thank you!
prevState in setSketchState infact is the actual state of sketchState (I mean the state setted by setSketchState). So you could write something like:
const setStateImageAndIndex = (state, image) => {
if (state.index !== undefined && sketchState.index !== state.index) {
setCurrentSketchIndex(state.index);
}
setSketchState(prevState => {
const new_state = {
...prevState,
...state,
image: image
};
return new_state;
});
};
you can hold the previous state of the parent component in a useRef and derivate the state in the child component passing that value down as a prop, for example:
function Parent() {
let [state, setState] = React.useState({ version: 1 });
let prevState = usePrevious(state.version);
return (
<div>
<p>Parent value: {state.version}</p>
<Child value={prevState} />
<button
onClick={() =>
setState({
version: state.version + 1
})
}
>
Update version
</button>
</div>
);
}
function Child(props) {
return (
<div>
<p>Child value: {props.value || "No value yet"}</p>
</div>
);
}
as we can see above, the parent component will update the state.version in the button click and usePrevious will hold the previous value:
function usePrevious(value) {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
});
return ref.current;
}
Working example in: https://codesandbox.io/s/use-previous-hook-0iemv
According to the comments above from #GabrielePetrioli and #GiovanniEsposito, this solution seemed to be safer.
const setStateImageAndIndex = (state, image) => {
let updateCurrentSketchIndex = false;
setSketchState(prevState => {
if (state.index !== undefined && prevState.index !== state.index) {
updateCurrentSketchIndex = true;
}
const new_state = {
...prevState,
...state,
image: image
};
return new_state;
});
if (updateCurrentSketchIndex) {
setCurrentSketchIndex(state.index);
}
};
With class based components you could use the computed properties to in order to use one single handler for several inputs with the help of an id attribute like this:
function handleChange(evt) {
const value = evt.target.value;
setState({
...state,
[evt.target.name]: value
});
}
This is thebest I caome up with but im sure there must be a shorter way:
const handleChange = (e) => {
if (e.target.id === 'name') setName(e.target.value)
if (e.target.id === 'genre') setGenre(e.target.value)
if (e.target.id === 'description') setDescription(e.target.value)
}
Any ideas?
Thanks.
You could have create an object in the useState and update just like the first example.
// Declare the state with a object default value
const [state, setState] = useState({})
const handleChange = (e) => {
setState(prev => ({
...prev,
[e.target.id]: e.target.value
}))
}
If you can't change the useState and have a very strong reason to have these 3 separeted useStates, you could create an object that returns the 3 functions based on the e.target.id
const updater = {
name: setName,
genre: setGenre,
description: setDescription,
}
And when updating you do
const handleChange = (e) => {
const updateValue = updater[e.target.id]
if (updateValue) {
updateValue(e.target.value)
}
else {
// No function to update found
// Do something else
}
}