React Hooks reversing syntax of useState - pretty strange - reactjs

So I've used React Hooks somewhat heavily in the past few months on one particular project - this is the first time I've seen anything like this and was wondering if anybody had an explanation as to what is happening.
I have the following:
const [ setSectionDefects, sectionDefects ] = useState([]);
const { propertyDefects, propertySections } = props;
useEffect(()=> {
const defectsBySection = [];
propertySections.map(propertySection => {
const sectionId = propertySection.SectionId;
const defectArray = [];
const sectionObject = { id: sectionId, defects: defectArray };
propertyDefects.map(propertyDefect => {
if (propertyDefect.WosectionId == sectionId) {
defectArray.push(propertyDefect);
}
})
defectsBySection.push(sectionObject);
})
// setSectionDefects(defectsBySection);
// sectionDefects(defectsBySection);
}, []);
console.log(setSectionDefects, sectionDefects)
When the code reaches the console.log statement, it says that 'setSectionDefects' is an array and 'sectionDefects' is the function to set it!
My mind is blown, I can't figure it out for the life of me - the syntax, as I've learned it, is the function declaration first, and then the variable to be set -
ie: const [ setSectionDefects, sectionDefects ] = useState([]);
Has anyone else ran into this?

the first item in useState is the state itself and and second item is function to update it.
https://reactjs.org/docs/hooks-reference.html#usestate.
In your case just swipe the names.
const [ sectionDefects , setSectionDefects] = useState([]);

You have array destructuring the wrong way around.
It should be:
const [ sectionDefects, setSectionDefects ] = useState([]);

Related

Too many re-renders when setting state

