🍏 iOS / Mac OSDas Wochenend-Gewinnspiel mit einem IFA-Goodie-Bag(04.09.2026 um 20:00 Uhr)
🔧 AI Nachrichten GPT-6 Astra: OpenAI setzt auf Computersteuerung und Tempo(04.09.2026 um 15:26 Uhr)
🔧 AI Nachrichten New Beam Ultra & Ace Ultra arrive with AI-powered Sonos 27(01.09.2026 um 19:55 Uhr)
🍏 iOS / Mac OSApple still on track for a revolutionary iPhone 20 in 2027(04.09.2026 um 18:53 Uhr)
🔧 AI Nachrichten Evernote 11.30.6(24.08.2026 um 17:14 Uhr)
🔧 AI Nachrichten Speed Limiters, Short Cables, and Other EV Road Trip Revelations(26.08.2026 um 21:51 Uhr)
🍏 iOS / Mac OSDas Wochenend-Gewinnspiel mit einem IFA-Goodie-Bag(04.09.2026 um 20:00 Uhr)
🔧 AI Nachrichten GPT-6 Astra: OpenAI setzt auf Computersteuerung und Tempo(04.09.2026 um 15:26 Uhr)
🔧 AI Nachrichten New Beam Ultra & Ace Ultra arrive with AI-powered Sonos 27(01.09.2026 um 19:55 Uhr)
🍏 iOS / Mac OSApple still on track for a revolutionary iPhone 20 in 2027(04.09.2026 um 18:53 Uhr)
🔧 AI Nachrichten Evernote 11.30.6(24.08.2026 um 17:14 Uhr)
🔧 AI Nachrichten Speed Limiters, Short Cables, and Other EV Road Trip Revelations(26.08.2026 um 21:51 Uhr)

26 🕛 kürzlich 7 Min Lesezeit CVE-RADAR
0

How to Build a Basic CRUD App with NextJS, TS, React, Redux-Tookit and MUI5 Components

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Learn how to build a NextJS application that uses redux-observable to manage side effects.






1. Introduction



In this project, we will learn how to build a basic CRUD application using NextJS Typescript and Redux-Toolkit to manage the side effects.






2. Tech Stack



  • NextJS - React framework

  • Typescript

  • Redux-Toolkit - a toolset for redux state management

  • MUI5 - UI components

  • Formik - form management

  • Yup - form validation

  • Prisma - database




3. Coding






3.1 Setup the database



In this example, we use Prisma, a lightweight server-side database, as our data store. To enable Prisma in our project, we need to add the following dependencies:



  • "@prisma/client": "^3.8.0"

  • "prisma": "^3.8.0" - devDependencies

Create the Prisma data source configuration




CODE
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}

generator client {
provider = "prisma-client-js"
binaryTargets = ["native"]
}

model User {
id Int @id @default(autoincrement())
firstName String
lastName String
email String?
birthDate DateTime?
}






And initial schema and migration SQL inside src/prisma folder.




CODE
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"email" TEXT,
"birthDate" DATETIME
);






To build the local database, execute:



npx prisma migrate dev --name=init



The next step is whether you want to set up the redux store or view. It depends on your programming style. As for me, I normally go with the store or core of the framework.






4. Setup the Application Store






4.1 Application Store



The application store is derived from the NextJS example. It persists the combined reducers and middlewares from Redux-toolkit's createApi.




CODE
import {configureStore} from '@reduxjs/toolkit';
import {UsersService} from "./UserService";
import {UserSlice} from "./slices/UserSlice";

