Accessing nodes returns null in react - javascript

I am trying to render a list of searched users returned from server and add when clicked on any of the list items, get the value of inside the node component.
First I fetch the users
useEffect(() => {
if (searchText.length === 0) {
setSearchedUsers([])
return
}
fetch(--users--)
}).then((res) => res.json())
.then((users) => {
setSearchedUsers(users)
})
}, [searchText])
then render
const searchedNodes = useRef([])
function renderWithRefs() {
return (searchedUsers.map((user, i) => (
<div className={`searched-user-div ${i}`} ref={(div) => searchedNodes.current[i] = div} key={i} onClick={(e) => showUserData(e)}>
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
</div>
)))
}
function showUserData(e) {
let ref = searchedNodes.current[parseInt(e.target.className.split(" ")[1])]
let Name = ref.querySelector(".searchedUser-Name").textContent
let age = ref.querySelector(".searchedUser-age").textContent.slice(1)
....
}
when clicked I get Error: cannot read property 'querySelector' of undefined

Directly querying the dom in React is a very bad pattern. You should not do it unless you have no choice ( for example you need to query the size of an item ).
In your case, you could do something way easier like this:
<div className={`searched-user-div ${i}`} key={i} onClick={(e) => showUserData(user)}>
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
</div>
You then don't have to query the DOM.

First,
The use of array index as a key is not recommended
It's a bad idea to use the array index since it doesn't uniquely
identify your elements. In cases where the array is sorted or an
element is added to the beginning of the array, the index will be
changed even though the element representing that index may be the
same. This results in unnecessary renders.
function renderWithRefs() {
return (searchedUsers.map((user, i) => (
<div className={`searched-user-div`} ref={(div) => searchedNodes.current[i] = div} key={JSON.Stringlify(user)} onClick={() => showUserData(user)} onKeyPress={() => showUserData(user)} tabIndex={0} role="button">
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
</div>
)))
}
function showUserData(user) {
//here you can show all user data
<>
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
<p className="searchedUser-LastName">{user.LastName}</p>
<p className="searchedUser-tel">{user.tel}</p>
....
</>
....
}
You can see I added a few props to your div all because of no-static-element-interactions
Static HTML elements do not have semantic meaning. This is clear in
the case of and . It is less so clear in the case of
elements that seem semantic, but that do not have a semantic mapping
in the accessibility layer. For example , , ,
, , and -- to name a few -- have no
semantic layer mapping. They are as void of meaning as .

Related

Remove element from useState array by index

SOLUTION: Update the key value for the input element to refresh the default value => content of the input element. Deleting an element from the array DID work. Thanks for your help!
src: https://thewebdev.info/2022/05/12/how-to-fix-react-input-defaultvalue-doesnt-update-with-state-with-javascript/#:~:text=state%20with%20JavaScript%3F-,To%20fix%20React%20input%20defaultValue%20doesn't%20update%20with%20state,default%20value%20of%20the%20input.
I got an useState array in my code which represents a lisst of students:
const [students, setStudents] = useState([""]);
This array gets mapped to student elements:
{students.map((student, index) => <Student setStudents={setStudents} students={students} id={index} key={index} content={student} />)} I also got an AddStudent element which adds students to the array.
function AddStudent(props) {
const {setStudents} = props;
return (
<button className="change-student add-student" onClick={() => {
setStudents((students) => [...students, ""])
}}>
+
</button>
);
}
The RemoveStudent component is supposed to remove a student by its index in the array. I've tried many different ways but none worked correctly. How can I get it to work? Here is my code:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button className="change-student remove-student" onClick={() => {
let data = students;
if(id > -1) {
data.splice(id, 1);
}
console.log(data)
// setStudents(data)
// alternative:
// setStudents(students.filter(index => index !== id)); // removes the last element in the list
// doesn't work properly
}}>
-
</button>
)
}
Thanks for your help!
2 things should be noted here:
While updating react state arrays, use methods that return a new array (map, filter, slice, concat),
rather than ones that modify the existing array (splice, push, pop, sort).
While updating React state using its previous value, the callback argument should be used for the state setter. Otherwise you may get stale values. (See React docs).
if(id > -1) {
setStudents(students=> students.filter((s,i)=>(i != id)))
}
Consult this article, for a complete reference about how to update React state arrays.
You need to copy the students array first and then try removing the student by index. I assume by id you mean index at which to remove the student. Then you can try something like:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button
className="change-student remove-student"
onClick={() => {
if(id > -1) {
const data = [...students]; // making a copy
data.splice(id, 1); // removing at index id
console.log(data)
setStudents(data)
}
}}
>
-
</button>
)
}
With array.filter() you have a mistake in how you pass callback to filter() method. Please try the following:
setStudents(students.filter((,index) => index !== id));
Notice the index is second param of the callback so I used a , before index.
After #Irfanullah Jan 's answer you should make sure how you show the student.
Here is the simple example:
const [students, setStudents] = useState([1, 2, 3]);
return (
<div>
{students.map((student, index) => {
return <div>{student}</div>; // show the value not the index
})}
<button
onClick={() => {
let id = 1;
const copy = [...students];
copy.splice(id, 1)
console.log(copy)
setStudents(copy);
}}
>
-
</button>
</div>
);
The code above will delete the student of "index==1"

