Why this does not remove item from the list but only for console.log? As you can see i update list in the function that i assign later to the button.
let DATA = [
{
id: 1,
name: "item1"
},
{
id: 2,
name: "item2"
}
];
const App = () => {
const deleteItem = (id) => {
DATA = DATA.filter((item) => item.id !== id);
console.log(DATA);
};
return (
<div className="App">
{DATA.map((item) => (
<p key={item.id} onClick={() => deleteItem(item.id)}>
{item.name}
</p>
))}
</div>
);
}
const root = ReactDOM.createRoot(
document.getElementById("root")
).render(<App/>);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
It does remove the item from the list. But there's nothing telling the component to re-render the UI.
What you're looking for is state in React. Updating state triggers a component re-render:
const App = () => {
// store the data in state
const [data, setData] = React.useState([
{
id: 1,
name: "item1"
},
{
id: 2,
name: "item2"
}
]);
const deleteItem = (id) => {
// update state with the new data
setData(data.filter((item) => item.id !== id));
};
return (
<div className="App">
{data.map((item) => (
<p key={item.id} onClick={() => deleteItem(item.id)}>
{item.name}
</p>
))}
</div>
);
}
const root = ReactDOM.createRoot(
document.getElementById("root")
).render(<App/>);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
It deletes it but react does not re-render. You have to use a setState method to trigger the re-render, you could put a copy of DATA in a state variable.
Related
I'm displaying different cars and a button to add or remove the selections the user has made. How do I get the buttons to change state individually? As of now, it changes the state of all the buttons to one value.
const cars = [
{ name: "Benz", selected: false },
{ name: "Jeep", selected: false },
{ name: "BMW", selected: false }
];
export default function App() {
const isJeepSelected = true;
const isBenzSelected = true;
return (
<div className="App">
{cars.map((values, index) => (
<div key={index}>
<Item
isBenzSelected={isBenzSelected}
isJeepSelected={isJeepSelected}
{...values}
/>
</div>
))}
</div>
);
}
const Item = ({ name, isBenzSelected, isJeepSelected }) => {
const [toggle, setToggle] = useState(false);
const handleChange = () => {
setToggle(!toggle);
};
if (isBenzSelected) {
cars.find((val) => val.name === "Benz").selected = true;
}
console.log("cars --> ", cars);
console.log("isBenzSelected ", isBenzSelected);
console.log("isJeepSelected ", isJeepSelected);
return (
<>
<span>{name}</span>
<span>
<button onClick={handleChange}>
{!toggle && !isBenzSelected ? "Add" : "Remove"}
</button>
</span>
</>
);
};
I created a working example using Code Sandbox. Could anyone please help?
There's too much hardcoding here. What if you had 300 cars? You'd have to write 300 boolean useState hook calls, and it still wouldn't be dynamic if you had an arbitrary API payload (the usual case).
Try to think about how to generalize your logic rather than hardcoding values like "Benz" and Jeep. Those concepts are too closely-tied to the arbitrary data contents.
cars seems like it should be state since you're mutating it from React.
Here's an alternate approach:
const App = () => {
const [cars, setCars] = React.useState([
{name: "Benz", selected: false},
{name: "Jeep", selected: false},
{name: "BMW", selected: false},
]);
const handleSelect = i => {
setCars(prevCars => prevCars.map((e, j) =>
({...e, selected: i === j ? !e.selected : e.selected})
));
};
return (
<div className="App">
{cars.map((e, i) => (
<div key={e.name}>
<Item {...e} handleSelect={() => handleSelect(i)} />
</div>
))}
</div>
);
};
const Item = ({name, selected, handleSelect}) => (
<React.Fragment>
<span>{name}</span>
<span>
<button onClick={handleSelect}>
{selected ? "Remove" : "Add"}
</button>
</span>
</React.Fragment>
);
ReactDOM.createRoot(document.querySelector("#app"))
.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
Consider generating unique ids for elements rather than using indices or assuming the name is unique. crypto.randomUUID() is a handy way to do this.
I'm passing an array of objects to a child as props, and I wanted the child component to re-render when the aray changes, but it doesn't:
parent:
export default function App() {
const [items, setItems] = useState([]);
const buttonCallback = () => {
setItems([...items, { text: "new item" }]);
};
return (
<div className="App">
<h1>Items list should update when I add item</h1>
<button onClick={buttonCallback}>Add Item</button>
<Items />
</div>
);
}
child:
const Items = ({ itemlist = [] }) => {
useEffect(() => {
console.log("Items changed!"); // This gets called, so the props is changing
}, [itemlist]);
return (
<div className="items-column">
{itemlist?.length
? itemlist.map((item, i) => <Item key={i} text={item.text + i} />)
: "No items"}
<br />
{`Itemlist size: ${itemlist.length}`}
</div>
);
};
I found this question with the same issue, but it's for class components, so the solution doesn't apply to my case.
Sandbox demo
<Items propsname={data} />
const buttonCallback = () => {
setItems([...items, { text: "new item" }]);
};
but you should put it as:
const buttonCallback = () => {
setItems([...items, { text: "new item", id: Date.now() }]);
};
Because is not recommended to use index as a key for react children. Instead, you can use the actual date with that function. That is the key for React to know the children has changed.
itemlist.map((item) => <Item key={item.id} text={item.text} />)
Try below:
you are adding array as a dependency, to recognise change in variable, you should do deep copy or any other thing which will tell that useEffect obj is really different.
`
const Items = ({ itemlist = [] }) => {
const [someState,someState]=useState(itemlist);
useEffect(() => {
someState(itemlist)
}, [JSON.stringify(itemlist)]);
return (
<div className="items-column">
{someState?.length
? someState.map((item, i) => <Item key={i} text={item.text
+ i}
/>)
: "No items"}
<br />
{`Itemlist size: ${someState.length}`}
</div>
);
};
With the code below:
<!DOCTYPE html>
<html>
<body>
<head>
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<style>
</style>
</head>
<body>
<div id="root">
</body>
<script type="text/babel">
const api = {
data: [
{
id: 1, party: 'Zuckerberg', news: [
{id: 1, headline: 'Zuckerberg news1'},
{id: 2, headline: 'Zuckerberg news2'},
]
},
{
id: 2, party: 'Musk', news: [
{id: 1, headline: 'Musk news1'},
{id: 2, headline: 'Musk news2'},
]
},
]
}
const App = () => {
const [data, setData] = React.useState([])
React.useEffect(() => setData(api.data), [])
const handleSelectChange = (e) => {
const name = e.target.value
setData(prev => prev.filter(datum => datum.party === name))
}
return (
<div>
<select
onChange={handleSelectChange}>
{data.map(datum => <option key={datum.id}>{datum.party}</option>)}
</select>
{data.map(
datum =>
datum.news.map(newsItem =>
<div key={newsItem.id}>{newsItem.headline}</div>
)
)}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
</script>
</html>
how can I prevent the reduction of dropdown options upon dropdown change, preferably without changing the api.data structure?
I basically need this dropdown to be refreshed only when api.data returns new data.
Is it possibly while keeping only single state (data)?
One way to handle this scenario by define state as an object like I mentioned in my comment. This might help you. It's working for me.
const MyComponent = () => {
const [data, setData] = React.useState({})
React.useEffect(() => setData({dropdownValues: api.data, filteredValues: api.data}), [])
const handleSelectChange = (e) => {
const name = e.target.value
const filteredValues = data.dropdownValues.filter(datum => datum.party === name);
setData({...data, filteredValues})
}
return (
<div>
<select
onChange={handleSelectChange}>
{data.dropdownValues && data.dropdownValues.map(datum => <option key={datum.id}>{datum.party}</option>)}
</select>
{data.filteredValues && data.filteredValues.map(
datum =>
datum.news.map(newsItem =>
<div key={newsItem.id}>{newsItem.headline}</div>
)
)}
</div>
)
}
I can't think of a way to do it without using another state variable. What's the reason for only wanting one variable?
You could use an object state variable as Qubaish said but that can be much more difficult to maintain than just using a second state variable.
If you can get away with using two state variables use the following:
const App = () => {
const [data, setData] = React.useState([]);
const [options, setOptions] = useState([]);
React.useEffect(() => {
setData(api.data);
setOptions(api.data);
}, []);
const handleSelectChange = (e) => {
let name = e.target.value;
setData(options.filter((datum) => datum.party === name));
};
return (
<div>
<select onChange={handleSelectChange}>
{options.map((datum) => (
<option key={datum.id}>{datum.party}</option>
))}
</select>
{data.map((datum) =>
datum.news.map((newsItem) => (
<div key={newsItem.id}>{newsItem.headline}</div>
))
)}
</div>
);
};
It just stores a copy of the potential options in the options state and then stores the data to be displayed in "data".
I've modified your code below.
Main points:
removed useEffect - the API is static so doesn't need to be re-allocated
useState only monitors the filter (filtering of the API data only happens when the state changes so there isn't a need to store this - but you could useMemo if you wanted and make it dependent on the data)
The options are derived from api.data
<!DOCTYPE html>
<html>
<body>
<head>
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<style>
</style>
</head>
<body>
<div id="root">
</body>
<script type="text/babel">
const api = {
data: [
{
id: 1, party: 'Zuckerberg', news: [
{id: 1, headline: 'Zuckerberg news1'},
{id: 2, headline: 'Zuckerberg news2'},
]
},
{
id: 2, party: 'Musk', news: [
{id: 1, headline: 'Musk news1'},
{id: 2, headline: 'Musk news2'},
]
},
]
}
const App = () => {
const [data, setData] = React.useState(api.data.length?api.data[0].party:undefined)
const handleSelectChange = (e) => {
setData(e.target.value)
}
return (
<div>
<select
onChange={handleSelectChange}>
{api.data.map(datum => <option key={datum.id}>{datum.party}</option>)}
</select>
{api.data.filter(datum=>datum.party===data).map(
datum =>
datum.news.map(newsItem =>
<div key={newsItem.id}>{newsItem.headline}</div>
)
)}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
</script>
</html>
My App.js have this structure.
return (
<Container fluid="true" className="h-100">
<Header />
<Row className="contentRow">
<CustomerList />
<DetailPage />
</Row>
</Container>
);
There are many elements in CustomerList. With a click I want to send the ID of the element to DetailPage and display the details of the associated element. But I am still quite new in react and don't really know how to pass the data. Or if I even need to change something in the structure of the components.
You need to define a new state variable in your component.
Then please pass it with the setter function into CustomerList.
Define state variable.
const [id, setId] = useState(null);
Then pass setter function into <CustomerList />
<CustomerList setId={setId} />
// on CustomerList click event
const onClick = (event) => {
// your logic and use setId from props.
// This is just an example.
props.setId(event.target.value);
}
Finally, pass id state variable into <DetailPage /> so that your DetailPage component uses in its props.
<DetailPage id={id} />
Usage in Detailpage:
const DetailPage = (props) => {
const id = props.id;
// use id for your purpose.
}
You can use the event.target.id property. add an onclick function:
onClick={(e) => handleClick(e)}
handleClick = (e) => {
//access e.target.id here
}
See if this help you:
import React, { useState } from "react";
const Header = () => <div />;
const CustomerList = ({ onChange }) => (
<ul>
{["item1", "item2", "item3", "item4"].map((item) => (
<li onClick={() => onChange(item)} key={item}>
{item}
</li>
))}
</ul>
);
const DetailPage = ({ selectedItem }) => <div>{selectedItem}</div>;
const Component = () => {
const [selectedItem, setSelectedItem] = useState(null);
const handleChange = (item) => {
setSelectedItem(item);
};
return (
<div> {/* Container */}
<Header />
<div className="contentRow"> {/* Row */}
<CustomerList onChange={handleChange} />
<DetailPage selectedItem={selectedItem} />
</div>
</div>
);
};
When you click some item, we set the state in parent component, and then send to DetailPage, in DetailPage, you can use this selectedItem to show the info.You also can replace ["item1", "item2", "item3", "item4"] with an array of objects.
App.js
import "./styles.css";
import React, { useState } from "react";
import CustomersList from "./CustomersList";
import { DetailPage } from "./DetailPage";
export default function App() {
const [listOfElements, setListOfElements] = useState([
{ name: "abc", id: "0" },
{ name: "def", id: "1" },
{ name: "ghi", id: "2" },
{ name: "jkl", id: "3" },
{ name: "mno", id: "4" }
]);
const [selectedId, setSelectedId] = useState(1);
const [customerDEatiledinfo, setCuatomerDetailedInfo] = useState({
name: "sample"
});
const idSelectedHandler = (id) => {
const idd = +id;
const newState = listOfElements[idd];
setCuatomerDetailedInfo(newState);
};
return (
<div className="App">
<CustomersList customers={listOfElements} selectId={idSelectedHandler} />
<DetailPage customer={customerDEatiledinfo} />
</div>
);
}
CustomersList.js
export const CustomersList = (props) => {
const onClickHandler = (id) => {
props.selectId(id);
};
return (
<div>
{props.customers.map((customer) => {
return (
<div key={customer.id} onClick={()=>onClickHandler(customer.id)}>
{customer.name}
</div>
);
})}
</div>
);
};
export default CustomersList;
DeatilPage.js
export const DetailPage = (props) => {
return <div style={{ color: "blue" }}>
<br/>
DetailPage
<p>{props.customer.name}</p></div>;
};
I'm not sure how to ask this question, because I'm still unable to accurately frame the problem.
I've created a useHover function. Below, you'll see that I am mapping over data and rendering a bunch of photos. However, the useHover only works on the first iteration.
I suspect that it's because of my ref. How does this work? Should I creating a new ref inside of each iteration -- or is that erroneous thinking..?
How can I do this?
Here's my useHover function.
const useHover = () => {
const ref = useRef();
const [hovered, setHovered] = useState(false);
const enter = () => setHovered(true);
const leave = () => setHovered(false);
useEffect(() => {
ref.current.addEventListener("mouseenter", enter);
ref.current.addEventListener("mouseleave", leave);
return () => {
ref.current.removeEventListener("mouseenter", enter);
ref.current.removeEventListener("mouseleave", leave);
};
}, [ref]);
return [ref, hovered];
};
And here's my map function. As you can see I've assigned the ref to the image.
The problem: Only one of the images works when hovered.
const [ref, hovered] = useHover();
return (
<Wrapper>
<Styles className="row">
<Div className="col-xs-4">
{data.map(item => (
<div className="row imageSpace">
{hovered && <h1>{item.fields.name}</h1>}
<img
ref={ref}
className="image"
key={item.sys.id}
alt="fall"
src={item.fields.image.file.url}
/>
</div>
))}
</Div>
I'd handle this by using CSS if at all possible, rather than handling hovering in my JavaScript code.
If doing it in JavaScript code, I'd handle this by creating a component for the things that are hovered:
function MyImage({src, header}) {
const [ref, hovered] = useHover();
return (
<div className="row imageSpace">
{hovered && <h1>{header}</h1>}
<img
ref={ref}
className="image"
alt="fall"
src={src}
/>
</div>
);
}
and then use that component:
return (
<Wrapper>
<Styles className="row">
<Div className="col-xs-4">
{data.map(item =>
<MyImage
key={item.sys.id}
src={item.fields.image.file.url}
header={item.fields.name}
/>
)}
</Div>
(Obviously, make more of the props configurable if you like.)
As a general rule when you have a parent item with Array.map(), and functionality for each array item, refactor the items to a separate component (ImageRow in my code).
In this case you don't need to use refs for event handling, since React can handle that for you. Instead of return a ref from useHover, return an object with event handlers, and spread it on the component.
const { useState, useMemo } = React;
const useHover = () => {
const [hovered, setHovered] = useState(false);
const eventHandlers = useMemo(() => ({
onMouseEnter: () => setHovered(true),
onMouseLeave: () => setHovered(false)
}), [setHovered]);
return [hovered, eventHandlers];
};
const ImageRow = ({ name, url }) => {
const [hovered, eventHandlers] = useHover();
return (
<div className="row imageSpace">
{hovered && <h1>{name}</h1>}
<img
className="image"
alt="fall"
src={url}
{...eventHandlers}
/>
</div>
);
};
const images = [{ id: 1, name: 'random1', url: 'https://picsum.photos/200?1' }, { id: 2, name: 'random2', url: 'https://picsum.photos/200?2' }, { id: 3, name: 'random3', url: 'https://picsum.photos/200?3' }];
const Wrapper = ({ images }) => (
<div style={{ display: 'flex' }}>
{images.map(({ id, ...props }) => <ImageRow key={id} {...props} />)}
</div>
);
ReactDOM.render(
<Wrapper images={images} />,
root
);
h1 {
position: absolute;
pointer-events: none;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>