React race condition causing undefined id - javascript

I am just trying to figure out a weird issue in my React project. So I am executing some code in my useEffect. As you can see, I am trying to grab the currentObj from an array of objects based on searchTerm. However, there seems to be a race condition
in getting the value searchTerm. The toggleFilter ans setAvailabilities functions below are throwing an error saying that the id is not defined. And the page crashes.
useEffect(() => {
const search = window.location.search;
const params = new URLSearchParams(search);
let param = "";
if (search.includes("neighborhood")) {
param = params.get("neighborhood");
const searchTerm = search.toLowerCase();
if (searchTerm) {
const currentObj = searchTerm && locations.find(item => item.linktitle.toLowerCase().includes(searchTerm));
console.log(searchTerm, currentObj);
toggleFilter(currentObj.id);
setAvailabilitiesFilter([currentObj.id]);
}
}
return () => resetAllFilters();
}, []);
However, if I hardcode a value into it, it actually renders ok.
const currentObj = searchTerm && locations.find(item => item.linktitle.toLowerCase().includes('manhattan'));
But of course, I don't want to hardcode the value as I expect to dynamically render the searchTerm
Is there any way I can in the useEffect wait until searchTerm is defined before running that .find method? I also tried adding the searchTerm in the dependency array but that doesn't seem right as it is not giving suggestions there and seems not to be scoped there.
Any help appreciated. Thanks!

I attempted to replicate what you were doing to some extent in a code sandbox and the functionality seems to work. Originally, it worked fine until I passed a string which was not in the locations array and I got the same undefined error.
So basically, what you need to do is first run the find function. Then only, if there is a match, i.e. the find function does not return undefined, will the togglefilters and other code be run. You can see the code below.
import { useEffect, useState } from "react";
import "./styles.css";
const locations = [
{
id: 1,
linktitle: "Paris",
country: "France"
},
{
id: 2,
linktitle: "Madrid",
country: "Spain"
},
{
id: 3,
linktitle: "Shanghai",
country: "China"
},
{
id: 4,
linktitle: "London",
country: "England"
}
];
export default function App() {
const [filter, setFilter] = useState("");
const toggleFilter = (id) => {
console.log("toggled id:", id);
setFilter(id);
};
useEffect(() => {
const search = window.location.search;
// this statement avoids any matches with empty strings
if (search === "") return;
const searchTerm = search.slice(1).toLowerCase();
// first check whether a location is matched
// before running any other functionality
const location = locations.find((item) =>
item.linktitle.toLowerCase().includes(searchTerm)
);
if (location) {
toggleFilter(location.id);
console.log(searchTerm, location);
}
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
The code sandbox link is here where you can see what is happening and view the console logs:
https://codesandbox.io/s/currying-sound-wyb0iz?file=/src/App.js:0-831
You can see the console logs of the objects appear if they are cities in the location array which have been queried in the query params. Also, there is no undefined with the IDs which are filtered.
I sliced the searchTerm so that the initial "?" would be removed from the string.

Related

How to use useEffect hook with the dependency list as a specific field in an array of objects?

Let's say I have an array like this:
[
{
country: '',
'city/province': '',
street: ''
},
{
country: '',
'city/province': '',
street: ''
}
]
How do I have the useEffect() hook run every time the value of the 'country' field in any item inside the array changes?
Normally you wouldn't want to do that, but just to answer your question, it can be done, so let me propose the following assuming your list is called items:
useEffect(() => {
}, [...items.map(v => v.country)])
What the above code does is to spread all items (with its country property) into the useEffect dependency array.
The reason why this can be adhoc is mainly because React doesn't like to have a variable length of dependency. In the source code, when the length changes, it only appreciates the element change from the existing elements. So you might run into problem if you switch from 1 elements to 2 elements.
However if you have fixed number of elements, this should do what you wanted. Keep in mind the items has to be an array at all time.
NOTE: to accommodate the length issue, maybe we can add an additional variable length to the dependency array :)
}, [items.length, ...items.map(v => v.country)])
As i mentioned, most of time, you should avoid doing this, instead try to change the entire items every time when an item changes. And let the Item display to optimize, such as React.memo.
I don't think you can specifically tackle it in the dependency array, however, you can do your check inside the useEffect to have the same overall outcome.
Basically, the dependency array is passed the full data state, which will trigger the effect every change, then you do a further check if the sub property has changed.
I'm leverage lodash for brevity, but you can run any function to determine if the data has changed.
Codepen: https://codepen.io/chrisk7777/pen/mdMvpvo?editors=0010
const { useState, useEffect, useRef } = React;
const { render } = ReactDOM;
const { isEqual, map } = _;
const App = () => {
const [data, setData] = useState([
{
country: "",
"city/province": "",
street: ""
},
{
country: "",
"city/province": "",
street: ""
}
]);
const prevData = useRef(data);
// hacky updates just to demonstrate the change
// change country - should trigger useEffect
const update1 = () => {
setData((s) => [s[0], { ...s[1], country: s[1].country + "a" }]);
};
// change street - should not trigger useEffect
const update2 = () => {
setData((s) => [s[0], { ...s[1], street: s[1].street + "a" }]);
};
useEffect(() => {
if (!isEqual(map(prevData.current, "country"), map(data, "country"))) {
console.log("country changed");
}
prevData.current = data;
}, [data]);
return (
<div>
<button onClick={update1}>change country - trigger effect</button>
<br />
<button onClick={update2}>change street - do not trigger effect</button>
</div>
);
};
render(<App />, document.getElementById("app"));
Just map the countries into the effect dependency array.
const countries = data.map((x) => x.country);
useEffect(() => {
console.log(countries);
}, countries);

