Skip to content

Instantly share code, notes, and snippets.

@kaenova
Last active February 19, 2023 10:07
Show Gist options
  • Save kaenova/203bd4d34014764f6dbcf17b3bf235f0 to your computer and use it in GitHub Desktop.
Save kaenova/203bd4d34014764f6dbcf17b3bf235f0 to your computer and use it in GitHub Desktop.
Recursion Components with Callback function
// These are custom component, you can change it to your liking
import MainButton from '@/components/button/MainButton';
import InputText from '@/components/input/InputText';
import React, { useEffect, useState } from 'react';
function Test() {
return (
<div className="flex flex-col w-screen h-screen items-center justify-center">
<div className="w-5/12 rounded-md p-3 border border-neutral-500/20 shadow-md bg-white">
<Headers onChange={(data) => console.log(data)} />
</div>
</div>
);
}
interface HeaderData {
title: string;
headers: HeaderData[];
}
function Headers({ onChange }: { onChange: (data: HeaderData) => void }) {
const [Data, setData] = useState<HeaderData>({
title: 'Main title',
headers: [],
});
// Callback on Data changes
useEffect(() => {
onChange(Data);
}, [Data]);
function handleDeleteSubheader(idx: number) {
const newData = { ...Data };
newData.headers.splice(idx, 1);
setData(newData);
}
function handleChangesSubheader(idx: number, newSubheader: HeaderData) {
const newData = { ...Data };
newData.headers[idx] = newSubheader;
setData(newData);
}
return (
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-2">
<InputText
placeholder="Title"
value={Data.title}
onChange={(e) => {
setData({ ...Data, title: e.target.value });
}}
/>
<MainButton
buttonText="Tambah"
onClick={() => {
const newData = { ...Data };
newData.headers = [...Data.headers, { title: '', headers: [] }];
setData(newData);
}}
/>
</div>
<div className="flex flex-col items-start">
{Data.headers.map((v, idx) => (
<SubHeader
header={v}
parentIdxs={[]}
currentIdx={idx}
onChange={(subHeaderIdx, newData) => {
handleChangesSubheader(subHeaderIdx, newData);
}}
onDelete={(idxReq) => handleDeleteSubheader(idxReq)}
/>
))}
</div>
</div>
);
}
function SubHeader({
parentIdxs,
currentIdx,
header,
onChange,
onDelete,
}: {
header: HeaderData;
parentIdxs: number[];
currentIdx: number;
// eslint-disable-next-line no-unused-vars
onChange: (changesIndex: number, newSubheaderData: HeaderData) => void;
// eslint-disable-next-line no-unused-vars
onDelete: (deleteIndex: number) => void;
}) {
const allIdx = [...parentIdxs, currentIdx];
// Create subHeadeing Numbering
let subHeaderNumber = '';
for (let i = 0; i < parentIdxs.length; i += 1) {
subHeaderNumber += `${parentIdxs[i] + 1}.`;
}
subHeaderNumber += `${currentIdx + 1}`;
// Handle changes from self
function handleAddButton() {
const newData = { ...header };
newData.headers.push({ title: '', headers: [] });
onChange(currentIdx, newData);
}
function handleDeleteButton() {
onDelete(currentIdx);
}
function handleChangesText(newText: string) {
const newData = { ...header };
newData.title = newText;
onChange(currentIdx, newData);
}
// Handle changes from child subheader
function handleChangeSubheader(idx: number, newSubheader: HeaderData) {
const newData = { ...header };
newData.headers[idx] = newSubheader;
onChange(currentIdx, newData);
}
function handleDeleteSubheader(idx: number) {
const newData = { ...header };
newData.headers.splice(idx, 1);
onChange(currentIdx, newData);
}
return (
<div className="flex flex-col gap-2 items-left w-full">
<div className="flex flex-row gap-2 items-center justify-between">
<p className="bg-lime-300 rounded-full p-1 px-5 font-bold">
{subHeaderNumber}
</p>
<InputText
placeholder="Subheading title"
value={header.title}
onChange={(v) => {
handleChangesText(v.target.value);
}}
/>
<div className="flex flex-row gap-3">
<MainButton
className="text-xs"
buttonText="+"
onClick={handleAddButton}
/>
<MainButton
className="text-xs bg-red-600"
buttonText="-"
onClick={handleDeleteButton}
/>
</div>
</div>
<div className="ml-9">
{header.headers.map((v, idx) => (
<SubHeader
onChange={(subheaderIdx, newSubheader) => {
handleChangeSubheader(subheaderIdx, newSubheader);
}}
onDelete={(deletedIndex) => {
handleDeleteSubheader(deletedIndex);
}}
currentIdx={idx}
parentIdxs={allIdx}
header={v}
/>
))}
</div>
</div>
);
}
export default Test;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment