Wanted to ask people with more experience with React about this, since I can't seem to find a definitive answer. I know you don't want to add text to the DOM directly, which is why React makes you write dangerouslySetInnerHtml
in order to set it.
But what about classList.{ add || remove || toggle }
? Is using classList.add() || .remove()
tantamount to dangerouslySetInnerHtml
? Should I be worried about doing this, too?
Case in point - a function that renders a list from inside a component styled with bootstrap css.
Problem: Set add/remove .active
to li className
on mouse hover.
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
const RenderList = (array, position = 'text-center') => {
const handleMouseEnter = e => {
e.target.classList.add('active');
};
const handleMouseLeave = e => {
e.target.classList.remove('active');
};
return (
<ul className={`list-group ${position}`}>
{array.map(({ href, key, label, rel, target }) => (
<a key={key} href={href} rel={rel} target={target} fontSize={1.2}>
<li
className={`list-group-item '${key}'`}
data-id={key}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{label}
</li>
</a>
))}
</ul>
);
};
export default RenderList;
The problem is - I've tried setting this in state, but it affects all li
s at once (I've seen this issue in other questions - albeit, not for functional components, but for classes)
Is this safe? I don't see any warnings in my browser.
If it's not safe, what's the alternative? I tried using a data-id
attribute from the key for the li
, but I wasn't quite able to wrap my head around useState
for a single element...
Edit: A question was suggested as a possible answer, but it recommends making a separate instance of state for each list item, which doesn't seem like a very good solution.
React is a data-first UI lib, meaning that the data dictates how it should render, and it (almost) never need to read the DOM. It only writes to the DOM, and with its internal diffing algorithm, it computes the minimal chunks to update in the DOM.
When mutating the DOM ourselves, React won't be aware of our changes and might wipe our efforts in the next render phase.
Whenever possible, favor CSS selectors over JavaScript to style your components. In this case, it would be a simple selector:
.list-group li:hover {
/* `.active` styles here */
}
To avoid managing the state of each individual items in the list component, I'd make an Item
component which manages its own state and events, leaving the parent list to focus on its actual logic.
const { useState } = React;
// Manages its own state and events, making it
// possible to toggle the class here instead.
const Item = ({ to }) => {
const [active, setActive] = useState(false);
return (
<li
className={active? 'active' : ''}
onMouseEnter={() => setActive(true)}
onMouseLeave={() => setActive(false)}
>
<a data-link={to}>{to}</a>
</li>
);
};
// No more class toggling logic needed here.
const List = ({ items }) => (
<ul>
{items.map((itemProps) => <Item {...itemProps} />)}
</ul>
);
ReactDOM.render(
<List items={[{ to: '/home' }, { to: '/foo' }]} />,
document.querySelector('[data-role-react-root]')
);
.active {
background: red;
}
<div data-role-react-root></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>