Why my useState is not updating the value? - javascript

I have an useState that gets the value from DB:
const [checklist, setChecklist] = useState(undefined);
And I have this condition, that when happens, I would like to change the useState:
let newItems = [];
let newChecklist = {};
if (project?.analysisData?.actionPlan == "no") {
newItems = checklist?.items?.filter(
(item) => item.conformityStatus !== "initial_status"
);
newChecklist = { ...checklist, items: newItems };
}
Notice that the items were filtered.
And I'm trying to change the value from my useState with useEffect:
useEffect(() => {
setChecklist(newChecklist);
}, []);
But for some reason it's not changing.. Anyone can help me?
Thank you so much!

I created a new useState:
const [changeChecklist, setChangeChecklist] = useState(true);
called a setTimeout, to force some "change" after the component Mount:
setTimeout(() => {
setChangeChecklist(false);
}, 550);
and called the useEffect with the change of my new useState:
let newItems = [];
useEffect(() => {
if (project?.analysisData?.actionPlan == "no") {
newItems = checklist?.items?.filter(
(item) => item.conformityStatus !== "initial_status"
);
setChecklist({ ...checklist, items: newItems });
}
}, [changeChecklist]);
Maybe its not the best solution, but it was the one that I figured out to solve my problem :)

Related

React pushing to array duplicates it instead of adding

