Description - I'm pulling a list of Material data from firestore and displaying it as a table. I've added a modal to add a Material to the database.
Problem - The refresh method I'm passing to the modal, refreshList() isn't refreshing the list of Materials on the Materials page. I'm quite certain the refresh method works—it's the same one I use to pull all Materials from firestore. what did I do wrong?
Here's the AddMaterialModal.
import { FC, useContext, useState } from "react";
import { ProjectContext } from "../../context/ProjectContext";
import MaterialService from "../../services/materialService";
import { Material } from "../../services/orgTypes";
import { randomString } from "../../utils/utils";
import AddEditTextModal from "./AddEditTextModal";
interface AddMaterialsModalProps {
editting?: boolean;
setOpenModal: (bool: boolean) => void;
selectedMaterial?: Material;
refreshList?: () => void;
}
const AddMaterialModal: FC<AddMaterialsModalProps> = ({
editting,
setOpenModal,
selectedMaterial,
refreshList,
}) => {
const idTemp = randomString(20);
const projectContext = useContext(ProjectContext);
const [newMaterial] = useState<Material>({
material: "material",
actualquantity: "actualquantity",
size: "size",
id: idTemp,
description: "description",
type: "type",
unit: "unit",
quantity: "quantity",
});
const materialService = new MaterialService(
projectContext.selectedProject.id
);
const createNewMaterial = () => {
materialService.updateCreateGlobalMaterial(newMaterial);
if (refreshList != undefined) {
refreshList();
setOpenModal(false);
}
};
return (
<div>
<div className="flex flex-col gap- 2 w-full h-fit ">
{//input some material data here.}
</div>
<div className="flex justify-center align-center">
<button
onClick={createNewMaterial}
className=" w-fit px-6 py-2.5 mt-6 bg-flowius-blue text-white font-medium text-lg leading-tight uppercase rounded shadow-md hover:bg-blue-400 hover:shadow-lg focus:bg-cyan-400 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-500 active:shadow-lg
transition duration-150 ease-in-out"
>
{editting ? "UPDATE" : "ADD +"}
</button>
</div>
</div>
);
};
export default AddMaterialModal;
And here's the main Materials page.
import { FC, useCallback, useContext, useEffect, useState } from "react";
import DataTable from "react-data-table-component";
import { ProjectContext } from "../context/ProjectContext";
import { Material } from "../services/orgTypes";
import Loader from "../components/Loader";
import MaterialIcon from "../components/MaterialIcon";
import { toast } from "react-toastify";
import MaterialService from "../services/materialService";
import Modal from "../components/modal/Modal";
import AddMaterialModal from "../components/modal/AddMaterialModal";
interface Selected {
allSelected: boolean;
selectedCount: number;
selectedRows: Material[];
}
export const formatDate = (date: Date) => {
const month = date.toLocaleString("en-us", { month: "long" });
const year = date.getFullYear();
const day = date.getDate();
return `${day} ${month} ${year}`;
};
const Materials: FC = () => {
const [materials, setMaterials] = useState<Material[]>([]);
const [loading, setLoading] = useState(true);
const [selected, setSelected] = useState<Selected>();
const projectContext = useContext(ProjectContext);
const { id } = projectContext.selectedProject;
const materialService = new MaterialService(id);
const pullMaterials = useCallback(async () => {
if (!id) return;
const materials = await materialService.getProjectMaterials();
setMaterials(materials);
setLoading(false);
}, [id]);
const [openAddMaterialModal, setOpenAddMaterialModal] = useState(false);
useEffect(() => {
pullMaterials();
}, [pullMaterials]);
const columns = [
{
name: "Material",
selector: (row: Material) => row.material,
sortable: true,
},
{
name: "Description",
selector: (row: Material) => row.description,
sortable: true,
},
{
name: "Size",
selector: (row: Material) => row.size,
sortable: true,
},
{
name: "Unit",
selector: (row: Material) => row.unit,
sortable: true,
},
{
name: "Actual Quantity",
selector: (row: Material) => row.actualquantity,
sortable: true,
},
];
const deleteSelected = async () => {
if (!selected) return;
const { selectedRows } = selected;
materialService.deleteGlobalMaterial(selectedRows);
toast.success(`${selectedRows.length} Items deleted`);
pullMaterials();
setSelected(undefined);
};
return (
<div className="m-2 w-full">
<div className="flex text-flowius-blue text-xl flex-row justify-between my-1">
<button
className="bg-flowius-blue text-base text-white p-2 rounded-md"
onClick={() => {
setOpenAddMaterialModal(true);
}}
>
Add Material
</button>
</div>
{(selected?.selectedCount ?? 0) > 0 && (
<MaterialIcon
className="cursor-pointer "
onClick={deleteSelected}
icon="delete"
/>
)}
{loading ? (
<Loader />
) : (
<DataTable
selectableRows={true}
paginationRowsPerPageOptions={[50, 100, 150]}
paginationPerPage={50}
onSelectedRowsChange={setSelected}
pagination={true}
columns={columns}
data={materials}
defaultSortAsc={true}
defaultSortFieldId="Material"
/>
)}
{setOpenAddMaterialModal && (
<Modal
open={openAddMaterialModal}
close={() => {
setOpenAddMaterialModal(false);
}}
title={"Add Material"}
className={""}
>
<AddMaterialModal
editting={false}
setOpenModal={setOpenAddMaterialModal}
refreshList={pullMaterials}
/>
</Modal>
)}
</div>
);
};
export default Materials;
Related
long story short I was asked to create a table component using react-table,
in that table by default it uses component input, which when double-clicked it can immediately type.
and secondly, I want for one of the column editableCell to use dropdown. i have tried it but failed.
depedencies:
{
"#tanstack/react-table": "^8.5.22",
"next": "^12.3.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
component used : SubTable
<SubTable
data={dataTable}
handleAdd={addData}
columns={DataColumns}
tableStyle="h-[250px]"
hiddenColumns={["id"]}
showAddButton={true}
showDeleteButton={true}
skipPageReset={skipPageReset}
updateData={updateMyData}
/>
SubTable.tsx
import { FC, useState } from "react";
import {
useFlexLayout,
usePagination,
useTable,
useGlobalFilter,
useSortBy,
} from "react-table";
import styles from "./styles.module.css";
import EditableCell from "./EditableCell";
import Image from "next/image";
export interface SubTableProps {
setIsForm?: stateData;
data: any[];
columns: any[];
headerComponent?: JSX.Element;
page?: (page: number) => void;
showAddButton?: boolean;
showDeleteButton?: boolean;
onClickRow?: (cell: Cell<any, unknown>, row: Row<any>) => void;
hiddenColumns?: string[];
updateData?: (row: any, columnId: string, value: any) => void;
skipPageReset?: boolean;
tableStyle?: string;
handleAdd?: () => void;
handleConfig?: () => void;
deleteLabel?: string;
showBedButton?: boolean;
showConfigButton?: boolean;
}
const SubTable: FC<SubTableProps> = ({
data,
columns,
hiddenColumns,
skipPageReset,
updateData,
setIsForm,
tableStyle,
showAddButton,
showDeleteButton,
showBedButton = false,
showConfigButton = false,
deleteLabel = "Delete",
handleAdd,
handleConfig,
}) => {
const [editableRowIndex, setEditableRowIndex] = useState(null);
const [active, setActive] = useState("");
const [selectedData, setSelectedData] = useState({});
const { getTableProps, getTableBodyProps, headerGroups, page, prepareRow } =
useTable(
{
columns,
data,
// Set our editable cell renderer as the default Cell renderer
defaultColumn: {
Cell: EditableCell,
},
autoResetPage: !skipPageReset ?? false,
initialState: {
pageIndex: 0,
hiddenColumns: hiddenColumns ?? [],
},
updateData,
editableRowIndex,
setEditableRowIndex,
},
useGlobalFilter,
useSortBy,
usePagination,
useFlexLayout ?? null
);
const clickedRow = (e: React.MouseEvent, result: any) => {
const { index, values } = result;
setActive(index);
const currentIndex = index;
if (e.detail == 2) {
if (editableRowIndex !== currentIndex) {
setEditableRowIndex(index);
} else {
setEditableRowIndex(null);
setSelectedData(values);
// data changed here console.log('update row', values)
}
}
};
return (
<>
<div className={`bg-white p-2 text-xs ${tableStyle ?? ""}`}>
{/* */}
<div className={styles.toolbar}>
{showAddButton && (
<>
<button className={styles.btn} type="button" onClick={handleAdd}>
<Image
src="/images/button/add.png"
className="ml-1"
alt="add_icon"
width={16}
height={16}
/>
Add
</button>
<div className={styles.separate}>|</div>
</>
)}
{showConfigButton && (
<>
<button
className={styles.btn}
type="button"
onClick={handleConfig}
>
<Image
src="/images/button/update.svg"
className="ml-1"
alt="add_icon"
width={16}
height={16}
/>
Configuration
</button>
<div className={styles.separate}>|</div>
</>
)}
{showDeleteButton && (
<button
className={styles.btn}
type="button"
onClick={() => {
console.log("delete");
}}
>
<Image
src="/images/button/delete.svg"
className="ml-1"
alt="delete_icon"
width={16}
height={16}
/>
{deleteLabel}
</button>
)}
{showBedButton && (
<button
className={styles.btn}
type="button"
onClick={() => {
console.log("delete");
}}
>
<Image
src="/images/button/edit-undo.png"
className="ml-1"
alt="delete_icon"
width={16}
height={16}
/>
Empty Bed
</button>
)}
</div>
<div className="overflow-x-auto border-l-[#e7e9ec] border-r-[#e7e9ec] print:block max-h-[200px] overflow-y-auto">
<table
className="table-fixed w-full border-x bg-white relative border-collapse"
{...getTableProps()}
>
<thead className="sticky top-0">
{headerGroups.map((headerGroup, idx) => (
<tr {...headerGroup.getHeaderGroupProps()} key={idx}>
{headerGroup.headers.map((column, idx) => (
<th
className="border border-solid font-normal text-lg text-left p-1 bg-green-100"
{...column.getHeaderProps(column.getSortByToggleProps())}
key={idx}
>
{column.render("Header")}
<span>
{column.isSorted
? column.isSortedDesc
? " 🔻"
: " 🔺"
: " ↕"}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody className="overflow-y-auto" {...getTableBodyProps()}>
{page.map((row, idx) => {
prepareRow(row);
return (
<tr
{...row.getRowProps()}
key={idx}
className={`${
active == row.id ? "bg-bgGrey-1" : ""
} cursor-default`}
onClick={(e) => clickedRow(e, row)}
>
{row.cells.map((cell, idx) => {
return (
<td
className="whitespace-nowrap text-ellipsis overflow-hidden border-b border-r border-y p-1"
{...cell.getCellProps()}
key={idx}
>
{cell.render("Cell")}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</>
);
};
export default SubTable;
useSubTable.tsx
import { useState } from "react";
const useSubTable = () => {
const [dataTable, setDataTable] = useState<any>([]);
const [skipPageReset, setSkipPageReset] = useState(false);
const updateMyData = (rowIndex: number, columnId: any, value: any) => {
// We also turn on the flag to not reset the page
setSkipPageReset(true);
setDataTable((old: any[]) =>
old.map((row: any, index: number) => {
if (index === rowIndex) {
return {
...old[rowIndex],
[columnId]: value,
};
}
return row;
})
);
};
return { dataTable, setDataTable, updateMyData, skipPageReset };
};
export default useSubTable;
EditableCell.tsx
import React, { useState, useEffect } from "react";
// Create an editable cell renderer
const EditableCell = (props: any) => {
const {
value: initialValue,
row: { index },
column: { id },
updateData, // This is a custom function that we supplied to our table instance
editableRowIndex, // index of the row we requested for editing
} = props;
// We need to keep and update the state of the cell normally
const [value, setValue] = useState(initialValue);
const onChange = (e: any) => {
setValue(e.target.value);
};
// We'll only update the external data when the input is blurred
const onBlur = () => {
updateData(index, id, value);
};
// If the initialValue is changed externall, sync it up with our state
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return index === editableRowIndex ? (
<input
style={{ width: "100%" }}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
) : (
<p>{value}</p>
);
};
export default EditableCell;
define a column: column.ts
import TypeDropdown from "./TypeDropdown";
export const dataColumns = [
{
Header: "Name",
accessor: "name",
sticky: "left",
},
{
Header: "Order",
accessor: "order",
sticky: "left",
},
{
Header: "Type",
accessor: "type",
sticky: "left",
Cell: TypeDropdown,
},
];
my custom dropdown component: TypeDropdown.tsx
import React, { useState, useEffect } from "react";
import { Select } from "#/client/components/Inputs";
const TypeDropdown = (props: any) => {
const {
value: initialValue,
row: { index },
column: { id },
updateData, // This is a custom function that we supplied to our table instance
editableRowIndex, // index of the row we requested for editing
} = props;
// We need to keep and update the state of the cell normally
const [value, setValue] = useState(initialValue);
const onChange = (e: any) => {
setValue(e.target.value);
updateData(index, id, e.target.value);
};
// If the initialValue is changed externall, sync it up with our state
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return id === "type" ? (
<Select
value={value}
id="typex"
option={[
{ id: 1, name: "TextField", code: "TextField" },
{ id: 2, name: "Combo", code: "Combo" },
]}
showRed={true}
onChange={(e) => onChange(e)}
required={true}
/>
) : (
<p>{value}</p>
);
};
export default TypeDropdown;
lastly my useSubTableProps.tsx
import React, { useMemo, useState } from "react";
import { dataColumns } from "./column";
interface customObject {
[key: string]: any;
}
const useSubTableProps = () => {
const DataColumns = useMemo(() => dataColumns, []);
const [dataTable, setDataTable] = useState<any>([]);
const [skipPageReset, setSkipPageReset] = useState<boolean>(false);
const updateMyData = (rowIndex: number, columnId: any, value: any) => {
// We also turn on the flag to not reset the page
setSkipPageReset(true);
setDataTable((old: customObject[]) =>
old.map((row: any, index: number) => {
if (index === rowIndex) {
return {
...old[rowIndex],
[columnId]: value,
};
}
return row;
})
);
};
const addData = () => {
setDataTable((prev: any) => [
...prev,
{
name: `test`,
order: 0,
type: "TextField",
},
]);
};
return { DataColumns, dataTable, addData, updateMyData, skipPageReset };
};
export default useSubTableProps;
expected visual, what I want in column type is dropdown component
finally, I found the problem and the solution,
after I re-read the documentation about the column options section, it says to use a valid JSX element, after that, I try to use a simple JSX element and it works. Maybe my previous JSX element was not considered valid by react-table
link: React-table column options
import { FC } from 'react';
import { useDropzone } from 'react-dropzone';
import { FileIcon } from 'assets/icons';
import Typography from '../Typography';
import { TMultipleDropzoneProps } from './types';
import styles from './MultipleDropzone.module.scss';
const MultipleDropzone: FC<TMultipleDropzoneProps> = ({ title, onDrop }) => {
const { getRootProps, getInputProps, open, isDragAccept, isFocused, isDragReject } = useDropzone({
accept: { 'image/*': ['.jpeg', '.png'], 'video/mp4': ['.mp4', '.MP4'] },
onDrop,
noClick: true,
noKeyboard: true,
maxFiles: 3,
});
const accept = isDragAccept ? 1 : 0;
const focused = isFocused ? 1 : 0;
const rejected = isDragReject ? 1 : 0;
// This is used for warning in console for camel-case attributes to the DOM element and to make it boolean
return (
<div className={styles.wrapper}>
<div
onClick={open}
className={styles.container}
{...getRootProps({ accept, focused, rejected })}
>
<input {...getInputProps({})} />
{rejected === 1 && (
<div className={styles.error}>
Some files were rejected because they did not meet the requirements.
</div>
)}
<div className={styles.container__content}>
<Typography>{title}</Typography>
</div>
<button onClick={open} className={styles.icon}>
<FileIcon />
</button>
</div>
</div>
);
};
export default MultipleDropzone;
the type file:
export type TMultipleDropzoneProps = {
title: string;
onDrop: (e: any, a: any) => void;
isFileUploaded: boolean;
maxFiles?: number;
};
the modal I am using it in:
import { useContext, useState } from 'react';
import { ModalContext } from 'context/Modal';
import { FileExtended } from 'types/global/file';
import { useAppDispatch, useAppSelector } from 'hooks';
import { NewPostTextArea, MultipleDropzone, Typography, Button } from 'components';
import { createActivityPost } from 'store/slices/activitiesSlice/activitiesThunks';
import { CloseCircleIcon } from 'assets/icons';
import { TImages } from './types';
import styles from './NewPost.module.scss';
const NewPost = () => {
const { closeModal } = useContext(ModalContext);
const [images, setImages] = useState<TImages[]>([]);
const [description, setDescription] = useState<string>('');
const dispatch = useAppDispatch();
const { userData } = useAppSelector((state) => state.auth);
const createPost = () => {
const post = {
user_id: userData?.id as number,
description: description,
content_url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg',
content_type: 'string',
};
closeModal();
if (description.trim()) {
dispatch(createActivityPost(post));
}
};
const isFileUploaded = images.length > 0;
const onDrop = (acceptedFiles: FileExtended[], maxFiles: number) => {
acceptedFiles.forEach((file) => {
const reader = new FileReader();
reader.onload = (e) => {
setImages((prev) => [
...prev,
{
id: Date.now(),
type: file.type,
src: e.target && e.target.result,
file: file,
description: file.name,
},
]);
};
reader.readAsDataURL(file);
});
let modifiedAcceptedFiles = acceptedFiles;
if (acceptedFiles.length > maxFiles) {
// If the number of files exceeds the maxFiles limit,
// slice the extra files off the array and show an error message
modifiedAcceptedFiles = acceptedFiles.slice(0, maxFiles);
}
};
const removeImage = (id: number) => {
setImages(images.filter((image) => image.id !== id));
};
const imageFiles = images.map((image) => {
return (
<div key={image.id} className={styles.container__main__case__box}>
<CloseCircleIcon
onClick={() => removeImage(image.id)}
className={styles.container__main__case__box_close}
/>
{image.type.includes('video') ? (
<video src={image.src as string} autoPlay loop />
) : (
<img src={image.src as string} alt={image.description} />
)}
</div>
);
});
return (
<div className={styles.container}>
<Typography className={styles.container__head}>New Post</Typography>
<div className={styles.container__description}>
<NewPostTextArea value={description} setValue={setDescription} />
</div>
<div className={styles.container__main}>
<Typography className={styles.container__main__head}>{images.length} items</Typography>
<div className={styles.container__main__case}>{imageFiles}</div>
</div>
<MultipleDropzone
onDrop={onDrop}
title='Please attach your files here (Max 3)'
isFileUploaded={isFileUploaded}
/>
<div className={styles.container__footer}>
<Button className={styles.container__footer_close} onClick={closeModal}>
Close
</Button>
<Button type='submit' className={styles.container__footer_submit} onClick={createPost}>
Create
</Button>
</div>
</div>
);
};
export default NewPost;
I tried adding maxFiles to every single component, I also tried adding it to the onDrop component. New to React ( 1 week) and I am slowly losing my sanity. I will never forgive Zuckerberg for this apparition he has brought upon coderkin. Even chatGPT could not help my case.
i'm new in formik and react typescript.
currently, i have a screen called addScreen.tsx. inside that screen, there is component called <FormDropdown />. component FormDropdown has a child component called <Dropdown />.
how i can get the value when dropdown item changed, then i passed on onsubmit formik in addScreen ?
thanks in advance
below are the snippet codes from every screen or component
// addscreen.tsx
<FormDropdown
options={[
{
id: "what's your name?",
label: "what's your name?",
},
{
id: "where do you live?",
label: "where do you live?",
},
{
id: "what is your favorite food?",
label: "what is your favorite food?",
},
]}
name="securityQuestion"
data-testid="securityQuestion"
className="block h-full w-full border-transparent px-3 py-2 text-base leading-[1.813rem] text-nero focus:border-transparent focus:outline-none focus:ring-0"
/>;
//its formik component named FormDropdown.tsx
import { useField } from "formik";
import React, { FC, useCallback, useMemo, useState } from "react";
import { useUpdateEffect } from "#/hooks";
import Dropdown from "#/components/Dropdown";
import type { FormDropdownProps } from "../FormDropdown/types";
const FormDropdown: FC<FormDropdownProps> = (props) => {
const { name } = props;
const [, meta, helpers] = useField(name);
const [currentValue, setCurrentValue] = useState<string | number>(
meta.value || meta.initialValue
);
const hasError = useMemo(
() => meta.touched && !!meta.error,
[meta.error, meta.touched]
);
useUpdateEffect(() => {
setCurrentValue(meta.value);
}, [meta.value]);
const handleChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const text = event.target.value;
setCurrentValue(text);
helpers.setValue(text);
helpers.setError("");
},
[helpers]
);
const handleBlur = useCallback(() => {
helpers.setTouched(true);
}, [helpers]);
return (
<Dropdown
{...props}
// value={currentValue}
// hasError={hasError}
// onChange={handleChange}
// onBlur={handleBlur}
/>
);
};
export default FormDropdown;
//Dropdown.tsx
import React, { FC, Fragment, useEffect, useRef, useState } from "react";
import clsxm from "#/utils/clsxm";
import Button from "#/components/Button";
import type { DropdownProps, Option } from "./types";
import Typography from "../Typography";
const Dropdown: FC<DropdownProps> = ({
options,
isScrolled = false,
disabled = false,
hasError = false,
className,
}) => {
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
const [open, setIsOpen] = useState(false);
const ref = useRef<any>();
useEffect(() => {
/**
* Alert if clicked on outside of element
*/
function handleClickOutside(event: any) {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(false);
}
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
const handleClick = (option: Option) => () => {
setSelectedOption(option);
handleOpenClick();
};
const handleOpenClick = () => {
setIsOpen((open) => !open);
};
return (
<>
<button
type="button"
className={clsxm(
className,
"w-full rounded-[0.938rem] border border-beluga bg-white p-[10rem] px-4 py-4 text-base font-medium text-improbable focus-within:border-improbable",
hasError ? "border-poppySurprise" : ""
)}
aria-expanded="true"
aria-haspopup="true"
onClick={() => handleOpenClick()}
>
{selectedOption ? selectedOption.label : "Please Choose"}
</button>
{!disabled && open && (
<div
ref={ref}
className={clsxm(
"absolute z-20 mt-[0.625rem] w-[14.0625rem] overflow-hidden rounded-[0.938rem] border-[0.0625rem] border-beluga bg-white py-[1.25rem]",
isScrolled && "h-[15.5rem] overflow-scroll overflow-x-hidden"
)}
>
{options.map((option) => (
<Fragment key={option.id}>
<Button
className={clsxm(
"h-[0.75rem] w-[8.125rem] rounded-none border-none bg-transparent hover:bg-white"
)}
onClick={handleClick(option)}
>
<Typography variant="p" size="text-sm" color="text-improbable">
{option.label}
</Typography>
</Button>
</Fragment>
))}
</div>
)}
</>
);
};
export default Dropdown;
i have a drag and drop with multiple array in that card, but when i added new value inside the card the value added in all card, someone can help me how to the fix this?
the code like this:
import * as Style from '#/components/Form/FormStyle'
import { confirm } from '#/components/Modals/Confirmation'
import dynamic from 'next/dynamic'
import { useCallback, useEffect, useState } from 'react'
import { Trash2 } from 'react-feather'
import { useList } from 'react-use'
import { QuestionCard } from './QuestionCard'
import update from 'immutability-helper'
import { DragVertical, Plus } from '#/components/Icons'
type TermType = {
id: number
name: string
correct: boolean
}
const DefaultQuestion: { id: number; question?: string; placeholder?: string; term: any[] } = {
id: 1,
question: '',
placeholder: '',
term: []
}
const MDEditor = dynamic<any>(() => import('#/components/Form/MDEditor/withNoValue').then((fn) => fn.MDEditor), {
ssr: false
})
export const MultipleAnswer = ({ data }: { data?: any }) => {
const [term, { set: setTerm, updateAt: updateTermAt, removeAt: removeTermAt }] = useList<TermType>()
const [cards, { set: setCards, updateAt: updateCardsAt, removeAt: removeCardsAt }] = useList<typeof DefaultQuestion>()
const handleAddTerm = () => {
const newId = term?.slice(-1)[0]?.id ? term?.slice(-1)[0]?.id + 1 : 1
setTerm([...term, { id: newId, name: `Kolom jawaban ${newId}`, correct: false }])
}
const handleChangeTerm = (index: number, value: string) => {
updateTermAt(index, { ...term[index], name: value })
}
const handleDeleteTerm = async (id: number) => {
const confirmed = await confirm('Apakah anda yakin ingin menghapus pertanyaan ini?', 'Batal', 'Hapus')
if (confirmed) {
removeTermAt(id)
}
}
const [titles, setTitles] = useState<{ [id: string]: string }>({
1: 'Opsi 1',
2: 'Opsi 2',
3: 'Opsi 3',
4: 'Opsi 4'
})
const moveCard = useCallback(
(dragIndex: number, hoverIndex: number) => {
if (!cards) return
const dragCard = cards[dragIndex]
setCards(
update(cards, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragCard]
]
})
)
},
[cards]
)
const handleAdd = () => {
const newId = cards?.slice(-1)[0]?.id ? cards?.slice(-1)[0]?.id + 1 : 1
const newTermId = cards?.slice(-1)[0]?.term?.slice(-1)[0]?.id || 0
const collectIdxBefore = DefaultQuestion.term.map((term, i) => {
const index = i === 0 ? 1 : i + 1
return { ...term, id: newTermId + index }
})
const createTitles = {
[newTermId + 1]: 'Opsi 1',
[newTermId + 2]: 'Opsi 2',
[newTermId + 3]: 'Opsi 3',
[newTermId + 4]: 'Opsi 4'
}
setCards([...cards, { id: newId, term: [...collectIdxBefore] }])
setTitles({ ...titles, ...createTitles })
}
useEffect(() => {
if (data) {
setCards(data?.question)
}
}, [data])
const [isDesktop, setIsDesktop] = useState(false)
useEffect(() => {
const handleResize = () => {
if (window.innerWidth > 768) {
setIsDesktop(true)
} else {
setIsDesktop(false)
}
}
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
const renderCard = (card: typeof DefaultQuestion, index: number) => {
const handleDelete = async (id: number) => {
const confirmed = await confirm('Apakah anda yakin ingin menghapus pertanyaan ini?', 'Batal', 'Hapus')
if (confirmed) {
removeCardsAt(id)
// remove also titles
cards[id].term.forEach((term) => {
delete titles[term.id]
})
}
}
const handleChange = (idx: number, idTerm: number, checked: boolean, termIndex: number) => {
const changedTerm = { ...cards[idx], term: [...cards[idx].term] }
changedTerm.term[termIndex] = { id: idTerm, name: titles?.[idTerm], correct: checked }
updateCardsAt(idx, changedTerm)
}
return (
<QuestionCard key={card.id} index={index} id={card.id} moveCard={moveCard} data-questions>
<div tw="flex justify-center mb-8">
<button tw="flex items-center space-x-3 text-sm text-gray-600 focus:outline-none">
<DragVertical />
<span>Urutkan</span>
</button>
</div>
<MDEditor label="Pertanyaan" defaultValue={data?.question?.[0]?.question} data-question />
<input type="hidden" value="radio" data-type />
<Style.InputGroup mobile={isDesktop ? false : true} tw="mt-5 !flex md:!grid">
<label tw="self-start md:text-right w-28">Jawaban</label>
<div tw="flex flex-col space-y-3">
{term?.map((term, i) => (
<div
key={Math.random()}
tw="relative flex items-center justify-between px-5 py-1 space-x-5 bg-gray-100 rounded-lg"
data-term>
<div tw="flex items-center">
<input
type="text"
defaultValue={term?.name}
tw="max-w-xs bg-transparent border-none focus:outline-none"
onBlur={(evt) => handleChangeTerm(i, evt.target.value)}
style={{ boxShadow: 'none' }}
/>
</div>
<button type="button" onClick={() => handleDeleteTerm(i)}>
<Trash2 width={18} tw="text-gray-600" />
</button>
</div>
))}
<div>
<button
type="button"
onClick={() => handleAddTerm()}
tw="inline-flex text-sm text-left text-secondary hover:underline">
Tambah Opsi
</button>
</div>
</div>
</Style.InputGroup>
<div tw="flex justify-end mt-5">
<button
type="button"
onClick={() => handleDelete(index)}
tw="flex items-center space-x-1 text-gray-400 hover:text-primary">
<Trash2 strokeWidth={1} width={20} />
<span tw="text-sm font-light">Hapus</span>
</button>
</div>
</QuestionCard>
)
}
return (
<div tw="flex flex-col space-y-5">
{cards?.map((card, i) => renderCard(card, i))}
<button
type="button"
tw="flex items-center justify-center w-full py-3 space-x-3 bg-white border border-dashed text-primary border-primary hover:bg-gray-100"
onClick={() => handleAdd()}>
<Plus />
<span tw="font-semibold">Tambah Pertanyaan</span>
</button>
</div>
)
}
the result with error like this:
https://streamable.com/f8l8qs
the firts of error is when i added the new opsi the value added to more card, same when i delete it, there detele all value in card
Starter in React-hook project
I need to call openModal() from column.js which is defined in Table.js and need to fetch data and open the new modal form Table.js. Its react-hook project
column.js
// ** React Imports
import { Link } from 'react-router-dom'
// ** Custom Components
import Avatar from '#components/avatar'
// ** Store & Actions
import { getUser, deleteUser } from '../store/action'
import { store } from '#store/storeConfig/store'
import { useSelector } from 'react-redux'
// ** Third Party Components
import { Badge, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'
import { Slack, User, Settings, Database, Edit2, MoreVertical, FileText, Trash2, Archive } from 'react-feather'
// ** Renders Client Columns
const renderClient = row => {
const stateNum = Math.floor(Math.random() * 6),
states = ['light-success', 'light-danger', 'light-warning', 'light-info', 'light-primary', 'light-secondary'],
color = states[stateNum]
if (row.user_avatar.length) {
const server_base_url = "http://localhost:3001/uploads/"
return <Avatar className='mr-1' img={`${server_base_url}${row.user_avatar}`} width='32' height='32' />
} else {
return <Avatar color={color || 'primary'} className='mr-1' content={row.user_fullname || 'John Doe'} initials />
}
}
const renderSerialNo = row => {
const param = useSelector(state => state.users.params)
const serial = ((param.page - 1) * param.perPage) + row + 1
return (serial)
}
// ** Renders Role Columns
const renderRole = row => {
const roleObj = {
subscriber: {
class: 'text-primary',
icon: User
},
maintainer: {
class: 'text-success',
icon: Database
},
editor: {
class: 'text-info',
icon: Edit2
},
author: {
class: 'text-warning',
icon: Settings
},
admin: {
class: 'text-danger',
icon: Slack
}
}
const Icon = roleObj[row.type_name] ? roleObj[row.type_name].icon : Edit2
return (
<span className='text-truncate text-capitalize align-middle'>
<Icon size={18} className={`${roleObj[row.type_name] ? roleObj[row.type_name].class : ''} mr-50`} />
{row.type_name}
</span>
)
}
const statusObj = {
2: 'light-warning',
0: 'light-success',
1: 'light-secondary'
}
export const columns = (openModal) => [
{
name: '#',
maxWidth: '3px',
selector: 'serial',
sortable: true,
cell: (row, index) => (renderSerialNo(index))
},
{
name: 'User',
minWidth: '300px',
selector: 'fullName',
sortable: true,
cell: row => (
<div className='d-flex justify-content-left align-items-center'>
{renderClient(row)}
<div className='d-flex flex-column'>
<Link
to={`/apps/user/view/${row.id}`}
className='user-name text-truncate mb-0'
onClick={() => store.dispatch(getUser(row.user_id))}
>
<span className='font-weight-bold'>{row.user_fullname}</span>
</Link>
<small className='text-truncate text-muted mb-0'>#{row.user_name}</small>
</div>
</div>
)
},
{
name: 'Email',
minWidth: '220px',
selector: 'email',
sortable: true,
cell: row => row.user_email
},
{
name: 'Role',
minWidth: '172px',
selector: 'role',
sortable: true,
cell: row => renderRole(row)
},
{
name: 'Status',
minWidth: '138px',
selector: 'status',
sortable: true,
cell: row => (
<Badge className='text-capitalize' color={statusObj[row.user_status]} pill>
{row.user_status_text}
</Badge>
)
},
{
name: 'Actions',
minWidth: '10px',
cell: row => (
<UncontrolledDropdown>
<DropdownToggle tag='div' className='btn btn-sm'>
<MoreVertical size={14} className='cursor-pointer' />
</DropdownToggle>
<DropdownMenu right>
<DropdownItem
tag={Link}
to={`/apps/user/view/${row.user_id}`}
className='w-100'
onClick={() => store.dispatch(getUser(row.user_id))}
>
<FileText size={14} className='mr-50' />
<span className='align-middle'>Details</span>
</DropdownItem>
<DropdownItem
tag={Link}
to={`/apps/user/edit/${row.user_id}`}
className='w-100'
onClick={() => store.dispatch(getUser(row.user_id))}
>
<Archive size={14} className='mr-50' />
<span className='align-middle'>Edit</span>
</DropdownItem>
<DropdownItem className='w-100' onClick={() => openModal(row)}>
<Trash2 size={14} className='mr-50' />
<span className='align-middle'>Delete</span>
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
)
}
]
Table.js
// ** React Imports
import { Fragment, useState, useEffect } from 'react'
// ** Invoice List Sidebar
import Sidebar from './Sidebar'
// ** Columns
import { columns } from './columns'
// ** Store & Actions
import { getAllData, getData } from '../store/action'
import { useDispatch, useSelector } from 'react-redux'
// ** Third Party Components
import Select from 'react-select'
import ReactPaginate from 'react-paginate'
import { ChevronDown } from 'react-feather'
import DataTable from 'react-data-table-component'
import { selectThemeColors } from '#utils'
import { Card, CardHeader, CardTitle, CardBody, Input, Row, Col, Label, CustomInput, Button } from 'reactstrap'
// ** Styles
import '#styles/react/libs/react-select/_react-select.scss'
import '#styles/react/libs/tables/react-dataTable-component.scss'
// ** Table Header
const CustomHeader = ({ toggleSidebar, handlePerPage, rowsPerPage, handleFilter, searchTerm }) => {
return (
<div className='invoice-list-table-header w-100 mr-1 ml-50 mt-2 mb-75'>
<Row>
<Col xl='6' className='d-flex align-items-center p-0'>
<div className='d-flex align-items-center w-100'>
<Label for='rows-per-page'>Show</Label>
<CustomInput
className='form-control mx-50'
type='select'
id='rows-per-page'
value={rowsPerPage}
onChange={handlePerPage}
style={{
width: '5rem',
padding: '0 0.8rem',
backgroundPosition: 'calc(100% - 3px) 11px, calc(100% - 20px) 13px, 100% 0'
}}
>
<option value='10'>10</option>
<option value='25'>25</option>
<option value='50'>50</option>
</CustomInput>
<Label for='rows-per-page'>Entries</Label>
</div>
</Col>
<Col
xl='6'
className='d-flex align-items-sm-center justify-content-lg-end justify-content-start flex-lg-nowrap flex-wrap flex-sm-row flex-column pr-lg-1 p-0 mt-lg-0 mt-1'
>
<div className='d-flex align-items-center mb-sm-0 mb-1 mr-1'>
<Label className='mb-0' for='search-invoice'>
Search:
</Label>
<Input
id='search-invoice'
className='ml-50 w-100'
type='text'
value={searchTerm}
onChange={e => handleFilter(e.target.value)}
/>
</div>
<Button.Ripple color='primary' onClick={toggleSidebar}>
Add New User
</Button.Ripple>
</Col>
</Row>
</div>
)
}
const UsersList = () => {
// ** Store Vars
const dispatch = useDispatch()
const store = useSelector(state => state.users)
// ** States
const [searchTerm, setSearchTerm] = useState('')
const [currentPage, setCurrentPage] = useState(1)
const [rowsPerPage, setRowsPerPage] = useState(10)
const [sidebarOpen, setSidebarOpen] = useState(false)
const [currentRole, setCurrentRole] = useState({ value: '', label: 'Select Role' })
const [currentPlan, setCurrentPlan] = useState({ value: '', label: 'Select Plan' })
const [currentStatus, setCurrentStatus] = useState({ value: '', label: 'Select Status', number: 0 })
// ** Function to toggle sidebar
const toggleSidebar = () => setSidebarOpen(!sidebarOpen)
// ** Get data on mount
useEffect(() => {
dispatch(getAllData())
dispatch(
getData({
page: currentPage,
perPage: rowsPerPage,
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
})
)
}, [dispatch, store.data.length])
// ** User filter options
const roleOptions = useSelector(state => state.users.roleOptions)
const statusOptions = [
{ value: '', label: 'Select Status', number: 0 },
{ value: '2', label: 'Pending', number: 1 },
{ value: '0', label: 'Active', number: 2 },
{ value: '1', label: 'Inactive', number: 3 }
]
// ** Function in get data on page change
const handlePagination = page => {
dispatch(
getData({
page: page.selected + 1,
perPage: rowsPerPage,
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
})
)
setCurrentPage(page.selected + 1)
}
// ** Function in get data on rows per page
const handlePerPage = e => {
const value = parseInt(e.currentTarget.value)
dispatch(
getData({
page: currentPage,
perPage: value,
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
})
)
setRowsPerPage(value)
}
// ** Function in get data on search query change
const handleFilter = val => {
setSearchTerm(val)
dispatch(
getData({
page: currentPage,
perPage: rowsPerPage,
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: val
})
)
}
// ** Custom Pagination
const CustomPagination = () => {
const count = Number(Math.ceil(store.total / rowsPerPage))
return (
<ReactPaginate
previousLabel={''}
nextLabel={''}
pageCount={count || 1}
activeClassName='active'
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
onPageChange={page => handlePagination(page)}
pageClassName={'page-item'}
nextLinkClassName={'page-link'}
nextClassName={'page-item next'}
previousClassName={'page-item prev'}
previousLinkClassName={'page-link'}
pageLinkClassName={'page-link'}
containerClassName={'pagination react-paginate justify-content-end my-2 pr-1'}
/>
)
}
// ** Table data to render
const dataToRender = () => {
const filters = {
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
}
const isFiltered = Object.keys(filters).some(function (k) {
return filters[k].length > 0
})
if (store.data.length > 0) {
return store.data
} else if (store.data.length === 0 && isFiltered) {
return []
} else {
return store.allData.slice(0, rowsPerPage)
}
}
// ** Opening modal
const openModal= (row) => {
//Here i need to get the value
console.log(openModal)
}
return (
<Fragment>
<Card>
<CardHeader>
<CardTitle tag='h4'>User Lists</CardTitle>
</CardHeader>
<CardBody>
<Row>
<Col md='4'>
<Select
isClearable={false}
theme={selectThemeColors}
className='react-select'
classNamePrefix='select'
options={roleOptions}
value={currentRole}
onChange={data => {
setCurrentRole(data)
dispatch(
getData({
page: currentPage,
perPage: rowsPerPage,
role: data.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
})
)
}}
/>
</Col>
<Col md='4'>
<Select
theme={selectThemeColors}
isClearable={false}
className='react-select'
classNamePrefix='select'
options={statusOptions}
value={currentStatus}
onChange={data => {
setCurrentStatus(data)
dispatch(
getData({
page: currentPage,
perPage: rowsPerPage,
role: currentRole.value,
currentPlan: currentPlan.value,
status: data.value,
q: searchTerm
})
)
}}
/>
</Col>
</Row>
</CardBody>
</Card>
<Card>
<DataTable
noHeader
pagination
subHeader
responsive
paginationServer
columns={columns(openModal)}
sortIcon={<ChevronDown />}
className='react-dataTable'
paginationComponent={CustomPagination}
data={dataToRender()}
subHeaderComponent={
<CustomHeader
toggleSidebar={toggleSidebar}
handlePerPage={handlePerPage}
rowsPerPage={rowsPerPage}
searchTerm={searchTerm}
handleFilter={handleFilter}
/>
}
/>
</Card>
<Sidebar open={sidebarOpen} toggleSidebar={toggleSidebar} />
</Fragment>
)
}
export default UsersList
I'm trying to implement this in react-hook project. Need to get resolved to open the modal and data need to be showed in modal.Tried everything and need to call useState() for define modal but in column.js its showing its violation of hooks components also its just need to export the column so need to get the openModal() event trgger in Table.js
Your code will look like this:
**column.js**
// ** React Imports
import { Link } from 'react-router-dom'
// ** Custom Components
import Avatar from '#components/avatar'
// ** Store & Actions
import { getUser, deleteUser } from '../store/action'
import { store } from '#store/storeConfig/store'
import { useSelector } from 'react-redux'
// ** Third Party Components
import { Badge, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'
import { Slack, User, Settings, Database, Edit2, MoreVertical, FileText, Trash2, Archive } from 'react-feather'
export const columns = (openModal) => [
{
name: '#',
maxWidth: '3px',
selector: 'serial',
sortable: true,
cell: (row, index) => (renderSerialNo(index))
},
{
name: 'User',
minWidth: '138px',
selector: 'user',
sortable: true,
cell: row => (
<Badge className='text-capitalize' color={statusObj[row.user_status]} pill>
{row.user_status_text}
</Badge>
)
},
{
name: 'Actions',
minWidth: '10px',
cell: row => (
<UncontrolledDropdown>
<DropdownItem className='w-100' onClick={() => openModal(row)}>
<Trash2 size={14} className='mr-50' />
<span className='align-middle'>Delete</span>
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
)
}
]```
**Table.js**
```// ** React Imports
import { Fragment, useState, useEffect } from 'react'
// ** Columns
import { columns } from './columns'
// ** Store & Actions
import { getAllData, getData } from '../store/action'
import { useDispatch, useSelector } from 'react-redux'
// ** Third Party Components
import Select from 'react-select'
import ReactPaginate from 'react-paginate'
import { ChevronDown } from 'react-feather'
import DataTable from 'react-data-table-component'
import { selectThemeColors } from '#utils'
import { Card, CardHeader, CardTitle, CardBody, Input, Row, Col, Label, CustomInput, Button } from 'reactstrap'
// ** Styles
import '#styles/react/libs/react-select/_react-select.scss'
import '#styles/react/libs/tables/react-dataTable-component.scss'
const UsersList = () => {
// ** Store Vars
const dispatch = useDispatch()
const store = useSelector(state => state.users)
// ** Get data on mount
useEffect(() => {
dispatch(getAllData())
dispatch(
getData()
)
}, [dispatch, store.data.length])
// ** User filter options
const roleOptions = useSelector(state => state.users.roleOptions)
const openModal= e => {
//need to get the value here
console.log(e)
}
...
return (
<Fragment>
<Card>
<DataTable
responsive
paginationServer
columns={columns(openModal)}
sortIcon={<ChevronDown />}
className='react-dataTable'
paginationComponent={CustomPagination}
data={dataToRender()}
}
/>
</Card>
<Sidebar open={sidebarOpen} toggleSidebar={toggleSidebar} />
</Fragment>
)
}
export default UsersList