I was given the task to build a recursive tree with some functionality. I managed to build a tree using a recursive component, I attach the code below.
function App() {
const [data, setData] = useState([{
id: 1,
name: "node 1",
children: [{
id: 2,
name: "node 1.1",
children: [{
id: 8,
name: "node 1.1.1",
children: []
}]
},{
id: 3,
name: "node 1.2",
children: [{
id: 4,
name: "node 1.2.1",
children: [{
id: 5,
name: "node 1.2.1.1",
children: []
}]
}]
}]
}])
return (
<div className="App">
<Tree data = {data} setData = {setData} margin={15}/>
<button>Add child</button>
</div>
);
}
and
const Tree = ({data, margin, setData}) => {
return (
<Fragment>
{data.map((element, index) => (
<div>
<div className="tier">
<div
style={{marginLeft: `${margin}px`}}
>
{element.name}
</div>
</div>
{element.children && element.children.length ? <Tree
data={element.children}
setData={setData}
margin={margin + 15}
/> : false}
</div>))}
</Fragment>
);
};
I need to add a node selection when clicking on it. I have an idea about the implementation: create a state in which the ID of the desired node will be stored, and design it using CSS.
But I have absolutely no idea how to add a child node to the selected node when the button is clicked.
You did half of the job and your idea about storing id is the best overall, you could actually store the selected item itself but it will be not as great as it sounds due to mutation of this item, you will not be able to trigger dependent hooks update for it.
So you only need to store the id of selected item and a few utility things to simplify the work with the tree. On the code below you can see the flatTree that was calculated with useMemo from the original data. It just flattens your tree to an array, so you will be able to easilly find real selected item and to calculate the next id that will be inserted on button click. And to add a few more props to the Tree component itself. onClick to handle actual click and selectedTreeItem just to add some classes for selected item (optional).
import "./styles.css";
import { Fragment, useEffect, useMemo, useState } from "react";
export default function App() {
const [data, setData] = useState([{id:1,name:"node 1",children:[{id:2,name:"node 1.1",children:[{id:8,name:"node 1.1.1",children:[]}]},{id:3,name:"node 1.2",children:[{id:4,name:"node 1.2.1",children:[{id:5,name:"node 1.2.1.1",children:[]}]}]}]}]);
const [selectedTreeItemId, setSelectedTreeItemId] = useState();
const flatTree = useMemo(() => {
const flat = (item) => [item, ...(item.children || []).flatMap(flat)];
return data.flatMap(flat);
}, [data]);
const selectedTreeItem = useMemo(() => {
if (!selectedTreeItemId || !flatTree) return undefined;
return flatTree.find((x) => x.id === selectedTreeItemId);
}, [flatTree, selectedTreeItemId]);
const getNextListItemId = () => {
const allIds = flatTree.map((x) => x.id);
return Math.max(...allIds) + 1;
};
const onTreeItemClick = ({ id }) => {
setSelectedTreeItemId((curr) => (curr === id ? undefined : id));
};
const onAddChildClick = () => {
if (!selectedTreeItem) return;
if (!selectedTreeItem.children) selectedTreeItem.children = [];
const newObj = {
id: getNextListItemId(),
name: `${selectedTreeItem.name}.${selectedTreeItem.children.length + 1}`,
children: []
};
selectedTreeItem.children.push(newObj);
// dirty deep-clone of the tree to force "selectedTreeItem"
// to be recalculated and its !reference! to be updated.
// so any hook that has a "selectedTreeItem" in depsArray
// will be executed now (as expected)
setData((curr) => JSON.parse(JSON.stringify(curr)));
};
useEffect(() => {
console.log("selectedTreeItem: ", selectedTreeItem);
}, [selectedTreeItem]);
return (
<div className="App">
<Tree
data={data}
margin={15}
onTreeItemClick={onTreeItemClick}
selectedTreeItem={selectedTreeItem}
/>
<button
type="button"
disabled={!selectedTreeItem}
onClick={onAddChildClick}
>
Add child
</button>
</div>
);
}
const Tree = ({ data, margin, onTreeItemClick, selectedTreeItem }) => {
return (
<Fragment>
{data.map((element) => (
<div key={element.id}>
<div
className={`tier ${
selectedTreeItem === element ? "selected-list-item" : ""
}`}
onClick={() => onTreeItemClick(element)}
>
<div style={{ marginLeft: `${margin}px` }}>
{element.id}: {element.name}
</div>
</div>
{element.children?.length > 0 && (
<Tree
data={element.children}
margin={margin + 15}
onTreeItemClick={onTreeItemClick}
selectedTreeItem={selectedTreeItem}
/>
)}
</div>
))}
</Fragment>
);
};
Nested array in your date tree you should map as well. It should look like that
{element.children?.map(el, I => (
<p key={I}> {el. name}</p>
{el.children?.map(child, ind => (
<p key={ind}>{child.name}</p>
)}
)}
the ? is very important, because it would work only if there was a date like that.
Related
I am still pretty new and am doing a simple food ordering app in React. I have a file of dummy meals and am able to create a cart through user input, create a modal on the "Your Cart" button from the header, and map through the cart displaying as a list, but I am unable to figure out how to delete an item from the cart. I realize I need to target the id of the list item that the X is connected to, but for some reason I can't get the this command to work.
Is there something about the this command that I am not understanding when it comes to a mapped list item? So far I have both tried this through my CartModal component and the Home component, but both turn up undefined when using this and I throw an error when using e.target (enter different options like e.target.value, e.target.id, etc...), and have even tried working on an inline targeting (which is where the code below is at currently) but still comes up nothing.
I will add a couple dummy meals to this as well.
Thanks so much!
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
case "reset":
return initialState;
default:
return state;
}
};
export const CountContext = React.createContext();
const Home = (props) => {
const [cart, setCart] = useState([]);
const [count, dispatch] = useReducer(reducer, initialState);
const {isVisible, toggleModal} = useModal();
let totalPrice;
let cartCounter ;
const cartUpdaterHandler = (cartItem) =>{
setCart([...cart, cartItem]);
cartItem= cartItem;
}
useEffect(() => {
cartCounter = cart.length;
totalPrice = cart.reduce((acc, {price}) => parseFloat(acc) + parseFloat(price), 0).toFixed(2);
}, [cart])
const modalHandler = () => {
toggleModal(true)
}
const removeCI = () => {
console.log(cart)
}
return (
<CountContext.Provider
value={{ countState: count,
countDispatch: dispatch,
currentCart: cart
}}
className={classes.contextProvider}
>
{isVisible && (
<CartModal
overlayClassName="custom_overlay"
isVisible={isVisible}
hideModal={toggleModal}
currentCart={cart}
totalPrice={totalPrice}
remove={removeCI}
/>
)}
<Header cart={cart} cartCounter={cart} modal={modalHandler}/>
<Intro />
<MealForm onAdd={cartUpdaterHandler} />
{/* <Cart /> */}
{/* <CartModal isVisible={isVisible} hideModal={toggleModal} currentCart={cart} totalPrice={totalPrice}/> */}
</CountContext.Provider>
)
}
export default Home;
const CartModal = ({ isVisible, hideModal, currentCart, remove}) => {
// const checkKey = () => {
// console.log(cartItem.id)
// console.log(this.id)
// }
return isVisible
? createPortal(
<Card className={classes.modal}>
<div className={classes.modalBackground}>
<div className={classes.modalDiv}>
<div className={classes.modalHeader}>
<h5 className={classes.modalHeaderH5}>Your Cart</h5>
<button className={classes.modalHeaderX} onClick={hideModal}> X </button>
</div>
<div className={classes.modalBody}>
{currentCart.map((cartItem, i) => {
return<div key={Math.random()}>
<ul className={classes.cartModalItem} key={Math.random()}>
<div className={classes.cartModalItemInfo} >
<li className={classes.cartModalName}>{cartItem.name}</li>
<li className={classes.cartModalPrice}>{cartItem.price}</li>
<li className={classes.cartModalDesc}>{cartItem.description}</li>
</div>
//I am asking about the X below this.
<button className={classes.cartModalX} onClick={()=>{console.log(this)}}>X</button>
</ul>
</div>
})}
<h5 className={classes.modalTotal}>Total:{currentCart.reduce((acc, {price}) => parseFloat(acc) + parseFloat(price), 0).toFixed(2)} </h5>
</div>
<div className={classes.modalFooter}>
<button className={classes.closeModalButton} onClick={hideModal}> Close </button>
<button className={classes.orderModalButton}>Checkout</button>
</div>
</div>
</div>
</Card>,
document.body,
)
: null;
};
export default CartModal;
const AvalibleMeals = [
{
id: 'm1',
name: 'Sushi',
description: 'Finest fish and veggies',
price: 22.99,
},
{
id: 'm2',
name: 'Schnitzel',
description: 'A german specialty!',
price: 16.5,
},
{
id: 'm3',
name: 'Barbecue Burger',
description: 'American, raw, meaty',
price: 12.99,
},
{
id: 'm4',
name: 'Green Bowl',
description: 'Healthy...and green...',
price: 18.99,
},
];
export default AvalibleMeals;
In my code, I am trying to retrieve background images depending on the background data on the object, however those backgrounds will also change depending on the state of the element.
First of all, my data array looks like this:
const Cube = [
{
name: "foo"
faces: [
{
data: [
[{bonus: "bar", bg: "bar2"}],
...
],
...
},
...
],
...
},
...
];
And this is how my App.js looks:
function App() {
const [cellState, setCellState] = useState("inactive");
return (
<div className="App">
<div id="cube-container">
{Cube[0].faces[0].data.map((row) => {
return row.map((cell, index) => {
return (
<img
src={require(`./assets/cube/Square_Cube_Icon/${cell.bg}${
cellState === "inactive" ? "_Unlit" : "_Partial"
}.png`)}
alt={`${cell.bg} background`}
className="cellItem"
onClick={() => setCellState("active")}
state={cellState}
key={index}
/>
);
});
})}
</div>
</div>
);
}
Question is, in the 4x4 grid output, if I click on any item, instead of changing the clicked elements background because of state change, it changes background of every cell, which should happen since the useState is shared between all of them.
How can I make it so every element of the map function has their own state that I can update separately?
Found the solution:
Separating it as a component and passing key and cell values as props, then creating and manipulating the state inside fixed the issue.Selected answer also solves the issue.
Here's the solution for future readers.
App.js
function App() {
return (
<div className="App">
<div id="cube-container">
{CubeOfTruth[0].faces[0].data.map((row) => {
return row.map((cell, index) => {
return <CubeItem key={index} cell={cell} />;
});
})}
</div>
</div>
);
}
CubeItem.js
function CubeItem({ cell, index }) {
const [cellState, setCellState] = useState("inactive");
return (
<img
src={require(`../assets/cube/Square_Cube_Icon/${cell.bg}${
cellState === "inactive" || cellState === "partial" ? "_Unlit" : ""
}.png`)}
alt={`${cell.bg} background`}
className="cellItem"
onClick={() => setCellState("active")}
state={cellState}
key={index}
/>
);
}
Here's an easy way to manage multiple states using an object with its keys representing your cells and the values representing whether it is active or inactive. This is also easy to generalize so if you add more cells then all you have to do is just add it to the initialState & rest of your code should work as is.
function App() {
// const [cellState, setCellState] = useState("inactive");
const initialState = {
bar1: "inactive",
bar2: "inactive",
bar3: "inactive",
bar4: "inactive",
}
const [cellState, SetCellState] = React.useState(initialState);
const handleSetCellState = (key) => {
setCellState({
...cellState,
[key]: "active",
});
};
return (
<div className="App">
<div id="cube-container">
{Cube[0].faces[0].data.map((row) => {
return row.map((cell, index) => {
return (
<img
src={require(`./assets/cube/Square_Cube_Icon/${cell.bg}${
cellState[cell.bg] === "inactive" ? "_Unlit" : "_Partial"
}.png`)}
alt={`${cell.bg} background`}
className="cellItem"
onClick={() => handleSetCellState(cell.bg)}
state={cellState[cell.bg]}
key={index}
/>
);
});
})}
</div>
</div>
);
}
I've seen that this issue has been asked many times before but none of the answers make sense to me. After trying about every solution i could find, decided i'd just ask it myself.
Below is my code, the main issue being that in the DOM filteredData only changes on second click. Note, projs is the prop containing data fetched that gets filtered out and displayed
const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
const [projectData, setProjectData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [category, setCategory] = useState("");
useEffect(() => {
setProjectData(projs);
}, []);
const handleCategory = (res) => {
setCategory(res.data);
const filter = projectData.filter((data) => data.category === category);
setFilteredData(filter);
};
button:
{langs.map((data, index) => (
<button
key={index}
class="px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"
onClick={() => handleCategory({ data })}
>
{data}
</button>
))}
I'm pretty lost at the moment, any help would be greatly appreciated
You can't use the newly set value of a state in the same render cycle. To continue calling both setState calls in the same handler you need to use the category from res.data to generate the new filtered array and then set both.
const handleCategory = (res) => {
const filter = projectData.filter((data) => data.category === res.data);
setCategory(res.data);
setFilteredData(filter);
};
const { useState, useEffect } = React;
function App({ projs }) {
const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
const [projectData, setProjectData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [category, setCategory] = useState("");
useEffect(() => {
setProjectData(projs);
}, [projs]); // need to watch for change as props won't update this automatically. Setting state from props is somewhat of an anti-pattern
const handleCategory = (res) => {
const filter = projectData.filter((data) => data.category === res.data);
setCategory(res.data);
setFilteredData(filter);
};
return (
<div className="App">
{langs.map((data) => (
<button
key={data} // avoid using index as key as this will lead to erros in updating.
class={(data === category ? 'selected ' : '') + "px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"}
onClick={() => handleCategory({ data })}
>
{data}
</button>
))}
<div>
<h3>{category}</h3>
{filteredData.length
? filteredData.map(p => (
<div>
<p>{p.value} – {p.category}</p>
</div>
))
: <p>No projects match</p>}
</div>
</div>
);
}
const projectData = [{ id: 1, value: 'Project 1', category: 'react' }, { id: 2, value: 'Project 2', category: 'next js' }, { id: 3, value: 'Project 3', category: 'react' }, { id: 4, value: 'Project 4', category: 'material-ui' }]
ReactDOM.render(
<App projs={projectData} />,
document.getElementById("root")
);
.selected {
background-color: tomato;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Alternatively, update filteredData in a useEffect that watches for changes to either category or projectData
useEffect(() => {
setFilteredData(projectData.filter((data) => data.category === category));
}, [category, projectData]);
const handleCategory = (res) => {
setCategory(res.data);
};
const { useState, useEffect } = React;
function App({ projs }) {
const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
const [projectData, setProjectData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [category, setCategory] = useState("");
useEffect(() => {
setProjectData(projs);
}, [projs]); // need to watch for change as props won't update this automatically. Setting state from props is somewhat of an anti-pattern
useEffect(() => {
setFilteredData(projectData.filter((data) => data.category === category));
}, [category, projectData]);
const handleCategory = (res) => {
setCategory(res.data);
};
return (
<div className="App">
{langs.map((data) => (
<button
key={data}
class={(data === category ? 'selected ' : '') + "px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"}
onClick={() => handleCategory({ data })}
>
{data}
</button>
))}
<div>
<h3>{category}</h3>
{filteredData.length
? filteredData.map(p => (
<div>
<p>{p.value} – {p.category}</p>
</div>
))
: <p>No projects match</p>}
</div>
</div>
);
}
const projectData = [{ id: 1, value: 'Project 1', category: 'react' }, { id: 2, value: 'Project 2', category: 'next js' }, { id: 3, value: 'Project 3', category: 'react' }, { id: 4, value: 'Project 4', category: 'material-ui' }]
ReactDOM.render(
<App projs={projectData} />,
document.getElementById("root")
);
.selected {
background-color: tomato;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
As noted in the snippets –
Setting state from props is a bit of anti-pattern, but if you must you should add the prop to the dependency array so that state will be updated should the prop change. (prop changes don't force remounting).
Also, avoid using index as key, especially when mapping arrays whose order/membership will change as React will not update as expected.
I think this is the issue
setCategory(res.data);
const filter = projectData.filter((data) => data.category === category);
You invoke setCategory. But that won't change the immediate value of category that you reference in the subsequent line.
I think you want this:
const updatedCategory = res.data;
setCategory(updatedCategory);
const filter = projectData.filter((data) => data.category === updatedCategory);
Also, is res.data actually the "category" thing you want to use? I'm just asking because you use data between so many variables, parameters, and object members, I can't help but to wonder if it's really a typo.
Below is a basic example of the problem I'm experiencing.
I have a list of items that are in an array. The user can add another item to the list, and it shows on the page. Each item has a delete button and that delete button is a component inside the array item (this is so each item could have a button that does a different action.. in my example the action is deleting, but later it might be "Send" or "Delete" or "Cancel" or "Edit"...)
The trouble is, when I click the "Action" in this case delete, I want to know which item in the array this was. This way, I can get the array index and delete it. Or later grab additional details from the
import React, {useState} from "react"
function App() {
const [list, setList] = useState([])
function addRow(){
let newRow = {
name: "Test",
action: <span onClick={e=>removeThisRow()}>REMOVE</span>
}
setList([
...list,
newRow
])
}
function removeThisRow(){
// need this to remove the specific item from my list array...
console.log("removing...")
}
return (
<div>
{
list.map(item=>(
<div>
{item.name} | {item.action}
</div>
))
}
<div onClick={e=>addRow()}>ADD ROW</div>
</div>
);
}
export default App;
Working and tested
function App() {
const [list, setList] = React.useState([])
function addRow(){
let newRow = {
id: Math.random(),
name: `Test-${Math.random()}`,
action: (id) => <span onClick={()=>removeThisRow(id)}>REMOVE</span>
}
setList([
...list,
newRow
])
}
function removeThisRow(id){
setList(l => l.filter(li => li.id !== id))
}
return (
<div>
{
list.map((item)=>(
<div key={item.id}>
{item.name} | {item.action(item.id)}
</div>
))
}
<div onClick={e=>addRow()}>ADD ROW</div>
</div>
);
}
Id is just random number, I would use something more unique like uuid()
Here's an alternative approach to dynamically adding/editing/removing items from an array. Instead of unnecessarily creating JSX within an array, you can map over the array list and edit/remove the row based upon a unique id.
You can get even more sophisticated by adding two additional input toggles before adding a new row. These two toggles might add an isEditable and isRemovable properties to the row when when it's created. These properties can then be used to dynamically include or exclude buttons when the list is displayed. This is a cleaner approach as you're not recreating the same buttons over and over for each row, but flexible enough to conditionally render them.
On a related note, using the array index as a key is anti-pattern.
Demo Source Code:
Demo: https://q3wph.csb.app/
Code:
import * as React from "react";
import { v4 as uuid } from "uuid";
import Button from "./components/Button";
import Input from "./components/Input";
import Switch from "./components/Switch";
import "./styles.css";
export default function App() {
const [list, setList] = React.useState([]);
const [editRow, setEditRow] = React.useState({
id: "",
value: ""
});
const [newRowValue, setNewRowValue] = React.useState("");
const [newRowIsEditable, setNewRowEditable] = React.useState(true);
const [newRowIsRemovable, setNewRowRemovable] = React.useState(true);
const removeItem = (removeId) => {
setList((prevState) => prevState.filter(({ id }) => id !== removeId));
};
const editItem = (editId) => {
const { name } = list.find((item) => item.id === editId);
setEditRow({ id: editId, value: name });
};
const updateRowItem = ({ target: { value } }) => {
setEditRow((prevState) => ({ ...prevState, value }));
};
const handleRowUpdate = (e) => {
e.preventDefault();
const { id, value } = editRow;
if (!value) {
alert("Please fill out the row input before updating the row!");
return;
}
setList((prevState) =>
prevState.map((item) =>
item.id === id ? { ...item, name: value } : item
)
);
setEditRow({ id: "", value: "" });
};
const addNewRowItem = (e) => {
e.preventDefault();
if (!newRowValue) {
alert("Please fill out the new row item before submitting the form!");
return;
}
setList((prevState) => [
...prevState,
{
id: uuid(),
name: newRowValue,
isEditable: newRowIsEditable,
isRemovable: newRowIsRemovable
}
]);
setNewRowValue("");
setNewRowEditable(true);
setNewRowRemovable(true);
};
return (
<div className="app">
<h1>Dynamically Add/Edit/Remove Row</h1>
{list.length > 0 ? (
list.map(({ id, name, isEditable, isRemovable }) => (
<div className="uk-card uk-card-default uk-card-body" key={id}>
{editRow.id === id ? (
<form onSubmit={handleRowUpdate}>
<Input
placeholder="Add a new row..."
value={editRow.value}
handleChange={updateRowItem}
/>
<Button color="secondary" type="submit">
Update Row
</Button>
</form>
) : (
<>
<h2 className="uk-card-title">{name}</h2>
{isEditable && (
<Button
className="uk-margin-small-bottom"
color="primary"
type="button"
handleClick={() => editItem(id)}
>
Edit
</Button>
)}
{isRemovable && (
<Button
color="danger"
type="button"
handleClick={() => removeItem(id)}
>
Remove
</Button>
)}
</>
)}
</div>
))
) : (
<div>(Empty List)</div>
)}
<form
className="uk-card uk-card-default uk-card-body"
onSubmit={addNewRowItem}
>
<Input
placeholder="Add a new row..."
value={newRowValue}
handleChange={(e) => setNewRowValue(e.target.value)}
/>
<Switch
label="Editable"
handleChange={(e) => setNewRowEditable(Boolean(e.target.checked))}
name="Editable"
value={newRowIsEditable}
/>
<Switch
label="Removable"
handleChange={(e) => setNewRowRemovable(Boolean(e.target.checked))}
name="Removable"
value={newRowIsRemovable}
/>
<Button
className="uk-margin-small-bottom"
color="secondary"
type="submit"
>
Add Row
</Button>
<Button color="danger" type="button" handleClick={() => setList([])}>
Reset List
</Button>
</form>
</div>
);
}
I'm attaching an attribute to the span, named index and accessing that in removeThisRow.
import "./styles.css";
import React, { useState } from "react";
function App() {
const [list, setList] = useState([]);
function addRow() {
let newRow = {
name: "Test",
action: (
<span index={list.length} onClick={(e) => removeThisRow(e)}>
REMOVE
</span>
)
};
setList([...list, newRow]);
}
function removeThisRow(e) {
// need this to remove the specific item from my list array...
const index = e.target.getAttribute("index"); // got the index as a string
// Do whatever you want
}
return (
<div>
{list.map((item) => (
<div>
{item.name} | {item.action}
</div>
))}
<div onClick={(e) => addRow()}>ADD ROW</div>
</div>
);
}
export default App;
I have an array of objects, and I rendered the names from it. What I would like to do is when I click on them, I get the exact object that contains the name I clicked on, and then render those other data out.
export default function App() {
const array = [
{
id: 1,
name: 'John',
num: '0123'
},
{
id: 2,
name: 'Dave',
num: '456'
},
{
id: 3,
name: 'Bruce',
num: '789'
},
]
const handleClick = (e) => {
console.log(e.target)
}
return (
<div className="App">
{array.map((item, index) => {
return (
<div key={index}>
<p onClick={handleClick}>{item.name}</p>
</div>
)
})}
</div>
);
}
You need:
add to function handleSelect(userID) with argument userID:
const handleSelect = (userId) => {
const currentUser = array.filter(item => item.id === userId)[0]
console.log(currentUser) // or return
}
Add onClick event to div or p
<div className="App">
{array.map((item, index) => {
return (
<div key={index}>
<p onClick={() => handleSelect(item.id)}>{item.name}</p>
</div>
)
})}
</div>
Give some data-* attribute to the clicked element.
Find the item by id.
Do not use the index as a key in React. Use unique id.
Wrap functions inside React.useCallback.
export default function App() {
const array = [
{
id: 1,
name: 'John',
num: '0123'
},
{
id: 2,
name: 'Dave',
num: '456'
},
{
id: 3,
name: 'Bruce',
num: '789'
},
]
const handleClick = React.useCallback((e) => {
const clickedId = e.target.getAttribute('data-id');
const item = array.find(({ id }) => id === clickedId);
console.log(item);
}, []);
return (
<div className="App">
{array.map((item) => {
return (
<div key={item.id}>
<p data-id={item.id} onClick={handleClick}>{item.name}</p>
</div>
)
})}
</div>
);
}
This would do:
const handleClick = (id, name, num) => {
console.log(`id=${id}, name=${name}, num=${num} `);
};
return (
<div className="App">
{array.map((item, index) => {
return (
<div key={index}>
<p onClick={()=>handleClick(item.id, item.name, item.num)}>{item.name}</p>
</div>
);
})}
</div>
);
Here is a codesandbox link: for the above
Ofcourse, you can try to re-access the object. Check this out:
const handleClick = (id, name, num) => {
console.log(`Easy way : id=${id}, name=${name}, num=${num} `);
//Re-access the object. But why?
const desiredObjArr = array.filter(obj=> obj.id=id);
console.log(`I get this in a tough way: ${desiredObjArr[0].name}, ${desiredObjArr[0].id}, ${desiredObjArr[0].num}`);
};
The temptation is to treat the React onClick as if it was the same as an onclick attribute in an html tag. But while in the latter you would then call the function you want to execute along with it's argument (say, like so: <p onclick="handleClick(item)", in the former you wrap the function you are calling in another anonymous function. Following those strictures, to make your own code work as you want it to, you only have to revise it slightly like so:
const handleClick = (item) => {
console.log(item.id, item.name, item.num)
}
return (
<div className="App">
{array.map((item, index) => {
return (
<div key={index}>
<p onClick={()=>{handleClick(item)}}>{item.name}</p>
</div>
)
})}
</div>
);
Here's a working Stackblitz: https://stackblitz.com/edit/react-6ywm5v