export function makeStore() {
return configureStore({
reducer: {
[UsersService.reducerPath]: UsersService.reducer,
user: UserSlice.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(
UsersService.middleware
),
})
}

const store = makeStore()

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>

// Inferred type: {users: UsersState}
export type AppDispatch = typeof store.dispatch

export default store;









4.2 User Service/API



This file contains all the API endpoints for the user which uses query and mutation to edit the user's data. It uses the header defined in the BaseService file.




CODE
import {BaseService} from "./BaseService";
import {UserType} from "./types/UserType";
import {createSlice} from "@reduxjs/toolkit";

interface Pagination {
offset: number,
limit: number
}

export const UsersService = BaseService.injectEndpoints({
endpoints: (build) => ({
getUsers: build.query<UserType[], Pagination>({
query: (param: Pagination) => `/users?offset=${param.offset}&limit=${param.limit}`,
providesTags: [{type: "User", id: "LIST"}]
}),
getUser: build.query<UserType, number>({
query: (id) => ({
url: `/users/${id}`,
})
}),
createUser: build.mutation<UserType, UserType>({
query: (body: UserType) => ({
url: `/users`,
method: 'POST',
body
}),
invalidatesTags: [{type: "User", id: "LIST"}]
}),
updateUser: build.mutation<UserType, Pick<UserType, 'id'> & Partial<UserType>>({
query: ({id, ...body}) => ({
url: `/users/${id}`,
method: 'PATCH',
body
}),
invalidatesTags: [{type: "User", id: "LIST"}]
}),
deleteUser: build.mutation<void, number>({
query: (id) => ({
url: `/users/${id}`,
method: 'DELETE',
}),
invalidatesTags: [{type: "User", id: "LIST"}],
}),
}),
overrideExisting: true,
})

export const {
useGetUsersQuery, useGetUserQuery,
useCreateUserMutation, useDeleteUserMutation, useUpdateUserMutation
} = UsersService;









5. UI Side



We implemented a traditional list and swipeable detail pages in the UI using MUI5 components.



The User list page uses MUI5 components to render a table. On page load implemented using useEffect, it calls an API to retrieve the list of users. Delete action is also implemented on this page.




CODE
import React, {useState} from 'react';
import {
Alert,
Box,
Button,
ButtonGroup,
CircularProgress,
Container,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Snackbar,
SwipeableDrawer,
Table,
TableBody,
TableCell,
TableContainer,
TableFooter,
TableHead,
TablePagination,
TableRow
} from "@mui/material";
import moment from "moment";
import {Delete, Edit, PersonAdd} from "@mui/icons-material";
import {useAppDispatch} from 'services/hooks';

import {useRouter} from "next/router";
import {NextPage} from "next";
import {useDeleteUserMutation, useGetUsersQuery} from "../../services/UserService";
import Footer from "../../components/Footer/Footer";
import UserDetail from "./components/UserDetail";
import {UserType} from "../../services/types/UserType";
import {clearUser, setUser} from "../../services/slices/UserSlice";

const EMPTY_DIALOG = {
open: false,
text: '',
title: '',
onConfirm: () => {
},
onCancel: () => {
}
}

const EMPTY_ALERT = {
open: false,
text: '',
};

const Users: NextPage = () => {

const router = useRouter();
const dispatch = useAppDispatch();
const [offset, setOffset] = useState(0);
const [limit, setLimit] = useState(10);
const [dialog, setDialog] = useState(EMPTY_DIALOG);
const [alert, setAlert] = useState(EMPTY_ALERT);

const {
data,
error,
isLoading: isUsersLoading,
isSuccess: isUsersQueried,
isFetching: isUsersFetching,
isError: isUsersError
} = useGetUsersQuery({offset: (offset * limit), limit});

const [deleteUser, {
data: deletedUser,
isLoading: isUserDeleting,
isSuccess: isUserDeleted
}] = useDeleteUserMutation();

const drawerBleeding = 56;
const [openDrawer, setOpenDrawer] = React.useState(false);

const handleChangeRowsPerPage = ({target: {value}}) => {
setLimit(value);
};

const handleChangePage = (_, nextPage) => {
setOffset(nextPage);
};

const handleDeleteUser = (userId: number) => async () => {
try {
await deleteUser(userId).unwrap();
setAlert({
open: true,
text: `Successfully deleted user: ${userId}`,
});
resetDeleteDialog();

} catch (error) {
console.log(`Error: Failed deleting user with id ${userId}`);
}
};

const resetDeleteDialog = () => {
setDialog(EMPTY_DIALOG);
}

const openDeleteDialog = (userId: number) => () => {
setDialog({
open: true,
title: 'Delete user',
text: `Delete user: ${userId}?`,
onConfirm: handleDeleteUser(userId),
onCancel: () => resetDeleteDialog()
});
}

const resetAlert = () => {
setAlert(EMPTY_ALERT);
}

const editUser = (user: UserType) => () => {

setOpenDrawer(true);
dispatch(setUser(user));
};

const toggleEditDrawer = (newOpen: boolean) => () => {

if (!newOpen) {
dispatch(clearUser());
}
setOpenDrawer(newOpen);
};

const renderTable = (users: UserType[], count: number) => {
const hasUsers = count > 0;

return (
<React.Fragment>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell colSpan={6} align="right">
<Button variant="outlined" color="primary" onClick={toggleEditDrawer(true)}>
<PersonAdd/>
</Button>
</TableCell>
</TableRow>
<TableRow>
<TableCell>Id</TableCell>
<TableCell>First name</TableCell>
<TableCell>Last name</TableCell>
<TableCell>Email</TableCell>
<TableCell>Birth date</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{hasUsers ? (
users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.id}</TableCell>
<TableCell>{user.firstName}</TableCell>
<TableCell>{user.lastName}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
{moment.utc(user.birthDate).format('MM-DD-YYYY')}
</TableCell>
<TableCell sx={{textAlign: "right"}}>
<ButtonGroup>
<Button onClick={editUser(user)}>
<Edit/>
</Button>
<Button onClick={openDeleteDialog(user.id)}>
{<Delete/>}
</Button>
</ButtonGroup>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6}>No users found.</TableCell>
</TableRow>
)}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
count={count}
page={offset}
rowsPerPage={limit}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
<SwipeableDrawer
anchor="bottom"
open={openDrawer}
onClose={toggleEditDrawer(false)}
onOpen={toggleEditDrawer(true)}
swipeAreaWidth={drawerBleeding}
disableSwipeToOpen={false}
ModalProps={{
keepMounted: true,
}}
>
<UserDetail toggleEditDrawer={toggleEditDrawer}></UserDetail>
</SwipeableDrawer>
</React.Fragment>
);
}

