Try this code:
import React, { useState, useEffect, useRef, useCallback } from "react";
function assertIsNode(e: EventTarget | null): asserts e is Node {
if (!e || !("nodeType" in e)) {
throw new Error(`Node expected`);
}
}
export interface ITask {
id?: string;
task?: string;
status?: boolean;
}
const list = [
{
id: "1a1",
task: "wash dishes",
status: true,
},
{
id: "7bs",
task: "cook dinner",
status: false,
},
{
id: "45q",
task: "Study",
status: true,
},
];
function App() {
const noteRefs = useRef<(HTMLDivElement | null)[]>([]);
const [toDos, setTodos] = useState<ITask[]>(list);
useEffect(() => {
noteRefs.current = noteRefs.current.slice(0, toDos.length);
}, [toDos]);
return (
<div className="bg-blue-500 h-screen">
<h2>To do</h2>
{toDos.map((note, index) => {
return (
<div
className="m-5 grid gap-5"
key={note.id}
>
<ForwardedNote
note={note}
index={index}
toDos={toDos}
setToDos={setTodos}
ref={element => (noteRefs.current[index] = element)}
/>
</div>
);
})}
</div>
);
}
interface INoteProps {
note: ITask;
toDos: ITask[];
setToDos: React.Dispatch<React.SetStateAction<ITask[]>>;
index: number;
}
function Note(
{ note, toDos, setToDos, index }: INoteProps,
ref: React.ForwardedRef<HTMLDivElement>
) {
const [open, setOpen] = useState(false);
const handleClickOutside = useCallback(
(e: MouseEvent) => {
console.log("clicking anywhere");
assertIsNode(e.target);
if (ref?.current?.contains(e.target)) {
console.log("clicked inside!");
return;
}
console.log(open);
setOpen(false);
},
[open, ref]
);
useEffect(() => {
if (open) {
document.addEventListener("mousedown", handleClickOutside);
} else {
document.removeEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [open, handleClickOutside]);
function inputHandler() {
setToDos(
toDos.map(task => {
return task ? { ...task, status: !task.status } : task;
})
);
}
return (
<div ref={ref} className="flex gap-2 bg-yellow-400">
<input className="text-black" type="text" value={note.task} readOnly />
<input
checked={note.status}
onChange={inputHandler}
type="checkbox"
readOnly
/>
</div>
);
}
const ForwardedNote = React.forwardRef<HTMLDivElement, INoteProps>(Note);
export default App;
Output
import React, { useState, useEffect, useRef, useCallback } from "react";
function assertIsNode(e) {
if (!e || !("nodeType" in e)) {
throw new Error(`Node expected`);
}
}
const list = [
{
id: "1a1",
task: "wash dishes",
status: true,
},
{
id: "7bs",
task: "cook dinner",
status: false,
},
{
id: "45q",
task: "Study",
status: true,
},
];
function App() {
const noteRefs = useRef([]);
const [toDos, setTodos] = useState(list);
useEffect(() => {
noteRefs.current = noteRefs.current.slice(0, toDos.length);
}, [toDos]);
return (React.createElement("div", { className: "bg-blue-500 h-screen" },
React.createElement("h2", null, "To do"),
toDos.map((note, index) => {
return (React.createElement("div", { className: "m-5 grid gap-5", key: note.id },
React.createElement(ForwardedNote, { note: note, index: index, toDos: toDos, setToDos: setTodos, ref: element => (noteRefs.current[index] = element) })));
})));
}
function Note({ note, toDos, setToDos, index }, ref) {
const [open, setOpen] = useState(false);
const handleClickOutside = useCallback((e) => {
var _a;
console.log("clicking anywhere");
assertIsNode(e.target);
if ((_a = ref === null || ref === void 0 ? void 0 : ref.current) === null || _a === void 0 ? void 0 : _a.contains(e.target)) {
console.log("clicked inside!");
return;
}
console.log(open);
setOpen(false);
}, [open, ref]);
useEffect(() => {
if (open) {
document.addEventListener("mousedown", handleClickOutside);
}
else {
document.removeEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [open, handleClickOutside]);
function inputHandler() {
setToDos(toDos.map(task => {
return task ? Object.assign(Object.assign({}, task), { status: !task.status }) : task;
}));
}
return (React.createElement("div", { ref: ref, className: "flex gap-2 bg-yellow-400" },
React.createElement("input", { className: "text-black", type: "text", value: note.task, readOnly: true }),
React.createElement("input", { checked: note.status, onChange: inputHandler, type: "checkbox", readOnly: true })));
}
const ForwardedNote = React.forwardRef(Note);
export default App;
Compiler Options
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"esModuleInterop": true,
"declaration": true,
"target": "ES2017",
"jsx": "react",
"module": "ESNext",
"moduleResolution": "node"
}
}
Playground Link: Provided