Why I can't delete a user object this way?

Hello Everyone !
I'm new to ReactJS, and I'm trying to do some simple projects to get the basics.
I started a little project consisting at adding and deleting user of a list with React Hooks (manipulating the state).
I can properly add a new User to my Userlist and display it, but when it comes to delete a user, nothing happened.
I found the solution, but I can't explain it, that's why I'm asking for your help !
Here is my App.js file with the DeleteUser function that works
import style from './App.module.css';
import React, { useState } from 'react';
import UserList from './components/UserList';
import UserForm from './components/UserForm'
let USERS = [
{
id: 1,
name: 'John',
age: 27
},
{
id: 2,
name: 'Mark',
age: 24
}
]
const App = () => {
const [userss, SetUsers] = useState(USERS);
const AddNewUser = (user) => {
SetUsers((prevList) => {
let updatedList = [...prevList];
updatedList.unshift(user);
return updatedList;
});
};
const DeleteUser = user => {
SetUsers((prevList) => {
let updated = prevList.filter(el => el.id !== user.id);
return updated;
});
};
return (
<div className={style.root}>
<UserForm liftUpNewUser={AddNewUser} />
<UserList users={userss} liftUpUserToDelete={DeleteUser} />
</div>
);
}
export default App;
My Question is:
Why does the DeleteUser function writtten this way (below) doesn't work ? Knowing that it is the same logic as the AddNewUser function.
const DeleteUser = user => {
SetUsers((prevList) => {
let updated = [...prevList];
updated.filter(el => el.id !== user.id);
return updated;
});
};
Sorry in advance for my english!
Hope someone can help me =)
This line in your code...
updated.filter(el => el.id !== user.id);
... is a no-op, as value of updated array never gets changed. filter returns a new array instead, and this new array gets assigned to a variable in the first snippet.
The side effect of this is that React won't have to compare those arrays by value: their references will be different. It wouldn't have been the case if filter worked the way you expected it to work, making the changes in-place, similar to Array.splice.
Because Array.filter method does not modify the original array but returns a new one.

Array is initially empty, but after an entry in a text field which is used for filtering, it is full

I verushc an array from one component to another component.
The initial array is filled by a DB and is not empty.
If I try to map over the array in my second component, it is empty (length = 0);
However, after I wrote a value in a search box to filter the array, all articles appear as intended.
What is that about?
export default function Einkäufe({ alleEinkäufe, ladeAlleEinkäufe, url }) {
const [searchTerm, setSearchTerm] = React.useState("");
const [searchResults, setSearchResults] = React.useState(alleEinkäufe);
const listeFiltern = (event) => {
setSearchTerm(event.target.value);
};
React.useEffect(() => {
setSearchResults(alleEinkäufe);
}, []);
React.useEffect(() => {
const results = alleEinkäufe.filter((eink) =>
eink.artikel.toLowerCase().includes(searchTerm.toLowerCase())
);
setSearchResults(results);
}, [searchTerm]);
[...]
{searchResults.map((artikel, index) => {
return ( ... );
})}
}
The problem is with your useEffect hook that sets the list of searchResults, it's not rerun when alleEinkäufe property is updated. You need to add alleEinkäufe as it's dependency.
React.useEffect(() => {
setSearchResults(alleEinkäufe);
}, [alleEinkäufe]);
My bet is that the parent component that renders Einkäufe is initially passing an empty array which is used as searchResults state and then never updated since useEffect with empty dependencies array is only run once on the component's mount.
I would also advise you to use English variable and function names, especially when you ask for assistance because it helps others to help you.
Your search term intially is "". All effects run when your components mount, including the effect which runs a filter. Initially, it's going to try to match any article to "".
You should include a condition to run your filter.
React.useEffect(() => {
if (searchTerm) {
const results = alleEinkäufe.filter((eink) =>
eink.artikel.toLowerCase().includes(searchTerm.toLowerCase())
);
setSearchResults(results);
}
}, [searchTerm]);
BTW, "" is falsy.

How to access child key-values on objects and clonate that object