Seems like this is a common issue in React, so apologies for asking this question again. I have a ParentComponent that contains a map with state. The map needs to have state because you can update its contents are meant to be updated. When I try to set the state of the map, I get an error stating too many re-renders. I've even tried using the useEffect hook, but I can't figure out the issue. Here's what I have:
const ParentComponent = ({show, onCloseModalButton}) => {
const [resorts, setResorts] = React.useState(new Map())
const tmpResorts = new Map(resorts)
tmpResorts.set("Keystone", [39.6069742, -105.97011])
tmpResorts.set("Breckenridge", [39.4808, -106.0676])
tmpResorts.set("Vail", [39.6061, -106.3550])
tmpResorts.set("Crested Butte", [38.8991, -106.9658])
tmpResorts.set("Winter Park", [39.8841, -105.7627])
tmpResorts.set("Copper Mountain", [39.5022, -106.1497])
setResorts(tmpResorts)
...
Here's what I've tried, to no avail:
const ParentComponent = ({show, onCloseModalButton}) => {
const [resorts, setResorts] = React.useState(new Map())
const tmpResorts = new Map(resorts)
React.useEffect(() => {
tmpResorts.set("Keystone", [39.6069742, -105.97011])
tmpResorts.set("Breckenridge", [39.4808, -106.0676])
tmpResorts.set("Vail", [39.6061, -106.3550])
tmpResorts.set("Crested Butte", [38.8991, -106.9658])
tmpResorts.set("Winter Park", [39.8841, -105.7627])
tmpResorts.set("Copper Mountain", [39.5022, -106.1497])
setResorts(tmpResorts)
}, [tmpResorts])
...
Appreciate any help!
Remove tmpResorts from useEffect Dependency array will fix it:
const [resorts, setResorts] = useState(new Map());
useEffect(() => {
console.log('called');
const tmpResorts = new Map(resorts);
tmpResorts.set('Keystone', [39.6069742, -105.97011]);
tmpResorts.set('Breckenridge', [39.4808, -106.0676]);
tmpResorts.set('Vail', [39.6061, -106.355]);
tmpResorts.set('Crested Butte', [38.8991, -106.9658]);
tmpResorts.set('Winter Park', [39.8841, -105.7627]);
tmpResorts.set('Copper Mountain', [39.5022, -106.1497]);
setResorts(tmpResorts);
}, []);

React useState is not re-rendering while it is referring cloned array by spreading

I am trying to sort an array and reflect its sort result immediately by useState hook.
I already new that react is detecting its state change by Object.is(), so trying to spread array before using useState like below;
const [reviews, setReviews] = useState([...book.reviews]);
useEffect(() => {
const sortedReviews = [...reviews]
.sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule));
setReviews([...sortedReviews])
}, [sortRule])
After sorting I checked the value of variable sortedReviews and it was sorted as expected, but react did not re-render the page so this sorting was not reflected to UI.
I already searched solutions and it seemed many could solve the issue by spreading an array to before calling useState like this stack overflow is explaining: Why is useState not triggering re-render?.
However, on my end it is not working.. Any help will be very appreciated. Thank you!
[Added]
And my rendering part is like below;
<>
{
sortedReviews
.map((review: IReview) => (
<ReviewBlock id={review.id}
review={review}
targetBook={book}
setTargetBook={setBook}/>
))
}
</>
Sometimes I facing this issue too, in my case I just "force" render calling callback.
First solution:
const [reviews, setReviews] = useState([...book.reviews]);
useEffect(() => {
const sortedReviews = [...reviews]
.sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule));
setReviews(()=> [...sortedReviews]) // <-- Here
}, [sortRule])
second solution:
You can use useRef to get data in real time, see below:
const [reviews, setReviews] = useState([...book.reviews]);
const reviewsRef = useRef();
useEffect(() => {
const sortedReviews = [...reviews]
.sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule));
setReviewRef([...sortedReviews])
}, [sortRule])
function setReviewRef(data){
setReview(data);
reviewsRef.current = data;
}
So, instead use the state reviews use reviewsRef.current as u array
I hope you can solve this!
When components don't re-render it is almost always due to mutations of state. Here you are calling a mutating operation .sort on reviews. You would need to spread the array before you mutate it.
useEffect(() => {
const sortedReviews = [...reviews].sort((review1: IReview, review2: IReview) =>
sortBy(review1, review2, sortRule)
);
setReviews(sortedReviews);
}, [sortRule]);
But there are other issues here. reviews is not a dependency of the useEffect so we would want to use a setReviews callback like setReviews(current => [...current].sort(....
In general sorted data makes more sense as a useMemo than a useState. The sortRule is a state and the sortedReviews are derived from it.
The way that you are calling sortBy as a comparer function feels a bit off. Maybe it's just a confusing name?
Also you should not need to include the type IReview in your callback if book is typed correctly as {reviews: IReview[]}.
If you include your sortBy function and sortRule variable then I can be of more help. But here's what I came up with.
import React, { useState, useMemo } from "react";
type IReview = {
rating: number;
}
type Book = {
reviews: IReview[]
}
type SortRule = string; // just a placeholder - what is this really?
declare function sortBy(a: IReview, b: IReview, rule: SortRule): number;
const MyComponent = ({book}: {book: Book}) => {
const [sortRule, setSortRule] = useState("");
const reviews = book.reviews;
const sortedReviews = useMemo( () => {
return [...reviews].sort((review1, review2) =>
sortBy(review1, review2, sortRule)
);
}, [sortRule, reviews]);
...
}
Typescript Playground Link

Implementing undo/redo function in react

I am trying to implement simple undo/redo function in my react app. So I am trying to maintain an array with old state values. But when I check the values of the old states, its all updated with the new state.
state :
state = {
schedule : [],
loads:[],
undo:[],
redo:[]
};
const redoUndoObj ={
oldStateSchedule : [...this.state.schedule],
oldStateLoads : [...this.state.loads]
}
this.setState({ undo : [ ...this.state.undo , redoUndoObj]});
I hope this give you an idea on how to solve the problem. I made code only for undo now to point you in the right direction. This example I made via React functional component using useState instead of Component class.
const [schedule, setSchedule] = useState([]);
const [loads, setLoads] = useState([]);
const [undo, setUndo] = useState([]);
const [redo, setRedo] = useState([]);
const updateData = (newSchedule, newLoads) => {
setSchedule([...newSchedule]);
setLoads([...newLoads]);
const newUndo = {
schedule: [...newSchedule],
loads: [...newLoads],
};
setUndo([...undo, ...newUndo]);
}
const undoChanges = () => {
const lastElement = undo[undo.length - 1];
const copyOfUndo = [...undo];
// Update redo to be able to rollback
setRedo([...undo]);
// Set the previous values to Schedule and Loads
schedule([...lastElement.schedule]);
loads([...lastElement.loads]);
// Remove the last element from undo
lastElement.pop();
undo([...lastElement]);
}

Storing useState hooks in a common object not working

Attempting to organize application hooks in one object:
Minimal, Complete, Verifiable Example
Instead of this:
export default function App() {
const [count1, setCount1] = useState(0)
const [count2, setCount2] = useState(0)
// Usage:
setCount1(17)
console.log(count1)
//-> 17
...this:
export default function App() {
let hook = {
"count1": {
"set": "",
"n": 0
},
"count2": {
"set": "",
"n": 0
}
}
}
const [hook.count1.n, hook.count1.set] = useState(0)
const [hook.count2.n, hook.count2.set] = useState(0)
Also tried this way, but no luck:
const [hook.count1['n'], hook.count1['set'] = useState(0)
const [hook.count2['n'], hook.count2['set'] = useState(0)
Intended to be used like this:
// Usage:
hook.count2.set(17)
console.log(hook.count2.n)
// Expected 17, but...
No luck :( throws this error:
Unexpected token, expected "," (16,13)
(Which is the "." between hook and count1)
Why can't I aggregate hook states and setters as object properties and methods? Ty Keith :^)
The useState hook should return an array consisting of the value at index 0 and setter function at index 1. So what is usually done is to desestructuring the array into 2 other variables, those which we give the names we want. Your problem is not with the hook itself, it is with the array desestructuring, it does not allow, as far as I know, for you to desestructure an array into object attributes. If you try something like the following at the browser console you'll see it won't work either.
const t = [1, () => {}]
const obj = {n: 0, s: null}
const [obj.n, obj.s] = t
You'll see an error happens with the message: Uncaught SyntaxError: Illegal property in declaration context.
So to sum up, javascript syntax does not allow you to do it, as far as I know.
I'm having trouble understanding what the intention is behind your approach of assigning the value and setter to the hooks object. My suggestion would be to use an object that holds all the counts with a single useState instance.
Does something like this work? If not can you elaborate more on why you need to use an approach similar to what you described?
export default function App() {
const [counters, setCouters] = useState({ count1: 0, count2: 0 })
// usage
setCounters({ ...counters, count1: 1 })
// ...
return null
}
Alternatively, you could do something like this if you really want it to be an object for some reason:
export default function App() {
const hooks = {
count1: {},
count2: {},
}
const [count1, setCount1] = React.useState(0)
const [count2, setCount2] = React.useState(0)
hooks.count1.n = count1
hooks.count1.set = setCount1
hooks.count2.n = count2
hooks.count2.set = setCount2
// ...
return null
}

React-native infinite re-renders

I am build a replica of the 2048 game.
I am working on the animations right now and I seem to get infinite re-renders for some reason when updating the state with any kind of array.
It seems that passing anything but an array to 'setAppearAnimations' works fine.
Thanks in advance :)
const GameProvider = (props) => {
const [appearAnimations, setAppearAnimations] = useState([])
const addAppearAnimation = (animation) => {
const newAppearAnimations = appearAnimations.concat([animation])
console.log(newAppearAnimations)
setAppearAnimations(newAppearAnimations)
}
const Piece = ({ piece }) => {
const { value, prevX, prevY, x, y, hasJustAppeared } = piece
let appearAnimation = 1
useEffect(() => {
//Add appear animation
if (hasJustAppeared) {
appearAnimation = new Animated.Value(0)
addAppearAnimation(appearAnimation)
}
}, [])
I changed the way the animations are being called so now i declare it inside A Piece component and start it inside a 'useEffect' inside it.

Resources