const renderBody = () => {
if (isUsersQueried) {
const {users, count} = data;

return (isUsersFetching || isUsersLoading) ?
<Box sx={{display: 'flex'}}>
<CircularProgress/>
</Box> :
renderTable(users, count)
}
}

const renderError = () => {
return isUsersError && <Alert severity="error">{JSON.stringify(error)}</Alert>;
}

return (
<Container maxWidth={"md"} fixed>
{renderError()}
{renderBody()}
<Footer></Footer>
<Dialog
open={dialog.open}
onClose={dialog.onCancel}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{dialog.title}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{dialog.text}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={dialog.onCancel}>Disagree</Button>
<Button onClick={dialog.onConfirm} autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
<Snackbar
open={alert.open}
autoHideDuration={6000}
onClose={resetAlert}
message={alert.text}
/>
</Container>
);
}

export default Users;






And here is the User detail page. We use formik to build the form and yup to define the validation rules. This is triggered when we submit the form.



This is very important. Notice that inside the useEffect we are setting the form values in edit mode when the user is using formik. This useEffect will only be triggered if the value of the user changes to avoid too many re-renders errors.






6. Git Repository



As always, the complete source code is available on GitHub:



  • Vollständiger Original-Bericht
    Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
    ↗ Original-Artikel auf dev.to lesen
    Wie bewertest du diesen Beitrag?
    1 Klick Feedback
    Teilen mit Netzwerk & Team:

    Community-Analysen & Experten-Meinungen 0

    Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
    Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
    Community Pulse: Relevanz-Einschätzung
    1 Klick Experten-Votum
    🔴 Akute Relevanz 46%
    🟡 In Evaluierung 30%
    🟢 Keine Auswirkung 15%
    Spannende Innovation 9%
    Verwandte Story-Cluster & Quellen (Vektor-KI)
    Port 8095 Engine
    1 Quelle
    Lexar auf der IFA 2026: Lexar zeigt erstmals ultradünne Portable-SSD „Muse“
    1 Quelle
    Das Wochenend-Gewinnspiel mit einem IFA-Goodie-Bag
    1 Quelle
    GPT-6 Astra: OpenAI setzt auf Computersteuerung und Tempo
    Ähnliche Beiträge
    🔍 Verwandte News

    Auch interessante Nachrichten How to Build a Basic CRUD App with NextJS, TS, React, Redux-Tookit and MUI5 Components

    Thematisch verwandte Begriffe: Build, Basic, CRUD, with · 6 Treffer

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...