Using react-bootstrap, I’m creating a "todo" list and I want to have a line drawn through the text when I check a checkbox. I suspect that my problem lies in trying to use a ref within a map function. I also don’t think I’m using state correctly here, either.
I’m learning aspects of both react and bootstrap, and I’ve been a little mixed up on how to use a ref with an array. Looking at some solutions for this online, I haven’t found a way to use a ref properly in an array like this while using react-bootstrap 2.7.2, with bootstrap 5.2.3.
I’ve tried using both useRef()
and useRef([])
to start. Using the code below, nothing happens with the text when I check the box.
import 'bootstrap/dist/css/bootstrap.min.css';
import React, { useRef, useState } from 'react';
import Form from 'react-bootstrap/Form';
import FormCheck from 'react-bootstrap/FormCheck';
import './App.css';
function App() {
const [state, setState] = useState({ labelChecked: false });
const labelRef = useRef();
const handleClick = event => {
labelRef.current = event.target;
if (state.labelChecked === false) {
labelRef.current.style.textDecorationLine = 'line-through';
} else {
labelRef.current.style.textDecorationLine = 'none';
}
setState({ labelChecked: !state.labelChecked });
};
return (
<div className="App">
<Form style={{ margin: 10 }}>
{['Todo 1', 'Todo 2', 'Todo 3'].map((todo, index) => {
return (
<Form.Check type="checkbox" id={todo} key={index} ref={labelRef}>
<FormCheck.Input type="checkbox" onChange={handleClick} />
<FormCheck.Label>{todo}</FormCheck.Label>
</Form.Check>
);
})}
</Form>
</div>
);
}
export default App;
Thanks!
2
Answers
Controlled component pattern
A straight forward solution is to move your checked state to the item level and not use refs at all. Its good practice to use controlled components so that your strikethrough doesn’t decouple from your check box’s native DOM state.
It’s a common pattern in React design systems to treat the mapped item as its own component with a
({item, index})
interface. This allows you to use hooks in each item.Controlled component pattern with a single state object
In order to get the state of all checkboxes, you can then move your checked state up to App and maintain it as a map or array of current values.
The issue with your current code is that you are using the same ref (labelRef) for all the checkboxes. When you update the labelRef.current value inside the handleClick function, it gets updated for all the checkboxes, resulting in unexpected behavior.
To fix this, you can create an array of refs using useRef([]) and store a ref for each checkbox. Then, in the handleClick function, you can use the index of the clicked checkbox to update the corresponding ref.
Here’s an updated version of your code that should work as expected: