I have a problem in this code. I need reset item array in line but don't work.
if (!(element.name in filter){setItem([])}
import React, { useState, useEffect, cloneElement } from 'react';
import {fetchData} from '../helpers/fetchData';
function MainElement() {
const [data, setData] = useState([]);
const [filter, setFilter] = useState({});
const [item, setItem] = useState([]);
useEffect( () => {
fetchData("http://localhost/cataleg/php/p3filtres.php")
.then( category => {
setData(category);
});
}, [])
return (
<div onChange={
(e) => {
const product = document.querySelectorAll('input[type="checkbox"]:checked');
[...product].map((element) => {
if (!(element.name in filter){
setItem([]);
}else
setItem([...item, element.value])
return filter[element.name]=item;
})
}
}
>
{
data && Object.entries(data).map((element,i) => {
return (
<div key={i}>
<h3 key={element[0]}>{element[0]}</h3>
{
element[1].map(item => {
return (
Object.entries(item).map((value,i) => {
if (i%2)
return <div key={i}>
<input type="checkbox" key={value[1]} name={element[0]} value={value[1]}/>
<label key={`${value[1]}-${i}`} htmlFor={value[1]}>{value[1]}</label>
</div>
})
)
})
}
</div>
)
})
}
</div>
)
}
export default MainElement;
enter image description here
When ckeckbox is checked I want to store in an object like:
{'processor': [I7,I5], 'RAM':['4GB','8GB']}
So I need reset item array for each key in the object. In may code item array is [I7,I5,'4GB','8GB']
There is a list of things worth fixing in your codes.
onChange has no effect on div
document.querySelector and document.querySelectorAll should not be used inside a React component
Include the <input> checkboxes as part of your app
See useRef if you must reference other elements
.map is being used like .forEach
setItem([...item, element.value]) is called for every element regardless of your condition
filtre is misspelled
item is derived state that should not have its own state
Use data.filter to compute item
See Single Source of Truth
Here is a minimal complete example you can run in your browser.
function fetchProducts() {
return new Promise(resolve =>
setTimeout(resolve, 1000, [
{"name": "carrot", type: "vegetable", quantity: 6 },
{"name": "potato", type: "vegetable", quantity: 0 },
{"name": "pretzels", type: "snack", quantity: 3 }
]))
}
function App() {
const [products, setProducts] = React.useState([])
const [isVegetable, veggieFilter] = useCheckbox(false)
const [isStocked, stockFilter] = useCheckbox(false)
React.useEffect(_ => {
fetchProducts().then(setProducts).catch(console.error)
}, [])
function filter() {
return products
.filter(p => !isVegetable || p.type == "vegetable")
.filter(p => !isStocked || p.quantity > 0)
}
return <div>
Vegetable:{veggieFilter} In-Stock:{stockFilter}
{filter().map(p => <pre>{JSON.stringify(p)}</pre>)}
</div>
}
function useCheckbox(initValue = false) {
const [checked, setChecked] = React.useState(initValue)
return [
checked,
<input type="checkbox" checked={checked} onClick={_ => setChecked(!checked)} />
]
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Related
This is the code for the interface:
export interface ActorAttributes {
TYPE?: string,
NAME?: string,
}
export interface MovieAttributes {
OBJECTID: number,
SID: string,
NAME: string,
DIRECTOR: string,
DESCRIP: string,
}
App.tsx code:
import { useEffect, useState } from 'react';
import { searchMovies, searchActors, MovieAttributes, ActorAttributes } from "#utils/atts"
const Home: React.FC = () => {
const [search, setSearch] = useState(false);
const [movieSearch, setMovieSearch] = useState<MovieAttributes[]>([]);
const [actorSearch, setActorSearch] = useState<ActorAttributes>([]);
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
if (searchTerm.length > 0) {
setSearch(true);
searchMovies(searchTerm).then(results => {
setMovieSearch(results);
});
searchActors(searchTerm, movieSearch[1].SID).then(results => {
setActorSearch(results);
});
setSearch(false);
}
}, [searchTerm, movieSearch]);
const handleSearchTermChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
}
return (
<div>
<input type="text" value={searchTerm} onChange={handleSearchTermChange} />
{search && <p>Searching...</p>}
{movieSearch.length > 0 && <p>Found {movieSearch.length} movies</p>}
{actorSearch.length > 0 && <p>Found {actorSearch.length} actors</p>}
</div>
);
}
So when a user searches a movie name or actor it's able to display the amount of movies and actors found but I'm curious on how I would display the data attributes (i.e. NAME, DIRECTOR, DESCRIP, etc.)
This is what I've tried so far, but it just displays errors stating that the movieAttributes doesn't contain toLowerCase() and I'm not quiet sure where to go from here or if I'm on the right track. I apologize in advance for the errors within my code as I am fairly new to react. If anyone has any tips, ideas, suggestions, etc. please feel free to leave a comment.
<div className="App">
<ul className="posts">
<input type="text" onChange={handleSearchTermChange} />
{movieSearch.map((movieSearch) => {
if (searchTerm == "" || movieSearch.toLowerCase().includes(searchTerm.toLowerCase())) {
return (
<li key={movieSearch.OBJECTID}>
<h3>{movieSearch.NAME}</h3>
<p>{movieSearch.DIRECTOR}</p>
<p>{movieSearch.DESCRIP}</p>
</li>
);
}
return null;
)}
</ul>
/div>
There are some problems in your code.
actorSearch state type should be ActorAttributes[].
You should remove movieSearch from useEffect dependencies. Because each time the state gets updated, the useEffect gets executed but it's wrong.
You're using movieSearch right after updating its value. It's wrong because set state functions in react are async. Instead, you should use another variable to hold the new movieSearch data in useEffect.
In your .map function, you're using the same name as your state name (movieSearch) which is wrong.
You're executing toLowerCase() on the MovieAttributes object but instead, you should do it on NAME property.
So hope this helps you:
import { useEffect, useState } from 'react';
import { searchMovies, searchActors, MovieAttributes, ActorAttributes } from "#utils/atts"
const Home: React.FC = () => {
const [search, setSearch] = useState(false);
const [movieSearch, setMovieSearch] = useState<MovieAttributes[]>([]);
const [actorSearch, setActorSearch] = useState<ActorAttributes[]>([]);
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
if (searchTerm.length > 0) {
setSearch(true);
let newMovieSearch = [...movieSearch];
searchMovies(searchTerm).then(results => {
newMovieSearch = results;
setMovieSearch(results);
});
searchActors(searchTerm, newMovieSearch[1].SID).then(results => {
setActorSearch(results);
});
setSearch(false);
}
}, [searchTerm]);
const handleSearchTermChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
}
return (
<div>
<input type="text" value={searchTerm} onChange={handleSearchTermChange} />
{search && <p>Searching...</p>}
{movieSearch.length > 0 && <p>Found {movieSearch.length} movies</p>}
{actorSearch.length > 0 && <p>Found {actorSearch.length} actors</p>}
</div>
);
}
<div className="App">
<ul className="posts">
<input type="text" onChange={handleSearchTermChange} />
{movieSearch.map((movie) => {
if (searchTerm == "" || movie.NAME.toLowerCase().includes(searchTerm.toLowerCase())) {
return (
<li key={movie.OBJECTID}>
<h3>{movie.NAME}</h3>
<p>{movie.DIRECTOR}</p>
<p>{movie.DESCRIP}</p>
</li>
);
}
return null;
)}
</ul>
</div>
I have a sample where I have a list of answers and a paragraph that has gaps.
I can drag the answers from the list to the paragraph gaps. After the answer fills the gap, the answer will be removed from the list. 3 answers and 3 gaps, the answer list should be empty when i drag all of them to the gaps.
But whenever I filter the listData, the component re-renders and the listData gets reset. The list always remained 2 items no matter how hard I tried. What was wrong here?
My code as below, I also attached the code sandbox link, please have a look
App.js
import GapDropper from "./gapDropper";
import "./styles.css";
const config = {
id: "4",
sort: 3,
type: "gapDropper",
options: [
{
id: "from:drop_gap_1",
value: "hello"
},
{
id: "from:drop_gap_2",
value: "person"
},
{
id: "from:drop_gap_3",
value: "universe"
}
],
content: `<p>This is a paragraph. It is editable. Try to change this text. <input id="drop_gap_1" type="text"/> . The girl is beautiful <input id="drop_gap_2" type="text"/> I can resist her charm. Girl, tell me how <input id="drop_gap_3" type="text"/></p>`
};
export default function App() {
return (
<div className="App">
<GapDropper data={config} />
</div>
);
}
gapDropper.js
import { useEffect, useState } from "react";
import * as _ from "lodash";
import styles from "./gapDropper.module.css";
const DATA_KEY = "answerItem";
function HtmlViewer({ rawHtml }) {
return (
<div>
<div dangerouslySetInnerHTML={{ __html: rawHtml }} />
</div>
);
}
function AnwserList({ data }) {
function onDragStart(event, data) {
event.dataTransfer.setData(DATA_KEY, JSON.stringify(data));
}
return (
<div className={styles.dragOptionsWrapper}>
{data.map((item) => {
return (
<div
key={item.id}
className={styles.dragOption}
draggable
onDragStart={(event) => onDragStart(event, item)}
>
{item.value}
</div>
);
})}
</div>
);
}
function Content({ data, onAfterGapFilled }) {
const onDragOver = (event) => {
event.preventDefault();
};
const onDrop = (event) => {
const draggedData = event.dataTransfer.getData(DATA_KEY);
const gapElement = document.getElementById(event.target.id);
const objData = JSON.parse(draggedData);
gapElement.value = objData.value;
onAfterGapFilled(objData.id);
};
function attachOnChangeEventToGapElements() {
document.querySelectorAll("[id*='drop_gap']").forEach((element) => {
element.ondragover = onDragOver;
element.ondrop = onDrop;
});
}
useEffect(() => {
attachOnChangeEventToGapElements();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div>
<HtmlViewer rawHtml={data} />
</div>
);
}
const GapDropper = ({ data }) => {
const [gaps, setGaps] = useState(() => {
return data.options;
});
function onAfterGapFilled(id) {
let clonedGaps = _.cloneDeep(gaps);
clonedGaps = clonedGaps.filter((g) => g.id !== id);
setGaps(clonedGaps);
}
return (
<div>
<div>
<AnwserList data={gaps} />
<Content
data={data.content}
onAfterGapFilled={(e) => onAfterGapFilled(e)}
/>
</div>
</div>
);
};
export default GapDropper;
Code sandbox
the problem is that you are not keeping on track which ids you already selected, so thats why the first time it goes right, and then the second one, the values just replace the last id.
Without changing a lot of your code, we can accomplish by tracking the ids inside a ref.
const GapDropper = ({ data }) => {
const [gaps, setGaps] = useState(() => {
return data.options;
});
const ids = useRef([])
function onAfterGapFilled(id) {
let clonedGaps = _.cloneDeep(gaps);
ids.current = [...ids.current, id]
clonedGaps = clonedGaps.filter((g) => !ids.current.includes(g.id));
setGaps(clonedGaps);
}
return (
<div>
<div>
<AnwserList data={gaps} />
<Content
data={data.content}
onAfterGapFilled={(e) => onAfterGapFilled(e)}
/>
</div>
</div>
);
};
Maybe there is a better solution but this one does the job
I'm basically trying to call a function (getValue) from a class (Time) in a different file, but there is some issues.
Here is the code for the two files:
Time.js
export default class Time extends Component {
constructor(props) {
super(props);
this.state = {
input: '',
input2: '',
checked: false
}
this.getValue = this.getValue.bind(this);
}
hrChange = e => {
this.setState({input: e.target.value}, function () {this.getValue()})
}
minChange = e => {
this.setState({input2: e.target.value}, function () {this.getValue()})
}
amPm = () => {
this.setState({checked: !this.state.checked}, function () {this.getValue()})
}
getValue = () => {
const list = [
this.state.input,
this.state.input2,
this.state.checked
]
return (list)
}
render() {
return(
<text>some stuff</text>
)
}
}
NewStorage.js
function NewStorage() {
const time = () => {
var obj = new Time();
var list = obj.getValue()
const
hrInput = list[0],
minInput = list[1],
pm = list[2]
return(
console.log(hrInput, minInput, pm, list)
)
return(
time()
)
}
export default NewLocalStorage;
The main issue isn't that I can't call the function, it is that when I call the function, the values of input, input2, and checked are all the original value ('', '', false), not the updated versions (ex: '11', '30', true).
I'm not sure on how to solve this issue.
Your inclusion of the react-hooks tag suggest your hunch that hooks are applicable to solving your problem. I would agree -
const { useState, useEffect } = React
function Time ({ hour, minute, onChange }) {
const [h,setHour] = useState(hour)
const [m,setMinute] = useState(minute)
useEffect(_ => onChange({ hour: h, minute: m }), [h, m])
return <div>
<input value={h} onChange={event => setHour(event.target.value)} />
<input value={m} onChange={event => setMinute(event.target.value)} />
</div>
}
ReactDOM.render(<Time onChange={console.log} />, document.querySelector("main"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<main></main>
In a more sophisticated example, we can use the Time component's onChange callback to update nested state in a parent component, MyForm -
const { useState, useEffect, useCallback } = React
function Time ({ hour = 0, minute = 0, onChange }) {
const [h,setHour] = useState(hour)
const [m,setMinute] = useState(minute)
useEffect(_ => onChange({ hour: h, minute: m }), [h, m, onChange])
return <div>
<input value={h} onChange={event => setHour(event.target.value)} />
<input value={m} onChange={event => setMinute(event.target.value)} />
</div>
}
function MyForm () {
const [data, setData] = useState({ time: { hour: 5, minute: 30 }, foo: "bar" })
const onTimeChange = useCallback(t => setData({ ...data, time: t }), [])
return <form>
<Time hour={data.time.hour} minute={data.time.minute} onChange={onTimeChange} />
<pre>{JSON.stringify(data, null, 2)}</pre>
</form>
}
ReactDOM.render(<MyForm />, document.querySelector("main"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<main></main>
Instead of trying to create a class and call the function in another file, why not use React functional components and hooks?
Try something like this:
const Clock = () => {
const [hour, setHour] = useState();
const [min, setMin] = useState();
const [am, setAm] = useState(true);
useEffect(() => {
// Get your clock to work in here...
}, [hour, min, am]);
return (
<div>
{//This will post your clock here, and if you need the values, you
can set/use them individually as needed.}
{hour}:{min} {am ? 'am' : 'pm'}
{//The ternary statement will modify this portion for you in code.}
</div>
);
}
If you want to use the values globally, you may want to try using the React hook useContext(). This will allow you to access those specific values anywhere you want, but requires a bit more setup.
Context, if you don't know will turn your react app into Redux, without using Redux. Below is an example of what you need to do.
import { createContext } from "react";
export const QuizContext = createContext();
then you add the context to your App.js:
import { useState } from 'react';
import './App.css';
import MainMenu from './Components/MainMenu';
import Quiz from './Components/Quiz';
import EndScreen from './Components/EndScreen';
import { QuizContext } from './Helpers/Context';
function App() {
const [gameState, setGameState] = useState('Menu');
const [score, setScore] = useState(0);
return (
<div className="App">
<h1>Quiz App</h1>
<QuizContext.Provider value={{gameState, setGameState, score, setScore}}>
{gameState === 'Menu' && <MainMenu/>}
{gameState === 'Quiz' && <Quiz/>}
{gameState === 'EndScreen' && <EndScreen/>}
</QuizContext.Provider>
</div>
);
}
Then you can access the context from individual components as long as they are children of App.
Example:
import React, { useContext, useState } from 'react';
import { QuizContext } from '../Helpers/Context';
import {Questions} from '../Helpers/QuestionBank'
const Quiz = () => {
const [currentQuestion, setCurrentQuestion] = useState(0)
const [optionChosen, setOptionChosen] = useState('');
const {setGameState, score, setScore} = useContext(QuizContext);
const nextQuestion = () => {
Questions[currentQuestion].answer === optionChosen ? setScore(score + 1) : console.log(score);
setCurrentQuestion(currentQuestion + 1);
}
const finishQuiz = () => {
Questions[currentQuestion].answer === optionChosen ? setScore(score + 1) : console.log(score);
setGameState('EndScreen');
}
return (
<div className="Quiz">
<h1>{Questions[currentQuestion].prompt}</h1>
<div className="options">
<button onClick={() => setOptionChosen('optionA')}>{Questions[currentQuestion].optionA}</button>
<button onClick={() => setOptionChosen('optionB')}>{Questions[currentQuestion].optionB}</button>
<button onClick={() => setOptionChosen('optionC')}>{Questions[currentQuestion].optionC}</button>
<button onClick={() => setOptionChosen('optionD')}>{Questions[currentQuestion].optionD}</button>
</div>
{currentQuestion === Questions.length -1 ? <button onClick={finishQuiz}>Finish Quiz</button> : <button onClick={nextQuestion}>Next Question</button>}
</div>
)
}
export default Quiz
I learned this method from a Tutorial from PedroTech on YouTube. I followed along to create this. I wanted to make sure I didn't take credit for his work.
I have a very basic application to test how to prevent unnecessary rendering, but I'm very confused as it is not working no matter what I try. Please take a look.
App.js
import { useState, useCallback } from "react";
import User from "./User";
let lastId = 0;
function App() {
const [users, setUsers] = useState([
{ id: 0, name: "Nicole Kidman", gender: "Female" },
]);
const handleUserChange = useCallback(
(e, userId) => {
const { name, value } = e.target;
const newUsers = [...users];
const index = newUsers.findIndex((user) => user.id === userId);
if (index >= 0) {
newUsers[index] = {
...newUsers[index],
[name]: value,
};
setUsers(newUsers);
}
},
[users]
);
const addNewUser = useCallback(() => {
let newUser = { id: ++lastId, name: "John Doe", gender: "Male" };
setUsers((prevUsers) => [...prevUsers, newUser]);
}, []);
return (
<div className="App">
<button onClick={addNewUser}>Add user</button>
<br />
{users.map((user) => (
<User key={user.id} user={user} handleUserChange={handleUserChange} />
))}
</div>
);
}
export default App;
User.js
import { useRef, memo } from "react";
const User = memo(({ user, handleUserChange }) => {
const renderNum = useRef(0);
return (
<div className="user">
<div> Rendered: {renderNum.current++} times</div>
<div>ID: {user.id}</div>
<div>
Name:{" "}
<input
name="name"
value={user.name}
onChange={(e) => handleUserChange(e, user.id)}
/>
</div>
<div>
Gender:{" "}
<input
name="gender"
value={user.gender}
onChange={(e) => handleUserChange(e, user.id)}
/>
</div>
<br />
</div>
);
});
export default User;
Why the useCallback and memo doesn't do the job here? How can I make it work, prevent rendering of other User components if another User component is changing(typing something in Input)?
Thank you.
useCallback and useMemo take a dependency array. If any of the values inside that array changes, React will re-create the memo-ized value/callback.
With this in mind, we see that your handleUsersChange useCallback is recreated every time the array users changes. Since you update the users state inside the callback, every time you call handleUsersChange, the callback is re-created, and therefore the child is re-rendered.
Solution:
Don't put users in the dependency array. You can instead access the users value inside the handleUsersChange callback by providing a callback to setUsers functions, like so:
const handleUserChange = useCallback(
(e, userId) => {
const { name, value } = e.target;
setUsers((oldUsers) => {
const newUsers = [...oldUsers];
const index = newUsers.findIndex((user) => user.id === userId);
if (index >= 0) {
newUsers[index] = {
...newUsers[index],
[name]: value,
};
return newUsers;
}
return oldUsers;
})
},
[]
);
I am trying to make hover effect with react hooks
I wrote function to hover based on some tutorials
function useHover() {
const [hovered, setHovered] = useState(false);
const ref = useRef(null);
const handleMouseOver = () => setHovered(true);
const handleMouseOut = () => setHovered(false);
useEffect(() => {
const node = ref.current;
if (node) {
node.addEventListener("mouseover", handleMouseOver);
node.addEventListener("mouseout", handleMouseOut);
return () => {
node.removeEventListener("mouseover", handleMouseOver);
node.removeEventListener("mouseout", handleMouseOut);
};
}
}, [ref]);
return [ref, hovered];
}
but how to make it work in my App function
export default function App() {
const [ref, isHovered] = useHover();
const reactionItems = myObject.map(([key, value]) => (
<li key={key} ref={ref}>
{isHovered ? `${key} ${value.length > 1 ? "x " + value.length : ""}` : `${key} ${value.length > 1 ? "x " + value.length : ""} ${value}`}
</li>
));
return (
<div className="App">
<h1>{string}</h1>
<h2>Reactions</h2>
<ul>{reactionItems}</ul>
</div>
);
}
I can see it only in state false so second option and no hover effect
Use React's events' system, and not the DOM's. In addition, each item should have it's own event handlers, and state.
Create a hook that returns the hovered state, and the events' listeners of an item. Create an Item component, and use the hook in it's definition. Render the items.
const { useState, useMemo } = React;
const useHover = () => {
const [hovered, setHovered] = useState();
const eventHandlers = useMemo(() => ({
onMouseOver() { setHovered(true); },
onMouseOut() { setHovered(false); }
}), []);
return [hovered, eventHandlers];
}
const Item = ({ children }) => {
const [hovered, eventHandlers] = useHover();
return (
<li {...eventHandlers}>Item: {hovered && children}</li>
);
};
const myObject = {
a: 'A1',
b: 'B2',
c: 'C3',
}
function App() {
const reactionItems = Object.entries(myObject)
.map(([key, value]) => (
<Item key={key}>{value}</Item>
));
return (
<div className="App">
<h2>Reactions</h2>
<ul>{reactionItems}</ul>
</div>
);
}
ReactDOM.render(<App />, root);
<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>
A way to do this is to use React's events, and just make sure you let it be more generic.
One of the issues you were running into is that ref can only refer to a single node at a time. And ref never changes dependencies, so the useEffect only ever ran once.
const { useState, useRef, useEffect, useCallback } = React;
function useHover() {
const [hovered, setHovered] = useState({});
const mouseOver = useCallback((event) => {
const target = event.target;
const key = target.getAttribute('data-key');
setHovered((curState) => ({ ...curState, [key]: true }));
}, []);
const mouseOut = useCallback((event) => {
const target = event.target;
const key = target.getAttribute('data-key');
setHovered((curState) => ({ ...curState, [key]: false }));
}, []);
return { mouseOver, mouseOut, hovered };
}
const object = { key1: 'test', key2: 'test2', key3: 'test3' };
const myObject = Object.entries(object);
const string = 'Header';
function App() {
const { mouseOver, mouseOut, hovered } = useHover();
const reactionItems = myObject.map(([key, value]) => (
<li key={key} data-key={key} onMouseOver={mouseOver} onMouseOut={mouseOut}>
{hovered[key]
? `${key} ${value.length > 1 ? 'x ' + value.length : ''}`
: `${key} ${value.length > 1 ? 'x ' + value.length : ''} ${value}`}
</li>
));
return (
<div className="App">
<h1>{string}</h1>
<h2>Reactions</h2>
<ul>{reactionItems}</ul>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
You can also separate the list items out to their own component which would enable you to work with the useHover more closely to how you had it.