Cannot map value of object seen in console

My idea is to click on any of the buttons on the left navbar and whenever the name of the clicked button in the logos object matches any of the names in the items object within projects, then display those objects.
When I click on any of the buttons on the left, I transform that object's active property to true within the logos object. After I've filtered the values, I can see all of the correct values in the console, but I can't loop through them - with a for loop or a map. Oddly enough, when I write filteredValues[0] I am able to output that data to the screen but since I want multiple outputs for some of these clicked values, this is not an option. Any help is appreciated. Thank you!
These are the items that I can't loop through but am getting back when I console log them
These are my projects
These are my logos
const Homepage = () => {
const {state} = useContext(Context)
const {projects,logos} = state;
return (
<>
<div className="container">
<Language />
<div className="homepage">
{logos.map(logo => {
let filteredValues;
if(logo.active == true){
filteredValues = Object.values(projects).filter(({items}) => Object.values(items).includes(logo.name))
filteredValues.map((val) =>{
console.log(val)
return(
<div>{val.title}</div>
)
}) //end of filteredValues.map
} //end of if
}) // end of logos.map
}
</div>
</div>
</>
)}
An Array.map() would always return an Array of same length, irrespective of you return anything or not. If you do not return anything then
it would be a sparse Array with empty values.
Try returning only the array with the required data. Here I have just separated out the Logos logic in a separate variable.
render() {
const Logos = (
<div className="homepage">
logos.reduce((acc, logo) => {
if (logo.active) {
const filteredValues = Object.values(projects).filter(({items}) => Object.values(items).includes(logo.name));
Object.values(projects).forEach(({items}) => {
if (Object.values(items).includes(logo.name)) {
acc.push((<div>{val.title}</div>));
}
});
}
return acc
}, [])
</div>
)
return (
<div className="container">
<Language />
{Logos}
// rest of the code
)
}

React.js: How to filter JSX element array on custom attribute?

