REACT.JS - Part 5
Reusable Components, Code Splitting, Forms & Validation
Project Structure
student-manager/
│
├── src/
│ │
│ ├── components/
│ │ ├── Button.jsx
│ │ ├── Input.jsx
│ │ ├── StudentCard.jsx
│ │ └── Loader.jsx
│ │
│ ├── pages/
│ │ ├── Home.jsx
│ │ ├── Students.jsx
│ │ ├── AddStudent.jsx
│ │ └── Dashboard.jsx
│ │
│ ├── schemas/
│ │ └── studentSchema.js
│ │
│ ├── services/
│ │ └── studentService.js
│ │
│ ├── hooks/
│ │ └── useStudents.js
│ │
│ ├── App.jsx
│ └── main.jsx
│
├── package.json
└── index.htmlThe important thing is that each folder has a responsibility.
Reusable Components
Imagine we need several buttons.
Without reusable components, you might write:
<button className="btn">Add Student</button>
<button className="btn">Save Student</button>
<button className="btn">Update Student</button>If you later change the design, you have to change multiple places.
Instead, create:
components/
└── Button.jsxButton.jsx
function Button({ children, type = "button", onClick }) {
return (
<button
type={type}
onClick={onClick}
className="bg-blue-600 text-white px-4 py-2 rounded"
>
{children}
</button>
);
}
export default Button;Now anywhere in your application:
import Button from "../components/Button";
function AddStudent() {
return (
<div>
<h1>Add Student</h1>
<Button>
Add Student
</Button>
</div>
);
}
export default AddStudent;You can reuse it:
<Button>Save</Button>
<Button>Update</Button>
<Button>Register</Button>Understanding children
This:
<Button>
Add Student
</Button>passes:
Add Studentinto the children prop.
Therefore:
function Button({ children }) {
return (
<button>
{children}
</button>
);
}is essentially saying:
Whatever is placed between
<Button>and</Button>, put it inside the button.
You can even put more complex content:
<Button>
Save Student
</Button>or:
<Button>
Delete Student
</Button>Reusable Input Component
Instead of repeating this:
<input />throughout the application, create:
components/
└── Input.jsxfunction Input({
label,
type = "text",
placeholder,
error,
...props
}) {
return (
<div className="mb-4">
<label className="block mb-1">
{label}
</label>
<input
type={type}
placeholder={placeholder}
{...props}
className="border p-2 w-full rounded"
/>
{error && (
<p className="text-red-500 text-sm">
{error}
</p>
)}
</div>
);
}
export default Input;Now:
<Input
label="Student Name"
placeholder="Enter student name"
/>Another:
<Input
label="Email"
type="email"
placeholder="Enter email"
/>Another:
<Input
label="Age"
type="number"
placeholder="Enter age"
/>This is the power of reusable components.
Folder Structure
Now let’s understand why we created different folders.
components
Contains reusable UI:
components/
├── Button.jsx
├── Input.jsx
├── StudentCard.jsx
└── Loader.jsxThese components can be used by multiple pages.
pages
Contains complete screens:
pages/
├── Home.jsx
├── Students.jsx
├── AddStudent.jsx
└── Dashboard.jsxFor example:
function Home() {
return (
<div>
<h1>Student Manager</h1>
<p>Manage your students.</p>
</div>
);
}
export default Home;schemas
Contains validation rules.
schemas/
└── studentSchema.jsInstead of putting validation logic everywhere, we keep it here.
services
Contains API-related logic.
services/
└── studentService.jsFor example:
export async function getStudents() {
const response = await fetch(
"https://example.com/api/students"
);
return response.json();
}Your component doesn’t need to know how the API works.
Code Splitting
Imagine our application has:
Home
Students
Dashboard
Add Student
Reports
SettingsThe user initially visits:
/Why should the browser immediately download all the JavaScript for:
Dashboard
Reports
Settingswhen the user hasn’t visited them?
Code splitting solves this.
Normal Import
Normally:
import Dashboard from "./pages/Dashboard";The component is included in the application’s JavaScript bundle.
Lazy Loading
Instead:
import { lazy } from "react";
const Dashboard = lazy(
() => import("./pages/Dashboard")
);Now React can load Dashboard separately when it is needed.
This is called lazy loading.
Suspense
There’s a problem.
If Dashboard hasn’t loaded yet, what should React display?
We use:
SuspenseFull example:
import {
lazy,
Suspense
} from "react";
const Dashboard = lazy(
() => import("./pages/Dashboard")
);
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<Dashboard />
</Suspense>
);
}
export default App;The process is:
User requests Dashboard
↓
React starts downloading Dashboard
↓
Dashboard isn't ready
↓
Suspense displays "Loading..."
↓
Dashboard finishes loading
↓
Dashboard appearsLazy Loading with React Router
This is where lazy loading becomes extremely useful.
Our App.jsx:
import { lazy, Suspense } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
const Home = lazy(() => import("./pages/Home"));
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Profile = lazy(() => import("./pages/Profile"));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<p>Loading...</p>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}Now every page can be loaded independently.
Forms
Now let’s build our Add Student page.
The form needs:
Name
Email
Age
Course
SubmitStart with normal React state.
import { useState } from "react";
function AddStudent() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState("");
const [course, setCourse] = useState("");
function handleSubmit(event) {
event.preventDefault();
console.log({
name,
email,
age,
course
});
}
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) =>
setName(e.target.value)
}
placeholder="Name"
/>
<input
value={email}
onChange={(e) =>
setEmail(e.target.value)
}This works.
Get Tochukwu Mezue’s stories in your inbox
Join Medium for free to get updates from this writer.
But imagine 20 form fields.
You could end up with:
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState("");
const [course, setCourse] = useState("");
const [phone, setPhone] = useState("");
const [address, setAddress] = useState("");
const [country, setCountry] = useState("");That’s where React Hook Form becomes useful.
React Hook Form
Install it:
npm install react-hook-formThen:
import { useForm } from "react-hook-form";
function AddStudent() {
const {
register,
handleSubmit,
formState: {
errors
}
} = useForm();
function onSubmit(data) {
console.log(data);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register("name")}
placeholder="Name"
/>
<input
{...register("email")}
placeholder="Email"
/>
<input
{...register("age")}
placeholder="Age"
/>
<input
{...register("course")}
placeholder="Course"
/>
<button type="submit">
Add Student
</button>
</form>
);
}
export default AddStudent;Notice something important.
We no longer need:
useState("")for every field.
React Hook Form manages the form values for us.
What Does register() Do?
When we write:
<input {...register("name")} />we’re telling React Hook Form:
This input belongs to the form and its name is
name.
Another:
<input {...register("email")} />means:
Track this field as
When submitted:
function onSubmit(data) {
console.log(data);
}You might get:
{
name: "John",
email: "john@gmail.com",
age: "22",
course: "Computer Science"
}Form Validation
Now we need to make sure users don’t submit invalid data.
For example:
Name → required
Email → required + valid email
Age → required
Course → requiredReact Hook Form can handle simple validation.
<input
{...register("name", {
required: "Name is required"
})}
/>Then:
{errors.name && (<p>{errors.name.message}</p>
)}For email:
<input
type="email"
{...register("email", {
required: "Email is required"
})}
/>More Advanced Validation
Let’s validate the age:
<input
type="number"
{...register("age", {
required: "Age is required",
min: {
value: 16,
message: "Student must be at least 16"
},
max: {
value: 100,
message: "Enter a valid age"
}
})}
/>Now React Hook Form automatically checks the rules.
Building a Complete Form
Let’s combine our reusable Input component with React Hook Form.
components/Input.jsx
function Input({
label,
type = "text",
error,
...props
}) {
return (
<div className="mb-4">
<label>
{label}
</label>
<input
type={type}
{...props}
className="border p-2 w-full"
/>
{error && (
<p className="text-red-500">
{error}
</p>
)}
</div>
);
}
export default Input;pages/AddStudent.jsx
import { useForm } from "react-hook-form";
import Input from "../components/Input";
import Button from "../components/Button";
function AddStudent() {
const {
register,
handleSubmit,
formState: {
errors
}
} = useForm();
function onSubmit(data) {
console.log(data);
}
return (
<div className="max-w-lg mx-auto">
<h1>
Add Student
</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<Input
label="Name"
placeholder="Enter student name"
{...register("name", {
required: "Name is required"
})}
error={errors.name?.message}
/>
<Input
label="Email"
type="email"
placeholder="Enter email"
{...register("email", {
required: "Email is required"
})}
error={errors.email?.message}
/>
<Input
label="Age"
type="number"
placeholder="Enter age"
{...register("age", {
required: "Age is required"
})}
error={errors.age?.message}
/>
<Input
label="Course"
placeholder="Enter course"
{...register("course", {
required: "Course is required"
})}
error={errors.course?.message}
/>
<Button type="submit">
Add Student
</Button>
</form>
</div>
);
}
export default AddStudent;Now we have:
Reusable Input
↓
React Hook Form
↓
Validation
↓
Reusable Button
↓
SubmitYup Schema Validation
The previous approach puts validation inside the form:
register("name", {
required: "Name is required"
})For a large application, you might want all validation rules in one place.
Create:
schemas/
└── studentSchema.jsInstall Yup:
npm install yupThen:
studentSchema.js
import * as yup from "yup";
export const studentSchema = yup.object({
name: yup
.string()
.required("Name is required")
.min(2, "Name must be at least 2 characters"),
email: yup
.string()
.email("Enter a valid email")
.required("Email is required"),
age: yup
.number()
.required("Age is required")
.min(16, "Minimum age is 16"),
course: yup
.string()
.required("Course is required")
});Now our validation rules are centralized.
Connecting Yup to React Hook Form
Install:
npm install @hookform/resolversThen:
import { useForm } from "react-hook-form";
import {
yupResolver
} from "@hookform/resolvers/yup";
import {
studentSchema
} from "../schemas/studentSchema";Configure the form:
const {
register,
handleSubmit,
formState: {
errors
}
} = useForm({
resolver: yupResolver(studentSchema)
});Now the form knows about the Yup schema.
Your input becomes much cleaner:
<Input
label="Name"
{...register("name")}
error={errors.name?.message}
/>No validation rules are needed inside the input.
The schema handles them.
Zod Instead of Yup
Zod works in a very similar way.
Install:
npm install zodCreate:
schemas/
└── studentSchema.jsimport { z } from "zod";
export const studentSchema = z.object({
name: z
.string()
.min(2, "Name must be at least 2 characters"),
email: z
.string()
.email("Enter a valid email"),
age: z
.coerce
.number()
.min(16, "Minimum age is 16"),
course: z
.string()
.min(1, "Course is required")
});Then:
import {zodResolver
} from "@hookform/resolvers/zod";And:
const form = useForm({resolver: zodResolver(studentSchema)
});Complete Architecture
At this point, our project looks like this:
src/
│
├── components/
│ │
│ ├── Button.jsx
│ ├── Input.jsx
│ ├── StudentCard.jsx
│ └── Loader.jsx
│
├── pages/
│ │
│ ├── Home.jsx
│ ├── Students.jsx
│ ├── AddStudent.jsx
│ └── Dashboard.jsx
│
├── schemas/
│ │
│ └── studentSchema.js
│
├── services/
│ │
│ └── studentService.js
│
├── hooks/
│ │
│ └── useStudents.js
│
├── App.jsx
└── main.jsxAnd each part has a job:
components
↓
Reusable UI
pages
↓
Application screens
schemas
↓
Validation rules
services
↓
API communication
hooks
↓
Reusable React logic
App.jsx
↓
Application routing/layoutThe Complete Flow
When the user wants to add a student:
User opens /students/add
↓
React Router loads AddStudent
↓
Lazy Loading downloads AddStudent
↓
Suspense displays loader while waiting
↓
AddStudent renders
↓
React Hook Form manages the form
↓
User enters information
↓
Zod/Yup validates the information
↓
Validation succeeds
↓
onSubmit() runs
↓
studentService sends data to API
↓
Student is createdThis is how these concepts connect in a real React application.
What you should remember
Reusable Components
Build once, use everywhere.
Folder Structure
Organize code based on responsibility.
Code Splitting
Break a large JavaScript bundle into smaller pieces.
Lazy Loading
Don’t download code until you need it.
Suspense
Show a fallback while lazy content is loading.
Forms
Collect and manage user information.
Validation
Make sure the information is correct.
React Hook Form
Simplify form state and submission.
Yup/Zod
Create centralized validation schemas.
Project
The next logical step after this lesson is to build the complete Student Manager project, including React Router, a navbar, dashboard, CRUD operations, API integration, reusable form components, and protected routes.









