React y Redux Toolkit CRUD (con TailwindCSS)
- Install the NPM and NODEJS in your system Nodejs Download
- Check in $path or %path% the nodeJS and npm are on it
C:/Program Files/nodejs- Install also
pnpmpnpm installation, it is more fast thannpm - Install Postman Postman Download
- Install MySQL 5.6.x MySQL Download 5.6.26
- Install Visual Studio Code Visual Studio Download
- I used Vite, the best way to start any front-end project, with Typescrypt and a lot of templates:
npm init vite@latest react_redux_CRUD --template react-ts- Following the instruccions, install the applications based on the
package.jsonfile.
pnpm installThis is an example running with pnpm
3. And run the application.
pnpm dev- Some changes in the "vite.config.ts" file, based on Create react app vs Vite:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path' // pnpm install -D @types/node
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }],
},
})- Install the pending elements showing as error in
path
pnpm install -D @types/nodepnpm install @reduxjs/toolkit react-redux
2. Create a "redux" directory, and put there the "store.ts" file.
3. Write in this file this first line:
import { configureStore } from "@reduxjs/toolkit";- the next lines could be:
const store = configureStore({});
export default store;or
export default configureStore({});I used the second one.
- In the main file "App.tsx" add a
Providerto include all the others
import { Provider } from "react-redux";- Call the
storefrom ""store.ts".
import store from "./redux/store";- Finally I change the return by this:
<>
<Provider store={store}>
<div className="App">
<h1>Hola Mundo</h1>
</div>
</Provider>
</>- Create a directory called "states" into redux, and create a file called "tasksSlides.ts".
- Create a basic
Sliceto store the values and process to do:
import { createSlice } from '@reduxjs/toolkit'
export const tasksSlice = createSlice({
name: 'taskSlice',
initialState: [],
reducers:{
}
});
export default tasksSlice.reducer;- This is the "index.ts" file for this "states" directory:
export * from './tasksSlices';- This is how I use this
sliceinto the "store.ts" file:
export default configureStore({
reducer: {
task: tasksSlice.reducer,
}
});- I create a "pages" directory and add a Component called "Home.tsx"
- The "App.tsx" file to change and show the new page called "Home".
<>
<Provider store={store}>
<div className="App">
<Home/>
</div>
</Provider>
</>- In the "Home.tsx" file import the
useSelectorfrom'react-redux' - Add a "models" directory with a "task.model.ts" file.
export interface TaskInterface{
id: string;
name: string;
}- Assigned this to "tasksSlices.ts", to use in the InitalState, with an empty array of ths data from
TaskInterface. - Very important to assign types or Interface in the "store.ts" file.
export interface AppStore{
tasks : TaskInterface[];
}
export default configureStore<AppStore>({
reducer: {
tasks: tasksSlice.reducer,
}
});- Into "Home.tsk" file call the
useSelector, using this format:
const tasksState = useSelector<AppStore>(state => state.tasks);To show in a console.log.
- Added two new basic Components, called: TaskForm, and TaskList.
- The new components are added to "Home.tsx" file.
- Complete the
initialStatein "tasksSlices.ts" file.
const initialState: TaskInterface[] = [
{ id: "1", title: "Task1", description: "Task1 description", completed:false,},
{ id: "2", title: "Task2", description: "Task2 description", completed:false,}];- Then I have to chenge the
TaskInterfacein "task.model.ts" file:
export interface TaskInterface{
id: string;
title: string;
description: string;
completed: boolean;
}- Remove or become to Comment the
useSelectorin "Home.tsx" file. - Put the
useSelectorin the "TaskList.tsx" file:
const tasksState = useSelector((state:AppStore) => state.tasks );- Use to show the list of elements
{tasksState.map(task =>(
<div key={task.id}>
<h3>{task.title}</h3>
<p>{task.description}</p>
</div>
))} - to "taskForm.tsk" component, add an
<input>,<textarea>, and a<button>. - Add an
useStatejust with an empty title, and description:
const [task, setTask] = useState({ title:'', description: '' });- put an Event manager when Change, and assign to the
<input>, and<textarea>
const handleChange = (e:any) => {
console.log('event:', e.target.value, e.target.name);
};- The
handleChangemethod change to store - For the
<button>I associate and event calledhandleSubmit
const handleSubmit = (e:any) => {
e.preventDefault(); // Avoid to Page refresing
console.log(task);
}- In the "tasksSlices.ts" file add finale a first function, into
reducers:{.
reducers: {
addTask: (state, action) =>{
console.log(state, action);
}- Add to "TaskForm.tsx" and
useDispatch()
const dispatch=useDispatch();- The
handleSubmitto add thisdispatch.
dispatch(addTask(task));- Correction to
addTaskof the "taslSlice.ts" file, to really add an object.
addTask: (state, action) =>{
state.push(action.payload);
}- There are and error, because the "id" is empty when it shows in "TaskList", then install a Generator of ID:
pnpm i uuid- As well install the types for this
uuid:
npm i --save-dev @types/uuid- Add the new generator of
uuidin the "TaskForm.tsx" file.
import { v4 as uuid } from 'uuid';- Use this uuid when you are going to use the
dispatch(addTask(:
dispatch(addTask({
...task,
id: uuid(),
}));- Add a button in "TaskList.tsx" file by each Task:
<button onClick={()=>handleDelete(task.id)}>Delete</button>- By now the
handleDeletemethod with a consol.log with the id
const handleDelete = (id:string) => {
console.log('event:', id);
};- Add a new
reducersinto "TaskSlice.ts" file:
deleleteTask: (state, action) =>{
console.log(action.payload);
}- Add in the export list:
export const {addTask, deleteTask} = tasksSlice.actions;- in the "TaskList.tsx" add the
useDispatchfrom'reac-redux'.
const dispatch=useDispatch();- Import the
delTaskfrom "../redux" directory
import { AppStore, deleteTask } from "../redux";- Call the
deleteTaskin thehandleDelete
const dispatch=useDispatch();
const handleDelete = (id: string) => {
dispatch(deleteTask(id));
}; - Change the
deleleteTaskwith the find an element in the array:
const taskFound = state.find( task => task.id === action.payload);- Finally with the Task Found, lets to delete of array with
split, into a conditional:
if (taskFound) {
state.splice(state.indexOf(taskFound),1);
}- Install the react-router-dom
pnpm install react-router-dom- Also remember you are working with
TYPESCRIPT, then you need install@types/:
pnpm install @types/react-router-dom- We are working in "Home.tsx" file, first deleting the "Home" message into
<div>. - Import the
BrowserRouter,Routes, andRoutefromreact-router-dom:
import { BrowserRouter, Routes, Route } from 'react-router-dom';- Add the the
BrowserRouter, andRoutescomponents are over theTaskList, andTaskForm:
<BrowserRouter>
<Routes>
<TaskForm />
<TaskList />
</Routes>
</BrowserRouter>- Starting to use the
Route, calling theTaskListas a root, andTaskFormin site'/create-task'.
<Routes>
<Route path='/' element={<TaskList/>} />
<Route path='/create-task' element={<TaskForm/>} />
</Routes>- Import in "TaskForm.tsx" the
useNavigatefromreac-router-dom. - I'm going to instantiate as the
useDispatch():
const navigate = useNavigate();- After execute the dispatch , back to the original page.
- Add a "Navbar.tsx" component to link the pages, in "components" directory:
import { Link } from "react-router-dom";
function Navbar() {
return (
<nav className="navbar">
<ul>
<li> <Link to="/"> Home</Link>{" "} </li>
<li> <Link to="/create-task"> Create Task</Link> </li>
</ul>
</nav>
);
}
export default Navbar;- Update the barrel or the "index.ts" from "components" directory.
export { default as Navbar } from './Navbar';
export { default as TaskForm } from './TaskForm';
export { default as TaskList } from './TaskList';- Call the
<Navbar/>below of<BrowserRouter>, in "Home.tsk" file. - in "TaskList" can add a quantity of task as first element after
<div>:
<h1>Tasks: {tasksState.length}</h1>Horizontal Navigation Bar Examples
/* Elements of the "Navbar.tsx" file */
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
li {
float: left;
}
li a {
display: block;
color: white;
text-align: right;
padding: 14px 16px;
text-decoration: none;
}
/* Change the link color to #111 (black) on hover */
li a:hover {
background-color: #111;
}- Add a Link in the "TaskList.tsk" component, below the Delete
<button>.
<Link to="">Update</Link>- Add a route into "Home.tsx" file:
<Route path={`/update-task/:id`} element={<TaskForm />} />- Correction to the pending in
Linkof "TaskLst.tsx" file:
<Link to={`/update-task/${task.id}`}>Update</Link>- Verify in "TaskForm.tsx" file the ID with
useParamsfirst getparams:
const params = useParams();- Next into
useEffect, showparamsin "TaskForm.tsx" file:
useEffect(()=>{
console.log(params);
}, [params.id, tasks]); - With the
useSelectorwe get thestateor tasks list fromAppStore:
const tasks = useSelector((state: AppStore) => state.tasks);- Replace the
console.logofuseEffectin "TaskForm.tsx" file, with a conditional:
if (params.id){
setTask(tasks.find(task => task.id === params.id));
}- Adding the value for each data to read later by screen:
<input
value={task.title}- and
<textarea
value={task.description}- For
handleSubmit, add a contitional , to know if is add or update in "TaskForm.tsx" file:
if (params.id) {
dispatch();
} else {
dispatch( addTask({ ...task, id: uuid(), }) );
}- Add an
updateTask: (state, action) =>{in "tasksSlices.ts" file.
updateTask: (state, action) =>{
const {id, title, description} = action.payload;
const taskFound = state.find( task => task.id === id);
if(taskFound) {
taskFound.title = title;
taskFound.description = description;
}
},- Correction to the edit
dispatchin "TaskForm.tsx" file:
if (params.id) {
dispatch( updateTask(task));- Go the Tailwind CSS, and select "Framework Guides" option.
- Becasue I used the "Vite", select "Vite".
- Run this command of the 2 step "Install Tailwind CSS" option, in a terminal:
pnpm install -D tailwindcss postcss autoprefixer- Run this process to Initialize the tailwind css or create the config Tailwind file:
npx tailwindcss init -p- Add the paths into the
content: [],to all of your template files in your "tailwind.config.js" file.
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],- Delete all code into "index.css" file.
- Add the
@tailwinddirectives for each of Tailwind’s layers to the "index.css" file.
@tailwind base;
@tailwind components;
@tailwind utilities;- For fisrt Segment
<>, become in a<div>and add aclassName:
<div className="bg-zinc-200 h-screen text-blue-900">- Add another
<div>before of<Navbar>, with aclassName:
<div className="flex items-center justify-center h-full">- Add a another
<div>element into "home.tsx" file, before of<Navbar>:
<div className="w-4/6">- Delete all from "App.css" file.
- Add to
<nav>element aclassName:
<nav className="flex justify-end items-center py-4">- Add a
classNameto<h1>element:
<h1 className="flex justify-between items-center py-4">- Add a
classNameinto "Navbar.tsx" for the<Link>element:
className="text-neutral-500 hover:text-neutral-700"- The "TaskList" to put in a grid, first arround all the
tasks.mapadd a<div>element with aclassName:
<div className="grid grid-cols-3 gap-3">- Put a each element a
className, to create a cards .
<div key={task.id} className="bg-neutral-500 text-neutral-100 p-1 rounded-md">- Move in "TaskList.tsx"
<button>and<Link>below the<h3>{task.title}</h3>and closed in a<div>with a class:
<h3>{task.title}</h3>
<div className="flex gap-x-2">
<button onClick={() => handleDelete(task.id)} className="btn bg-red-800 hover:bg-red-600 text-xs rounded-md px-2 py-1">Delete</button>
<Link to={`/update-task/${task.id}`} className="btn bg-blue-800 hover:bg-blue-600 text-xs rounded-md px-2 py-1">Update</Link>
</div>- Put a
<header>over the<h3>, and closing wiht the</div>of<button>and<Link>, with aclassnamein "TaskList.tsx" file:
<header className="flex justify-between">- For "TaskForm.tsx" add
classNamein<formelement.
<form onSubmit={handleSubmit} className="bg-neutral-500 text-neutral-100 rounded-md max-w-sm p-4">- Add a Label before the
<inputelement:
<label htmlFor="title" className="block text-xs font-bold mb-1">Task:</label>- Add a Label before the
<textareaelement:
<label htmlFor="description" className="block text-xs font-bold mb-1 mt-3">Description:</label>- Add a
classNameto the<inputelement:
className="w-full p-2 rounded-md bg-zinc-700"- Add the
classNameto the<textareaelement:
className="w-full p-2 rounded-md bg-zinc-700"- Complete the "Save"
<buttonadding aclassName:
<button className="btn bg-green-800 hover:bg-green-600 text-xs rounded-md px-2 py-1">