Can anybody help me with this issue? I have a state called "filteredPokemon" which fetches a list of pokemon based on some things, then I pass it to this function called PokemonDisplayArea where I proceed to display the list of pokemon. However, when I change offset I expect the behavior to ADD onto the previous state of "Cards" which are the element rendering the HTML, however instead, it grabs the newest filteredpokemon and appends it twice. Any help would be appreciated!
Here is a video
https://clipchamp.com/watch/EcMJbOMjOaL
The code:
function PokemonDisplayArea({pokemons}) {
const [filteredPokemon, setFilteredPokemon] = useState([]);
const [offset, setOffset] = useState(20);
const [cards, setCards] = useState([]);
useEffect(() => {
let cardsSkele = [];
if (filteredPokemon.length > 0) {
for (let i = 0; i < filteredPokemon.length; i++) {
if (undefined !== filteredPokemon[i].name) {
cardsSkele.push(<PokemonCard key={filteredPokemon[i].id} name={filteredPokemon[i].name}></PokemonCard>);
cardsSkele.sort((a, b) => a.key - b.key)
}
}
setCards(prevArray => [...prevArray, ...cardsSkele]);
}
}, [filteredPokemon])
// SEARCH POKEMON RESULTS
// FILTER POKEMON RESULTS
return (
<div className="pokemon__display-area">
<GetFilteredPokemon pokemons={pokemons} amount={20} offset={offset} filteredPokemon={filteredPokemon} setFilteredPokemon={setFilteredPokemon}></GetFilteredPokemon>
{cards}
</div>
)
}
Any help would be appreciated, I am a beginner, thanks!
I tried passing different states into useEffect, and tried console logging the data but the data seems to change fine, and the list just duplicates.
For starters, I would just store data in the state rather than components itself as it easy to reason about and transform.
Iterate over the cards and update the data for existing items or add the new item. Something along these lines.
function getUpdatedCardsData(prevCards, filteredPokemon) {
// get list of all the ids for the filtered pokemons
const filteredPokemonIds = filteredPokemon.map(fp => fp.id);
// This will combine the data for existing cards that are part of
// filteredPokemon and maintain a map of ids that are new and need to be added to cards
const updatedCards = prevCards.reduce((memo, card) => {
const cardId = card.id;
const isNewCard = !filteredPokemonIds.includes(cardId);
// add it to the map if new
if(isNewCard) {
memo.newCardIds.push(cardId);
} else {
// find the card that is in filtered pokemon
const found = filteredPokemon.find(fp => fp.id === cardId);
if(found) {
const updatedCard = {
...card,
...found
};
memo.updatedCards.push(updatedCard);
}
}
return memo;
}, {
updatedCards: [],
newCardIds: []
});
}
function getNewCards(newCardIds, filteredPokemon) {
// List of the new cards
const newCards = newCardIds.reduce((memo, newCardId) => {
const found = filteredPokemon.find(fp => fp.id === newCardId);
if(found) {
memo.push(found);
}
return memo;
}, []);
}
function PokemonDisplayArea({pokemons}) {
const [filteredPokemon, setFilteredPokemon] = useState([]);
const [offset, setOffset] = useState(20);
const [cards, setCards] = useState([]);
useEffect(() => {
if(filteredPokemon) {
setCards(prevCards => {
const {
updatedCards,
newCardIds
} = getUpdatedCardsData(prevCards, filteredPokemon);
const newCards = getNewCards(newCardIds, filteredPokemon);
// add updated and new and sort them
const sortedCards = [...updatedCards, ...newCards].sort((a, b) => a.id - b.id);
return sortedCards;
});
}
}, [filteredPokemon])
// SEARCH POKEMON RESULTS
// FILTER POKEMON RESULTS
return (
<div className="pokemon__display-area">
<GetFilteredPokemon
pokemons={pokemons}
amount={20}
offset={offset}
filteredPokemon={filteredPokemon}
setFilteredPokemon={setFilteredPokemon}
/>
{cards.map((card, i) => {
const pokemon = card[i];
return (
<PokemonCard
key={pokemon.id}
name={pokemon.name}
/>
})}
</div>
)
}

React with TypeScript - LocalStorage won't work

I am working with persistence on a todo application written with React and TypeScript.
I am using localStorage to get the persistence I want.
Let me show you some code.
const [todos, setTodos] = useState<todoModel[]>([]);
useEffect(() => {
localStorage.setItem("todoItem", JSON.stringify(todos));
}, [todos])
const storesTodos = () => {
const storedValues = localStorage.getItem("todoItem");
if(!storedValues) { return todos; }
return JSON.parse(storedValues);
}
useEffect(() => { getToDoList(); storesTodos();
console.log("default") }, [])
useEffect(() => {
if (!props.reload) return;
console.log(props.reload)
getToDoList();
storesTodos();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.reload])
I am adding the StoresTodo() function into my useEffects, I tried both of them.
But it won't work. I get the data into my localStorage, but when reloading the page, it gets back to default values.
What am I missing here?
It seems like you are getting the same initial value because you never update the todos, judging based on shared code.
You can do this
...
const storesTodos = () => {
const storedValues = localStorage.getItem("todoItem");
if(!storedValues) {
setTodos(todos)
return todos;
}
return JSON.parse(storedValues);
}
Or you can use a local storage state hook from here
function useLocalStorageState({
key,
initialValue,
serialize = v => v,
deserialize = v => v,
}) {
const [state, setState] = React.useState(
() => deserialize(window.localStorage.getItem(key)) || initialValue,
)
const serializedState = serialize(state)
React.useEffect(() => {
window.localStorage.setItem(key, serializedState)
}, [key, serializedState])
return [state, setState]
}
When you refresh the page, even before the useEffect sets in
const [todos, setTodos] = useState<todoModel[]>([]);
already will set your initial todos array to an empty array.
Perhaps you'll need to perform the check on localStorage on it like so
const [todos, setTodos] = useState<todoModel[]>(() => {
const storedValues = localStorage.getItem("todoItem");
return storedValues ? JSON.parse(storedValues) : [];
});

React useState hook affecting default variable used in state regardless spread operators, Object.assign and etc

I am experienced js/React developer but came across case that I can't solve and I don't have idea how to fix it.
I have one context provider with many different state, but one state looks like following:
const defaultParams = {
ordering: 'price_asc',
page: 1,
perPage: 15,
attrs: {},
}
const InnerPageContext = createContext()
export const InnerPageContextProvider = ({ children }) => {
const [params, setParams] = useState({ ...defaultParams })
const clearParams = () => {
setParams({...defaultParams})
}
console.log(defaultParams)
return (
<InnerPageContext.Provider
value={{
params: params,
setParam: setParam,
clearParams:clearParams
}}
>
{children}
</InnerPageContext.Provider>
)
}
I have one button on page, which calls clearParams function and it should reset params to default value.
But it does not works
Even when i console.log(defaultParams) on every provider rerendering, it seems that defaultParams variable is also changing when state changes
I don't think it's normal because I have used {...defaultParams} and it should create new variable and then pass it to useState hook.
I have tried:
const [params, setParams] = useState(Object.assign({}, defaultParams))
const clearParams = () => {
setParams(Object.assign({}, defaultParams))
}
const [params, setParams] = useState(defaultParams)
const clearParams = () => {
setParams(defaultParams)
}
const [params, setParams] = useState(defaultParams)
const clearParams = () => {
setParams({
ordering: 'price_asc',
page: 1,
perPage: 15,
attrs: {},
})
}
None of above method works but 3-rd where I hard-coded same object as defaultParams.
The idea is to save dafult params somewhere and when user clears params restore to it.
Do you guys have some idea hot to make that?
Edit:
This is how I update my params:
const setParam = (key, value, type = null) => {
setParams(old => {
if (type) {
old[type][key] = value
} else old[key] = value
console.log('Params', old)
return { ...old }
})
}
please show how you update the "params".
if there is something like this in the code "params.attrs.test = true" then defaultParams will be changed
if old[type] is not a simple type, it stores a reference to the same object in defaultParams. defaultParams.attrs === params.attrs. Since during initialization you destructuring an object but not its nested objects.
the problem is here: old[type][key] = value
solution:
const setParam = (key, value, type = null) => {
setParams(old => {
if (type) {
old[type] = {
...old[type],
key: value,
}
} else old[key] = value
return { ...old }
})
}

How to handle an unchanging array in useEffect dependency array?

I have a component like this. What I want is for the useEffect function to run anytime myBoolean changes.
I could accomplish this by setting the dependency array to [myBoolean]. But then I get a warning that I'm violating the exhaustive-deps rule, because I reference myArray inside the function. I don't want to violate that rule, so I set the dependency array to [myBoolean, myArray].
But then I get an infinite loop. What's happening is the useEffect is triggered every time myArray changes, which is every time, because it turns out myArray comes from redux and is regenerated on every re-render. And even if the elements of the array are the same as they were before, React compares the array to its previous version using ===, and it's not the same object, so it's not equal.
So what's the right way to do this? How can I run my code only when myBoolean changes, without violating the exhaustive-deps rule?
I have seen this, but I'm still not sure what the solution in this situation is.
const MyComponent = ({ myBoolean, myArray }) => {
const [myString, setMyString] = useState('');
useEffect(() => {
if(myBoolean) {
setMyString(myArray[0]);
}
}, [myBoolean, myArray]
}
Solution 1
If you always need the 1st item, extract it from the array, and use it as the dependency:
const MyComponent = ({ myBoolean, myArray }) => {
const [myString, setMyString] = useState('');
const item = myArray[0];
useEffect(() => {
if(myBoolean) {
setMyString(item);
}
}, [myBoolean, item]);
}
Solution 2
If you don't want to react to myArray changes, set it as a ref with useRef():
const MyComponent = ({ myBoolean, myArray }) => {
const [myString, setMyString] = useState('');
const arr = useRef(myArray);
useEffect(() => { arr.current = myArray; }, [myArray]);
useEffect(() => {
if(myBoolean) {
setMyString(arr.current);
}
}, [myBoolean]);
}
Note: redux shouldn't generate a new array, every time the state is updated, unless the array or it's items actually change. If a selector generates the array, read about memoized selectors (reselect is a good library for that).
I have an idea about to save previous props. And then we will implement function compare previous props later. Compared value will be used to decide to handle function change in useEffect with no dependency.
It will take up more computation and memory. Just an idea.
Here is my example:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function arrayEquals(a, b) {
return Array.isArray(a) &&
Array.isArray(b) &&
a.length === b.length &&
a.every((val, index) => val === b[index]);
}
const MyComponent = ({ myBoolean, myArray }) => {
const [myString, setMyString] = useState('');
const previousArray = usePrevious(myArray);
const previousBoolean = usePrevious(myBoolean);
const handleEffect = () => {
console.log('child-useEffect-call-with custom compare');
if(myBoolean) {
setMyString(myArray[0]);
}
};
//handle effect in custom solve
useEffect(() => {
//check change here
const isEqual = arrayEquals(myArray, previousArray) && previousBoolean === myBoolean;
if (!isEqual)
handleEffect();
});
useEffect(() => {
console.log('child-useEffect-call with sallow compare');
if(myBoolean) {
setMyString(myArray[0]);
}
}, [myBoolean, myArray]);
return myString;
}
const Any = () => {
const [array, setArray] = useState(['1','2','3']);
console.log('parent-render');
//array is always changed
// useEffect(() => {
// setInterval(() => {
// setArray(['1','2', Math.random()]);
// }, 2000);
// }, []);
//be fine. ref is not changed.
// useEffect(() => {
// setInterval(() => {
// setArray(array);
// }, 2000);
// }, []);
//changed ref but value in array are not changed -> handle this case
useEffect(() => {
setInterval(() => {
setArray(['1','2', '3']);
}, 2000);
}, []);
return <div> <MyComponent myBoolean={true} myArray={array}/> </div>;
}

Dynamic and controlled component in react hooks

I want to dynamically generate controlled input components.
data is an array of object, which has following form:
data = [{obj1}, {obj2}, ...] // length not fixed
After data is loaded, it triggers useMemo and useEffect as below:
const [row, setRow] = useState([]);
const onContentChange = e => {
const currentRow = [...row]
currentRow[e.target.dataset.idx][e.target.className] = e.target.value
setRow(currentRow)
}
useEffect(() => {
const arr = []
data.forEach(obj => {
arr.push({
content: obj.content
})
})
setRow(arr)
}, [data])
const mData = useMemo(() => {
const retArr = []
data.forEach( (obj, i) => {
retArr.push({
content: (
<input
type="text"
className="content"
name={`content-${i}`}
id={`content-${i}`}
data-idx={i}
onChange={onContentChange}
value={row[i].content} // Error
/>
)
})
})
return retArr;
}, [data])
However, row[i] is empty at that moment. Maybe useRow function in useEffect is executed after factory of useMemo done.
Since there is a callback at useEffect, is there any way to control order of useEffect and useMemo?
Any other opinions are appreciated!

Categories

Resources