This app displays a list of books from the Redux store using React components. It uses Redux Toolkit for state management and TypeScript for type safety and clarity.
π§± Application Entry: main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { RouterProvider } from 'react-router'
import router from './routes/index.tsx'
import { Provider } from 'react-redux'
import { store } from './redux/store.ts'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<RouterProvider router={router}/>
</Provider>
</StrictMode>,
)
π Explanation:
- React 18+βs
createRoot
API is used to render the app. - The
<Provider>
fromreact-redux
injects the Redux store across the component tree. - Routing is handled via
RouterProvider
, which suggests the use ofreact-router
.
π§ Defining the Data Model: type.ts
export interface IBook {
id: string
title: string
author: string
genre: string
isbn: string
description: string
copies: number
available: boolean
}
Using TypeScript’s interface
, we define a strongly-typed structure for book data. This ensures consistency and helps avoid runtime errors due to shape mismatches.
ποΈ Setting Up Redux Store: store.ts
import { configureStore } from "@reduxjs/toolkit";
import bookReducer from "./features/book/bookSlice";
export const store = configureStore({
reducer: {
library: bookReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Key Concepts:
configureStore
simplifies store setup and automatically includes useful middleware like Redux Thunk and DevTools.- We slice our state using
bookReducer
under the namespacelibrary
. RootState
andAppDispatch
are TypeScript helpers for typed hooks.
πͺ Custom Hook for Typed useSelector
: hook.ts
import { useSelector } from "react-redux";
import type { RootState } from "./store";
export const useAppSelector = useSelector.withTypes<RootState>();
This hook allows us to use useSelector
with automatic type inference. This leads to safer and more accurate access to state values.
π¦ Redux Slice (Feature State): bookSlice.ts
import type { IBook } from "@/types";
import { createSlice } from "@reduxjs/toolkit";
interface InitialState {
books: IBook[];
}
const initialState: InitialState = {
books: [
{
id: "1",
title: "Sample Book",
author: "John Doe",
genre: "Fiction",
isbn: "123-4567890123",
description: "This is a sample book description.",
copies: 5,
available: true,
},
...
]
}
const bookSlice = createSlice({
name: "book",
initialState,
reducers: {},
});
export default bookSlice.reducer;
Here, createSlice
helps encapsulate Redux logic in a concise and readable manner:
initialState
contains sample book entries.- No
reducers
are defined yet, but they can easily be added to handle actions likeaddBook
,deleteBook
,toggleAvailability
, etc.
π Displaying the Books: books.tsx
import BookCard from "@/components/module/books/BookTable";
import { useAppSelector } from "@/redux/hook";
export default function Books() {
const books = useAppSelector((state) => state.library.books);
return (
<div className="space-y-4 p-4">
<div className="text-2xl font-semibold tracking-tight">π All Books</div>
<BookTable books={books} />
</div>
);
}
Highlights:
- Uses the custom
useAppSelector
hook to retrieve books from the Redux state. - Passes the data down to the
BookCard
component for display.
π‘ Rendering the Book Table: BookTable.tsx
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Pencil, Trash2 } from "lucide-react";
import type { IBook } from "@/types";
interface BookTableProps {
books: IBook[];
}
export default function BookTable({ books }: BookTableProps) {
return (
<Card>
<CardContent className="p-4">
<Table>
<TableHeader>
<TableRow>
<TableHead>Title</TableHead>
<TableHead>Author</TableHead>
<TableHead>Genre</TableHead>
<TableHead>ISBN</TableHead>
<TableHead>Copies</TableHead>
<TableHead>Availability</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{books.map((book) => (
<TableRow key={book.isbn}>
<TableCell>{book.title}</TableCell>
<TableCell>{book.author}</TableCell>
<TableCell>{book.genre}</TableCell>
<TableCell>{book.isbn}</TableCell>
<TableCell>{book.copies}</TableCell>
<TableCell>
{book.available ? (
<span className="text-green-600">Available</span>
) : (
<span className="text-red-600">Checked Out</span>
)}
</TableCell>
<TableCell className="flex gap-2">
<Button size="sm" variant="outline">
<Pencil className="h-4 w-4 mr-1" />
Edit
</Button>
<Button size="sm" variant="destructive">
<Trash2 className="h-4 w-4 mr-1" />
Delete
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
);
}
Notable UI Features:
- Displays book data in a tabular format.
- Action buttons for Edit and Delete suggest future interactivity.
- Uses Lucide icons for visual clarity.
π§© What Can Be Improved or Extended?
Here are some ideas for making this application more dynamic:
- Add Reducers: To support adding, editing, and deleting books.
- Forms: Use a form (e.g., with React Hook Form) to handle book input.
- Persistence: Integrate localStorage or a backend API.
- Routing: Add detail pages for each book.
- Search/Filter: Add functionality to filter or search books by title or author.
This simple library app showcases how Redux Toolkit and TypeScript make React state management predictable, scalable, and type-safe. With modular structure, clean type definitions, and maintainable code, youβre already started the journey to a full-featured library system.
π Source Code: Click Here