Why does an array reverse itself outside of a useEffect? - javascript

There is an array called recent that contains the values of an input when a form is submitted. Let's say the array contains the values 'London' and 'Paris'. Inside the useEffect, the array would return [London, Paris] as it should. However, outside the useEffect, the array would return the values [Paris, London] - the reverse.
At first, I thought that it was due to a second array list that was assigned the reverse of the recent array inside the useEffect, but this is not the case as the recent array still returns the values correct order after the reverse is assigned to list. The recent array is reassigned to list every time a new value is added to recent so that list is always the reverse of recent.
How do I fix this?
const [recent, setRecent] = useState([]);
const [reverse, setReverse] = useState(true); // says whether to reverse list or not
const [list, setList] = useState();
const RecentSearches = () => {
useEffect(() => {
if (reverse) {
console.log(recent, "before reverse"); // returns ['London', 'Paris']
setList(() => recent.reverse()); // reverses recent
setReverse(false); // does not reverse list every time the function runs - only reverses it once
console.log(recent, "after reverse"); // returns ['London', 'Paris']
console.log(recent[0], "first item"); // returns 'London'
}
}, []);
let searches;
console.log("============");
console.log(recent); // returns ['Paris', 'London']
console.log(recent[0], "first item"); // returns 'Paris'
console.log("---------");
console.log(list); // returns ['Paris', 'London']
console.log("============");
if (list) {
searches = list.map((value, index) => ( <
button type = "submit"
form = "form"
key = {
index
}
onClick = {
() => setSearch(value)
}
onMouseEnter = {
() => setSearch(value)
}
onMouseLeave = {
() => setSearch("")
} > {
value
} <
i className = "fa-solid fa-chevron-right" > < /i> < /
button >
));
} else {
searches = "";
}
return searches;
};
const [search, setSearch] = useState(""); // value in the input
const handleSubmit = async event => {
console.log("form submitted");
event.preventDefault();
setRecent([...recent, search]); // adds search value to end of recent array
setReverse(true); // means that list will be set to reverse of recent with the new value
...
};

Array.reverse is destructive which means that by doing recent.reverse() you're actually reversing recent. You should do instead
setList([...recent].reverse())
So you're not changing directly recent

Related

Problem with sessionStorage: I am not displaying the first item correctly

I am having a problem with sessionStorage; in particular, I want the id of the ads to be saved in the session where the user puts the like on that particular favorite article.
However, I note that the array of objects that is returned contains the ids starting with single quotes, as shown below:
['', '1', '7']
but I want '1' to be shown to me directly.
While if I go into the sessionStorage I notice that like is shown as:
,1,7
ie with the leading comma, but I want it to start with the number directly.
How can I fix this?
function likeAnnunci(){
let likeBtn = document.querySelectorAll('.like');
likeBtn.forEach(btn => {
btn.addEventListener('click', function(){
let id = btn.getAttribute('ann-id');
//sessionStorage.setItem('like', [])
let storage = sessionStorage.getItem('like').split(',');
//console.log(storage);
if(storage.includes(id)){
storage = storage.filter(id_a => id_a != id);
} else {
storage.push(id);
}
sessionStorage.setItem('like', storage)
console.log(sessionStorage.getItem('like').split(','));
btn.classList.toggle('fas');
btn.classList.toggle('far');
btn.classList.toggle('tx-main');
})
})
};
function setLike(id){
if(sessionStorage.getItem('like')){
let storage = sessionStorage.getItem('like').split(',');
if(storage.includes(id.toString())){
return `fas`
} else {
return `far`
}
} else {
sessionStorage.setItem('like', '');
return`far`;
}
}
The main issue you're having is that you're splitting on a , instead of using JSON.parse().
Also, you've got some other code issues and logical errors.
Solution:
function likeAnnunci() {
const likeBtn = document.querySelectorAll('.like');
likeBtn.forEach((btn) => {
btn.addEventListener('click', function () {
let id = btn.getAttribute('ann-id');
//sessionStorage.setItem('like', [])
let storage = JSON.parse(sessionStorage.getItem('like') || '[]');
//console.log(storage);
if (!storage.includes(id)) {
storage.push(id);
}
sessionStorage.setItem('like', JSON.stringify(storage));
console.log(JSON.parse(sessionStorage.getItem('like')));
btn.classList.toggle('fas');
btn.classList.toggle('far');
btn.classList.toggle('tx-main');
});
});
}
More modular and optimal solution:
const likeBtns = document.querySelectorAll('.like');
// If there is no previous array stored, initialize it as an empty array
const initLikesStore = () => {
if (!sessionStorage.getItem('likes')) sessionStorage.setItem('likes', JSON.stringify([]));
};
// Get the item from sessionStorage and parse it into an array
const grabLikesStore = () => JSON.parse(sessionStorage.getItem('likes'));
// Set a new value for the likesStore, automatically serializing the value into a string
const setLikesStore = (array) => sessionStorage.setItem('likes', JSON.stringify(array));
// Pass in a value.
const addToLikesStore = (value) => {
// Grab the current likes state
const pulled = grabStorage();
// If the value is already there, do nothing
if (pulled.includes(value)) return;
// Otherwise, add the value and set the new array
// of the likesStore
storage.push(value);
setLikesStore(pulled);
};
const likeAnnunci = (e) => {
// Grab the ID from the button clicked
const id = e.target.getAttribute('ann-id');
// Pass the ID to be handled by the logic in the
// function above.
addToLikesStore(id);
console.log(grabLikesStore());
btn.classList.toggle('fas');
btn.classList.toggle('far');
btn.classList.toggle('tx-main');
};
// When the dom content loads, initialize the likesStore and
// add all the button event listeners
window.addEventListener('DOMContentLoaded', () => {
initLikesStore();
likeBtns.forEach((btn) => btn.addEventListener('click', likeAnnunci));
});