I'm tryin to make a list from an object on React. Before I continue, I'll share my code:
const genres = {
Rock: {
album1: '',
album2: '',
},
Jazz: {
album1: '',
album2: '',
},
Pop: {
album1: '',
album2: '',
}
};
let myFunc = genress => {
let newObject = {};
Object.keys(genress).map(gen => {
newObject[gen] = 'a';
let newChild = newObject[gen];
let oldChild = genress[gen];
Object.keys(oldChild).map(gen2 => {
newChild[gen2] = 'b';
let newGrandChild = newChild[gen2];
console.log(newGrandChild);
})
});
return newObject;
}
myFunc(genres);
I wanna render that object on a list.
<ul>
<li>Rock
<ul>
<li>album1</li>
<li>album2</li>
</ul>
</li>
</ul>
...And so on
Before placing it on React I'm trying it on a normal function. I'm making a new object just to make sure I'm accesing the right values. The problem is, when I return the new object at the end of the function it returns the genres but not the albums, only the 'a' I set in the first Object.key. The console.log on the second Object.key logs undefined, and can't figure out why this is happening.
My idea is to have access to every level on the object so I can set them to variables and return them on the render's Component. I'll make more levels: Genres -> Bands -> Albums -> songs.
Thanks so much in advance :)
From what I can understand is that you are iterating over the object incorrectly.
The reason why 'a' is the only thing showing up is that you are hard coding that every time you run the loop and setting that current key that that value.
So essentially your code does not work because you set the value of the current key to be 'a' which is a string so there are no keys on 'a' so the second loop does not produce anything.
newObject[gen] = 'a'; // you are setting obj[Rock]='a'
let newChild = newObject[gen]; // this is just 'a'
let oldChild = genress[gen]; // this is just 'a'
Object.keys(newObject[gen]) // undefined
What I think you are trying to do is iterate over the object and then render the contents of that object in a list.
Let me know if the below answers your question.
You can see the code working here:
https://codesandbox.io/s/elastic-dhawan-01sdc?fontsize=14&hidenavigation=1&theme=dark
Here is the code sample.
import React from "react";
import "./styles.css";
const genres = {
Rock: {
album1: "Hello",
album2: "Rock 2"
},
Jazz: {
album1: "",
album2: ""
},
Pop: {
album1: "",
album2: ""
}
};
const createListFromObject = (key) => {
return (
<div>
<h1>{key}</h1>
<ul>
{Object.entries(genres[key]).map(([k, v], idx) => (
<li key={`${key}-${k}-${v}-${idx}`}>{`Key: ${k} Value ${v}`}</li>
))}
</ul>
</div>
);
};
export default function App() {
return (
<div className="App">{Object.keys(genres).map(createListFromObject)}</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Understanding useEffect behaviour

I have a component called StudentPage which shows the details of a student depending on the current student logged in currentStudent . The currentStudent object doesn't hold all the attributes of the Student model, so I fetch all the students with an axios request and get the student which matches the curresntStudent by id, and get all the attributes I need from it.
Now the problem is when I try to access the value of a student property, I get the error "TypeError: Cannot read property 'name' of undefined".
So I console.log- students, currentStudent, and student and noticed that when I try to access the currentStudent.name (commented below), the console.log-students outputs the list of students from my db, also noticed that the output of console.log-student object matches that of currentStudent. However, when I try to access student.name (code below), the console.log outputs shows an empty array for the students list causing the student object to be undefined.
Can anyone please help explain why I am noticing this behaviour, perhaps some concept of useEffect that I don't yet understand.
const StudentPage=({currentStudent, students, fetchStudents})=>{
useEffect(()=>{
fetchStudents();
},[]) //I tried making currentStudent a dependency [currentStudent] but same behaviour ocurs.
console.log(students);
console.log(currentStudent)
const student= students.filter(pupil=>{return(pupil.id===currentStudent.id)})[0]
console.log(student)
// return (<div>name: {currentStudent.name}</div>) this works normally but currentUser doesn't have all attributes i need
return(
<div>Name: {student.name}</div>
)
const mapStateToProps= state=>{
return {
students: state.students
}
}
export default(mapStateToProps,{fetchStudents})(StudentPage);
Here you can find an example on how to do what you want:
import React from "react";
import { useState, useEffect } from "react";
export function StudentPage({ studentId, fetchStudents }) {
const [student, setStudent] = useState();
useEffect(() => {
(async () => {
const students = await fetchStudents();
const currentStudent = students.find((student) => {
console.log(student);
return student.id === studentId;
});
setStudent(currentStudent);
})();
}, [studentId, fetchStudents]);
return (
<>
Student ID: {studentId}, Student name: {student && student.name}
</>
);
}
useEffect will be triggered whenever the dependency (studentId in this case) changes, including on first render. You should store you value in state (using useState) and have the necessary checks in place to see whether it is null or not.

Categories

Resources