I am starting with a simple array of JSX elements:
const jsxArray = dataItems.map(item => (
<div>
<Header>{item.title}</Header>
<Paragraph>{item.body}</Paragraph>
<Paragraph customAttribute={item.isActive} >{item.tags}</Paragraph>
</div>
))
Inside render, or rather return since I use functional components for everything now, I'd like to filter for JSX elements where the isActive attribute was tagged true.
return (
{jsxArray
.filter(jsxElement => // want to filter in JSX elements
// that are true for customAttribute keyed to `item.isActive`)
}
)
Is there any way to do it?
If there is not precisely a good way I am open to workarounds.
It is possible for me to simply filter the array at an earlier step. It would result in some extra code duplication though, since I would still need the array of unfiltered JSX elements elsewhere.
You don't filter the list after you render it. At that point it's just a tree of nodes that doesn't have much meaning anymore.
Instead you filter the items first, and then render only the items that pass your criteria.
const jsxArray = dataItems.filter(item => item.isActive).map(item => (
<div>
<h3>{item.title}</p>
<p>{item.body}</p>
<p customAttribute={item.isActive} >{item.tags}</p>
</div>
))
It is possible for me to simply filter the array at an earlier step. It would result in some extra code duplication though, since I would still need the array of unfiltered JSX elements elsewhere.
Not necessarily. When dealing with filtering like this myself I create two variables, one for the raw unfiltered list and one for the filtered items. Then whatever you're rendering can choose one or the other depending on its needs.
const [items, setItems] = useState([])
const filteredItems = items.filter(item => item.isActive)
return <>
<p>Total Items: ${items.length}</p>
<ItemList items={filteredItems} />
</>
Instead of accessing the jsx element properties (which I think it's either not possible or very difficult) I suggest you to act in this way:
Save the renderer function for items in an arrow function
const itemRenderer = item => (
<div>
<Header>{item.title}</Header>
<Paragraph>{item.body}</Paragraph>
<Paragraph customAttribute={item.isActive} >{item.tags}</Paragraph>
</div>
)
Save the filter function in an arrow function
const activeItems = item => item.isActive
Use them to filter and map
const jsxArray = dataItems.filter(activeItems).map(itemRenderer)
Use them to map only
const jsxArray = dataItems.filter(activeItems).map(itemRenderer)
Hope this helps!
Usually you would filter the plain data first and then render only the markup for the filtered elements as described in #Alex Wayne answer.
If you worry about duplication of the markup, that can be solved by extracting a component from it:
const Item = ({title, body, isActive, tags}) => (
<div>
<Header>{title}</Header>
<Paragraph>{body}</Paragraph>
<Paragraph customAttribute={isActive}>{tags}</Paragraph>
</div>
);
For rendering the filtered list you can then do:
{items.filter(item => item.isActive).map(item => <Item {...item} />)}
and for the unfiltered list:
{items.map(item => <Item {...item} />)}

React incrementing variable within .map() function

I am mapping through an array, and I want my variable i to be used as a unique key for my Components, however I do not know how (or where) to increment it correctly, if I add a {i++} within the <Component> tags then it will display the value of i on screen, and if I instead add {this.function(i)} and place the i++ inside the function, it will call the function but the variable i will reinitiate to the value of 0 everytime, so the key value will not be unique. I need the value of i to be the key for the component and it has to be incremented by 1 everytime, does anyone know how I can achieve this? Also, as you can see in the code, when the component is clicked it will make a function call which will send the value of i of the clicked component as a parameter to the called function.
Code:
function(i) {
console.log(i)
}
render() {
var i = 0;
var {array} = this.state;
return (
<div className="App">
{array.map(item => (
<Component key={i} onClick={(e) => this.function(i, e)}>
<p>{item.name}</p>
</Component>
))}
</div>
);
}
The map function gets a second parameter which is the index of the element:
{array.map((item, i) => (
<Component key={i} onClick={(e) => this.function(i, e)}>
<p>{item.name}</p>
</Component>
)}
Be aware that if you intend to sort this array or change its contents at runtime, then using array index as a key can cause some mistakes, as sometimes an old component will be mistake for a new one. If it's just a static array though, then using index as a key shouldn't be a problem.
.map already offer the increment, just add a second variable to the callback
render() {
var {array} = this.state;
return (
<div className="App">
{array.map((item,i) => (
<Component key={i} onClick={(e) => this.function(i, e)}>
<p>{item.name}</p>
</Component>
))}
</div>
);
}
You could try array.map((x, Key) => console.log(key)); ..
In place of console.log you could add your code, it should work fine as per your requirement.

How to search in a json array, and return the result as I type in the input

So far I can only search an element of the array if I type the exact name present in my api, in this my api has an array with 20 positions and if I type exactly the name of the element I search it returns an array with 19 positions with undefined and 1 position with the array found, what I want to do and search while I type instead of searching only when I type the full name.
After my search I try to change the state of a component so that it is rendered only with the value fetched, but this does not happen, if anyone knows I am very grateful.
updated code
import data from "../sample_data/recipes.json";
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchString: []
};
}
componentDidMount() {
this.setState({ searchString: data.results })
}
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var indexes = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) !== -1;
})
this.setState({ searchString : indexes });
}
render() {
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={this.state.searchString} />}
</div>
</div>
</div>
);
}
}
export default App;
I suppose that you want some kind of filtering while you're typing:
can you try this ? :
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var filteredItems = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) === -1;
})
this.setState({ searchString : filteredItems });
}
Explanation :
What is requested is to not display items that contains typed letters, to do that, you can use filter items with filter method and return only the items which doesn't have the typed letters in the title (using the indexOf method).
move setState outside map.
replace map with for loop
maybe try this?
var indexes = [];
data.results.forEach(item => {
if(item.title.indexOf(myObjTitle.title)) {
indexes.push(item);
}
});
this.setState({searchString : indexes});
As I understand your question, you are trying to search the element from json Array.I just added the new key to store filterString. Here is my solution
onChange(firstName){
if(firstName != undefined){
this.setState({
nameToFilter : firstName
})
}
}
//Inside the render method
I am using nameToFilter to filter the data.
render() {
let searchString = this.state.searchString
if(this.state.nameToFilter != undefined && this.state.nameToFilter.length>0)
{
searchString = this.state.searchString.filter(item => (
item.title == this.state.nameToFilter
));
}
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={searchString} />}
</div>
</div>
</div>
);
}
It seems you are trying to filter the matching results. In which case it's probably better to use filter instead of map.
onChange(value) {
let results = [];
// Empty string is `falsy`, avoid filtering when no value was given
if (value) {
/*
Pick only one option, remove the other two, or you will
process your data three times instead of once!
*/
// Case-sensitive, exact match
results = data.filter(item => item.title === value);
// Case-insensitive, exact match, value and title are the same when lowercased
results = data.filter(item => (
item.title.toLowerCase() === value.toLowerCase()
));
// Case-insensitive, partial match, value was found *within* title
results = data.filter(item => (
item.title.toLowerCase().indexOf(
value.toLowerCase()
) !== -1
));
}
this.setState({results});
}
Additionally, if you want a single result (the first one that matches), using find instead of filter is better, because it would stop searching after the first match instead of traversing the whole array.
Map
Returns a new array with as many entries as you previously had, but you may return anything you want as the entries, this is why you get so many undefined entries right now. (Because you only return an item when the title matches, hence you implicitly return undefined when not). Map loops through the whole array.
Filter
Returns a new array with as many entries matches your test function. If the function returns true, then the item is included, it it returns false, the item will be omitted. Filter loops through the whole array.
Find
Will only retrieve the first entry that matches your test function. It will stop looping once a match is found.
Bonus
You will most likely need to learn about debouncing, which you can do with lodash.debounce.
Debouncing is a method used to prevent a function from executing many times in a short interval (so you don't unnecessarily re-render)

Categories

Resources