REACT Dynamic checkboxes

Purpose: I want to create any # of rows containing any # of checkboxes that will be handled by a useState hook.
Problem: Page becomes frozen / constant loading state with nothing showing. No console logs, and debugger doesn't even start. React usually will prevent endless loops of updates. But in this case it didn't get caught.
What I've tried:
console.logs (nothing gets outputted)
debugger statements (nothing
gets paused)
Cant do much bc of frozen page.
CODE:
const CreateCheckboxes = ({ rows, cols }) => {
const classes = useStyles()
const [checked, setChecked] = useState({})
const [isLoaded, setIsLoaded] = useState(false)
//temp initializer
let state = {};
//configures state based on unique checkbox name.
const handleChange = (e) => {
const value = {
...checked,
[e.target.name]: e.target.checked,
}
setChecked(value)
};
//Helper function
const createBoxes = (row, col) => {
let rowArr = [];
for (let i = 0; i < row; i++) {
let checkboxArr = []
for (let j = 0; i < col; j++) {
checkboxArr.push(
<Checkbox
name={`row-${i}-checkbox-${j}`} //unique identifier in the state.
checked={checked[`row-${i}-checkbox-${j}`]}
onChange={(e) => handleChange(e)}
/>
)
//store temp state so that react useState is given a list of initialized 'controlled' states.
//react deosnt like undefined states.
state[`row-${i}-checkbox-${j}`] = false;
}
rowArr.push(
<div className={classes.row}>
<Typography>{`Sound ${i}`}</Typography>
{/* JSX array */}
{checkboxArr}
</div>
)
}
// JSX array
return rowArr
}
//output as a jsx array of 'x row divs' contiaining 'y checkboxes'
const sequenceData = createBoxes(rows, cols)
useEffect(() => {
setChecked(state)
setIsLoaded(true)
}, [])
return isLoaded && (
<>
{sequenceData}
</>
);
}
Solution: Check your loop conditions. Inner loop set to i instead of j.
yes, but I think that component doesn't need to have state various.
I tried to create one. You can check it out following when you have time to see :)
https://codesandbox.io/s/stackoverflow-dynamic-checkboxes-5dh08
Solution: Check your loop conditions. Inner loop set to i instead of j.

Cannot empty array in ReastJS useEffect hook

I'm having this table view in React where I fetch an array from API and display it. When the user types something on the table search box, I'm trying to clear the current state array and render the completely new result. But for some reason, the result keeps getting appended to the current set of results.
Here's my code:
const Layout = () => {
var infiniteScrollTimeout = true;
const [apiList, setapiList] = useState([]);
//invoked from child.
const search = (searchParameter) => {
//Clearing the apiList to load new one but the console log after setApiList still logs the old list
setapiList([]); // tried setApiList([...[]]), copying the apiList to another var and emptying it and then setting it too.
console.log(apiList); //logs the old one.
loadApiResults(searchParameter);
};
let url =
AppConfig.api_url + (searchParameter || "");
const loadApiResults = async (searchParameter) => {
let response = await fetch(url + formurlencoded(requestObject), {
method: "get",
headers: headers,
});
let ApiResult = await response.json(); // read response body and parse as JSON
if (ApiResult.status == true) {
//Set the url for next fetch when user scrolls to bottom.
url = ApiResult.next_page_url + (searchParameter || "");
let data;
data = ApiResult.data;
setapiList([...data]);
}
}
useEffect(() => {
loadApiResults();
document.getElementById("myscroll").addEventListener("scroll", () => {
if (
document.getElementById("myscroll").scrollTop +
document.getElementById("myscroll").clientHeight >=
document.getElementById("myscroll").scrollHeight - 10
) {
if (infiniteScrollTimeout == true) {
console.log("END OF PAGE");
loadApiResults();
infiniteScrollTimeout = false;
setTimeout(() => {
infiniteScrollTimeout = true;
}, 1000);
}
}
});
}, []);
return (
<ContentContainer>
<Table
...
/>
</ContentContainer>
);
};
export default Layout;
What am I doing wrong?
UPDATE: I do see a brief moment of the state being reset, on calling the loadApiResult again after resetting the state. The old state comes back. If I remove the call to loadApiResult, the table render stays empty.
add apiList in array as the second parameter in useEffect
You need to use the dependencies feature in your useEffect function
const [searchParameter, setSearchParameter] = useState("");
... mode code ...
useEffect(() => {
loadApiResults();
... more code ...
}, [searchParameter]);
useEffect will automatically trigger whenever the value of searchParameter changes, assuming your input uses setSearchParameter on change

Trying to populate array with string values based on filter selections, and filter records by comparing the array to string value of record attribute

I have an API response that returns a list of records each with a 'status' attribute. The status can be 'current', 'former', 'never'.
I have a set of 3 checkboxes that each carry a value for the same that a user would click to filter the list of records accordingly.
The way I'm trying to achieve the filtering functionality is by using a hook for const [statuses, setStatuses] = useState<string[]>([]);
And then populating that array with the value of each checkbox from:
<div>FILTER BY STATUS</div>
<FilterSection>
<span><input type="checkbox" value="Current" onClick={handleStatusChange}/> Current</span>
<span><input type="checkbox" value="Former" onClick={handleStatusChange}/> Former</span>
<span><input type="checkbox" value="Never" onClick={handleStatusChange}/> Never </span>
</FilterSection>
</div>
I then have the onClick method that calls handleStatusChange:
const handleStatusChange = e => {
setStatuses([...statuses, e.target.value]);
props.onFilterChange(statuses, state)
console.log('status value: ', e.target.value)
};
Which passes it's values up to the container component and feeds into the filter function which looks like:
const handleFilterChange = (status: string[], state: string) => {
store.set('currentPage')(0);
const allLocations = store.get('locations');
let filteredLocations = allLocations.slice();
const pageSize = store.get('pageSize');
if (status && status.length > 0) {
filteredLocations = filteredLocations
.filter(l => {
l.financialDetails && l.financialDetails.locationStatus === status;
})
.slice();
}
store.set('filteredLocations')(filteredLocations);
const pagedLocations = filteredLocations.slice(0, pageSize);
store.set('pagedLocations')(pagedLocations);
store.set('locationsLoading')(false);
};
The problem I'm seeing is that I get a TypeScript error inside handleFilterChange saying This condition will always return 'false' since the types 'string' and 'string[]' have no overlap.
EDIT
I've updated the handleStatusChange function and it's populating the array but only on the second click of a checkbox. So on first click it produces an empty array, on second click it grabs whatever the first clicked value was and pushes it into the array so that it's always one click behind what a user actually selected.
I think something like this would work.
const handleStatusChange = e => {
const status = e.target.value;
if (!statuses.includes(status)) {
setStatuses(statuses.concat(status));
}
else {
const statusIndex = statuses.findIndex(status);
statuses.splice(statusIndex, 1);
setStatuses(statuses);
}
props.onFilterChange(statuses)
};
And for the handleFilterChange...
const handleFilterChange = (statuses: string[]) => {
const allLocations = store.get('locations');
let filteredLocations = allLocations.slice();
if (statuses && statuses.length) {
statuses.forEach((status) => {
filteredLocations = filteredLocations
.filter(l => (l.financialDetails && l.financialDetails.locationStatus !== status));
}
const pagedLocations = filteredLocations.slice(0, pageSize);
};
I removed some of the code I thought was irrelevant to the problem.
const handleStatusChange = e => {
const updatedStatus = [...statuses, e.target.value];
props.onFilterChange(updatedStatus, state)
setStatuses(updatedStatus);
console.log('status value: ', e.target.value)
};
you always should call parent (props function) to pass state to parent because when you call setStatuses at that time statuses array is empty so call that
props.onFilterChange
first before setting state.
or in functional component you can use useEffect like this
useEffect(()=>{
props.onFilterChange(statuses, state);
},[statuses]);

javascript cannot access array methods on push

I have a react app that pulls data from firebase. It adds data to the array fine. But cannot be accessed. calling the index returns undefined and length of array is 0. but when you print the array the console shows there is an element inside. Whats going on?
componentWillMount() {
itemsRef.on('value', (snapshot) => {
let items = snapshot.val();
let keys = Object.keys(items);
for(var i = 0; i < keys.length; i += 1) {
let k = keys[i];
let name = items[k].name;
let start = items[k].start;
this.state.timers.push( {
name: name,
start: start
} );
}
});
console.log(this.state.timers); // shows an array with stuff in it
this.setState({timers: this.state.timers}); // timers dont get added
// you cant even access elements in the array.
// ex: this.state.timers[0] returns undefined, but the console shows that it exists when the whole array is printed.
}
You shouldn't mutate directly your state like you do in the `this.state.timers.push({})``
You should do something like that:
itemsRef.on('value', (snapshot) => {
const items = snapshot.val();
this.setState({
timers: [
...this.state.timers,
...Object
.keys(items)
.map(key => {
const { name, start } = items[key];
return { name, start };
}),
],
});
});
You shouldn't push directly to the state. Instead you should to something like this:
ES6 variant
const timers = [...this.state.timers];
timers.push({ name, start });
this.setState({ timers })

Categories

Resources