npm install @mui/x-data-grid
npm install @mui/material @emotion/react @emotion/styled
User.tsx | Need to Pass, Column(Table Header), Row(Data to display)
import { GridColDef } from "@mui/x-data-grid";
import DataTable from "../../components/dataTable/DataTable";
import { userRows } from "../../data";
import "./users.css";
const Users = () => {
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 90 },
{
field: "avatar",
headerName: "Avater",
width: 100,
renderCell: (params) => {
return <img src={params.row?.img || "noavatar.png"} alt="" />;
},
},
{
field: "status",
headerName: "Status",
width: 100,
type: "boolean",
},
{
field: "firstName",
headerName: "First name",
width: 150,
editable: true,
},
{
field: "lastName",
headerName: "Last name",
width: 150,
editable: true,
},
{
field: "fullName",
headerName: "Full name",
description: "This column has a value getter and is not sortable.",
sortable: false,
width: 160,
valueGetter: (_value, row) =>
`${row.firstName || ""} ${row.lastName || ""}`,
},
];
return (
<div className="users">
<div className="info">
<h2>Users</h2>
<button>Add New User</button>
</div>
<DataTable slug="users" columns={columns} rows={userRows} />
</div>
);
};
export default Users;
Need to provide hight and width explectly otherwisely it may not work.
import { DataGrid, GridColDef, GridToolbar } from "@mui/x-data-grid";
interface Props {
columns: GridColDef[];
rows: object[];
slug: string;
}
const DataTable = ({ columns, rows, slug }: Props) => {
const handelDelete = (id: number) => {
// axios.delete(`/api/${slug}/${id}`);
console.log(id);
};
const actionColumn: GridColDef = {
field: "action",
headerName: "Action",
width: 200,
renderCell: (params) => {
return (
<div className="action">
<Link to={`/${slug}/${params.row.id}`}>
<img src="view.svg" alt="" />
</Link>
<div className="delete" onClick={() => handelDelete(params.row.id)}>
<img src="delete.svg" alt="" />
</div>
</div>
);
},
};
return (
<div>
<DataGrid
className="dataGrid"
rows={rows}
columns={[...columns, actionColumn]}
initialState={{
pagination: {
paginationModel: {
pageSize: 10,
},
},
}}
slots={{ toolbar: GridToolbar }}
slotProps={{
toolbar: {
showQuickFilter: true,
quickFilterProps: { debounceMs: 500 },
},
}}
pageSizeOptions={[5]}
checkboxSelection
disableRowSelectionOnClick
disableColumnFilter
disableDensitySelector
disableColumnSelector
/>
</div>
);
};
export default DataTable;
Users.tsx | Great tutorial for layouting
const Users = () => {
const [open, setOpen] = useState(false);
const setOpenFn = (status: boolean) => {
setOpen(status);
};
return (
<div className="users">
<div className="info">
<h2>Users</h2>
<button onClick={() => setOpen(true)}>Add New User</button>
</div>
<DataTable slug="users" columns={columns} rows={userRows} />
{open && <Add slug="user" columns={columns} setOpenProps={setOpenFn} />}
</div>
);
};
Add.tsx
import { FormEvent } from "react";
interface Props {
slug: string;
columns: GridColDef[];
setOpenProps: (status: boolean) => void;
}
const Add = ({ slug, columns, setOpenProps }: Props) => {
const handelSubmit = (e: FormEvent) => {
e.preventDefault();
// axios.post()
console.log("From Submitted");
};
return (
<div className="add">
<div className="modle">
<span className="close" onClick={() => setOpenProps(false)}>
x
</span>
<h2>Add new {slug}</h2>
<form onSubmit={handelSubmit} className="form">
{columns
.filter((item) => item.field !== "id" && item.field !== "img")
.map((column) => (
<div className="form__item" key={column.field}>
<label htmlFor={column.field}>{column.headerName}</label>
<input type={column.type} placeholder={column.field} />
</div>
))}
<button className="form__button">Send</button>
</form>
</div>
</div>
);
};
add.css
.add {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.75);
display: flex;
align-items: center;
justify-content: center;
}
.modle {
padding: 5rem;
border-radius: 10px;
background: var(--main-bg);
position: relative;
}
.modle h2 {
color: var(--soft-color);
}
.close {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
line-height: 1;
}
.form {
display: flex;
flex-wrap: wrap;
max-width: 600px;
justify-content: space-between;
}
.form__item {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 2rem;
width: 45%;
}
label {
font-size: 1.4rem;
}
input {
padding: 1rem;
background: transparent;
color: white;
outline: none;
border: 1px solid var(--soft-color);
border-radius: 5px;
}
.form__button {
width: 100%;
padding: 1rem;
cursor: pointer;
}
Product.tsx, User.tsx
import "./product.css";
const Product = () => {
return (
<div className="product">
<Single {...singleProduct} />
</div>
);
};
export default Product;
import "./user.css";
const User = () => {
return (
<div className="user">
<Single {...singleUser} />
</div>
);
};
export default User;
Single.tsx
import "./single.css";
interface Props {
id: number;
img?: string;
title: string;
info: object;
chart: {
dataKeys: { name: string; color: string }[];
data: object[];
};
activities?: { time: string; text: string }[];
}
const Single = ({ id, img, title, info, chart, activities }: Props) => {
return (
<div className="single">
<div className="view">
<div className="profile">
<div className="topInfo">
{img && <img src={img} alt="" />}
<h2>{title}</h2>
<button>Update</button>
</div>
<div className="details">
{Object.entries(info).map((item) => (
<div className="single__item" key={item[0]}>
<span className="itemTitle">{item[0]}: </span>
<span className="itemValue">{item[1]}</span>
</div>
))}
</div>
</div>
<hr />
{chart && (
<div className="elementChart">
<ResponsiveContainer width="100%" height="100%">
<LineChart
width={500}
height={300}
data={chart.data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
{chart.dataKeys.map((dataKey) => (
<Line
key={dataKey.name}
type="monotone"
dataKey={dataKey.name}
stroke={dataKey.color}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
)}
</div>
<div className="activities">
<h2>Latest Activities</h2>
{activities && (
<ul>
{activities.map((activitie) => (
<li key={activitie.text}>
<div>
<h3>{activitie.text}</h3>
<time>{activitie.time}</time>
</div>
</li>
))}
</ul>
)}
</div>
</div>
);
};
export default Single;
npm i @tanstack/react-query
main.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient(); // this line added
// const queryClient = new QueryClient({
// defaultOptions: {
// queries: {
// retry: 4,
// refetchOnWindowFocus: false,
// refetchOnReconnect: false, // go offline and back onlinbe
// refetchOnMount: true, // When data is stale then it will refetch
// },
// },
// }); // this line added
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</React.StrictMode>
);
Users.tsx | If data is not stale refetch will not happen.
const Users = () => {
const [open, setOpen] = useState(false);
const setOpenFn = (status: boolean) => {
setOpen(status);
};
const { isLoading, data } = useQuery({
queryKey: ["users"],
queryFn: () =>
fetch("http://localhost:8800/api/users").then((res) => res.json()),
staleTime: 2 * 60 * 1000, // 2 min (How long data is considired fresh, next time react query refetch data)
});
return (
<div className="users">
<div className="info">
<h2>Users</h2>
<button onClick={() => setOpen(true)}>Add New User</button>
</div>
{isLoading ? (
"Loading..."
) : (
<DataTable slug="user" columns={columns} rows={data} />
)}
{open && <Add slug="user" columns={columns} setOpenProps={setOpenFn} />}
</div>
);
};
Mutation(Add, Update, Delete)
Add an item.
const Add = ({ slug, columns, setOpenProps }: Props) => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: () =>
fetch(`https://react-admin-api-omega.vercel.app/api/${slug}`, {
method: "post",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
id: 111,
img: "",
lastName: "World",
firstName: "Test",
email: "testme@gmail.com",
phone: "123 456 789",
createdAt: "01.02.2023",
verified: true,
}),
}),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [slug],
});
},
});
const handelSubmit = (e: FormEvent) => {
e.preventDefault();
mutation.mutate();
setOpenProps(false);
};
return (
<div className="add">
<div className="modle">
<span className="close" onClick={() => setOpenProps(false)}>
x
</span>
<h2>Add new {slug}</h2>
<form onSubmit={handelSubmit} className="form">
{columns
.filter((item) => item.field !== "id" && item.field !== "img")
.map((column) => (
<div className="form__item" key={column.field}>
<label htmlFor={column.field}>{column.headerName}</label>
<input type={column.type} placeholder={column.field} />
</div>
))}
<button className="form__button">Send</button>
</form>
</div>
</div>
);
};
Delete Item
const DataTable = ({ columns, rows, slug }: Props) => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (id: number) => {
return fetch(
`https://react-admin-api-omega.vercel.app/api/${slug}/${id}`,
{
method: "delete",
}
);
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [slug],
});
},
});
const handelDelete = (id: number) => {
mutation.mutate(id);
};
const actionColumn: GridColDef = {
field: "action",
headerName: "Action",
width: 100,
renderCell: (params) => {
return (
<div className="action">
<Link to={`/${slug}/${params.row.id}`}>
<img src="view.svg" alt="" />
</Link>
<div className="delete" onClick={() => handelDelete(params.row.id)}>
<img src="delete.svg" alt="" />
</div>
</div>
);
},
};
return (
<div>
<DataGrid
style={{ width: "100%" }}
className="dataGrid"
rows={rows}
columns={[...columns, actionColumn]}
initialState={{
pagination: {
paginationModel: {
pageSize: 10,
},
},
}}
slots={{ toolbar: GridToolbar }}
slotProps={{
toolbar: {
showQuickFilter: true,
quickFilterProps: { debounceMs: 500 },
},
}}
pageSizeOptions={[5]}
checkboxSelection
disableRowSelectionOnClick
disableColumnFilter
disableDensitySelector
disableColumnSelector
/>
</div>
);
};