I'm trying to reference to an input value rendered by a map(), but quickly found out that document.getElementById is not the way to go. I think using useRef is difficult because of the mapping. What is the most efficient and effective method for referencing a mapped input in a React application? Are there any specific techniques or tools that are particularly useful for this task?
import { Fragment, useContext } from "react";
import { RoutineContext } from "../../context/CurrentRoutineContext";
export default function Card() {
const { data: session, status } = useSession();
const { currentRoutine } = useContext(RoutineContext);
return (
<>
{currentRoutine.sections.map((item) => (
<div key={item.name} className="w-full rounded-md sm:max-w-md sm:shadow-lg">
{/* Header */}
<div className="px-4 py-5 border-b border-gray-200 shadow-md bg-gravel-400 sm:px-6">
<h3 className="text-xl font-medium leading-6 text-center text-slate-100">
{item.name}
</h3>
</div>
{/* Body */}
<div className="p-3">
{item.exercises.map((item, idx) => (
<div key={item.name} className="px-2 py-1.5">
<span className="block font-semibold text-gravel-700">{item.name}</span>
<table className="min-w-full">
<thead>
<tr key={item.name}>
//Table headers....
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
<tr key={item.name} className="w-full py-1">
<td className="px-2 text-sm text-gray-500 whitespace-nowrap">
{/* I want this input's value */}
<input
type="number"
className="w-12 pl-1 text-base rounded-sm bg-gravel-100"
/>
</td>
<td>
<button
disabled={false}
onClick={() => {}}
type="button"
>
<CheckIcon className="w-5 h-5 p-1 text-center text-stone-800" />
</button>
</td>
</tr>
</tbody>
</table>
</div>
))}
</div>
</div>
))}
</>
);
}
CodePudding user response:
You could have the ref itself be a map then use the callback form of the ref
prop to populate it.
import { Fragment, useContext } from "react";
import { RoutineContext } from "../../context/CurrentRoutineContext";
export default function Card() {
const { data: session, status } = useSession();
const { currentRoutine } = useContext(RoutineContext);
const fields = useRef({})
return (
<>
{currentRoutine.sections.map((section) => (
<div key={section.name} className="w-full rounded-md sm:max-w-md sm:shadow-lg">
{/* Header */}
<div className="px-4 py-5 border-b border-gray-200 shadow-md bg-gravel-400 sm:px-6">
<h3 className="text-xl font-medium leading-6 text-center text-slate-100">
{section.name}
</h3>
</div>
{/* Body */}
<div className="p-3">
{item.exercises.map((exercise, idx) => (
<div key={exercise.name} className="px-2 py-1.5">
<span className="block font-semibold text-gravel-700">{exercise.name}</span>
<table className="min-w-full">
<thead>
<tr key={exercise.name}>
//Table headers....
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
<tr key={exercise.name} className="w-full py-1">
<td className="px-2 text-sm text-gray-500 whitespace-nowrap">
{/* I want this input's value */}
<input
type="number"
className="w-12 pl-1 text-base rounded-sm bg-gravel-100"
ref={(el) => {
if (!fields.current[section.id]) fields.current[section.id] = {}
fields.current[section.id][exercise.id] = el
}
/>
</td>
<td>
<button
disabled={false}
onClick={() => {}}
type="button"
>
<CheckIcon className="w-5 h-5 p-1 text-center text-stone-800" />
</button>
</td>
</tr>
</tbody>
</table>
</div>
))}
</div>
</div>
))}
</>
);
}
Now fields.current
will have a structure like:
{
"section_id_123": {
"exercise_id_123": // <-- HTML ELEMENT HERE
// Other exercises in this section
}
// Other sections
}
I made an assumption both section
and exercise
have an id
property. You could probably use name
if uniqueness is guaranteed.
However this is usually where you are at the point where you have reached the practical limits of "uncontrolled" forms. At this point you should look into changing your form to be "controlled". It would be much easier to manage.
You might consider using a form lib also.