Project Overview
The Project Management app is a practice project from Section 9 that demonstrates how to build a full-featured application for managing projects and their associated tasks. This project focuses on advanced React patterns including refs, portals, and complex state management.
Learning Objectives
Refs & ForwardRef Access DOM elements and manage component references
React Portals Render modals outside the component hierarchy
State Management Handle complex application state patterns
Form Validation Validate user input with refs
Key Features
Project Management
Users can add new projects with title, description, and due date function handleAddProject ( projectData ) {
setProjectsState (( prevState ) => {
const projectId = Math . random ();
const newProject = {
... projectData ,
id: projectId ,
};
return {
... prevState ,
selectedProjectId: undefined ,
projects: [ ... prevState . projects , newProject ],
};
});
}
Click on a project in the sidebar to view its details and tasks function handleSelectProject ( id ) {
setProjectsState (( prevState ) => {
return {
... prevState ,
selectedProjectId: id ,
};
});
}
const selectedProject = projectsState . projects . find (
( project ) => project . id === projectsState . selectedProjectId
);
Remove projects and their associated tasks from the application function handleDeleteProject () {
setProjectsState (( prevState ) => {
return {
... prevState ,
selectedProjectId: undefined ,
projects: prevState . projects . filter (
( project ) => project . id !== prevState . selectedProjectId
),
};
});
}
Task Management
Each project can have multiple tasks that can be added and removed:
import NewTask from './NewTask.jsx' ;
export default function Tasks ({ tasks , onAdd , onDelete }) {
return (
< section >
< h2 className = "text-2xl font-bold text-stone-700 mb-4" > Tasks </ h2 >
< NewTask onAdd = { onAdd } />
{ tasks . length === 0 && (
< p className = "text-stone-800 my-4" >
This project does not have any tasks yet.
</ p >
) }
{ tasks . length > 0 && (
< ul className = "p-4 mt-8 rounded-md bg-stone-100" >
{ tasks . map (( task ) => (
< li key = { task . id } className = "flex justify-between my-4" >
< span > { task . text } </ span >
< button
className = "text-stone-700 hover:text-red-500"
onClick = { () => onDelete ( task . id ) }
>
Clear
</ button >
</ li >
)) }
</ ul >
) }
</ section >
);
}
Core Concepts
The project uses refs to access form input values without controlled components:
import { useRef } from 'react' ;
import Input from './Input.jsx' ;
import Modal from './Modal.jsx' ;
export default function NewProject ({ onAdd , onCancel }) {
const modal = useRef ();
const title = useRef ();
const description = useRef ();
const dueDate = useRef ();
function handleSave () {
const enteredTitle = title . current . value ;
const enteredDescription = description . current . value ;
const enteredDueDate = dueDate . current . value ;
// Validation
if (
enteredTitle . trim () === '' ||
enteredDescription . trim () === '' ||
enteredDueDate . trim () === ''
) {
modal . current . open ();
return ;
}
onAdd ({
title: enteredTitle ,
description: enteredDescription ,
dueDate: enteredDueDate ,
});
}
return (
<>
< Modal ref = { modal } buttonCaption = "Okay" >
< h2 className = "text-xl font-bold text-stone-700 my-4" > Invalid Input </ h2 >
< p className = "text-stone-600 mb-4" >
Oops ... looks like you forgot to enter a value.
</ p >
</ Modal >
< div className = "w-[35rem] mt-16" >
{ /* Form content */ }
< Input type = "text" ref = { title } label = "Title" />
< Input ref = { description } label = "Description" textarea />
< Input type = "date" ref = { dueDate } label = "Due Date" />
</ div >
</>
);
}
When using refs with custom components, you must forward the ref using forwardRef.
Modal with Portals and useImperativeHandle
The modal component demonstrates advanced patterns:
import { forwardRef , useImperativeHandle , useRef } from 'react' ;
import { createPortal } from 'react-dom' ;
import Button from './Button.jsx' ;
const Modal = forwardRef ( function Modal ({ children , buttonCaption }, ref ) {
const dialog = useRef ();
// Expose custom methods to parent components
useImperativeHandle ( ref , () => {
return {
open () {
dialog . current . showModal ();
},
};
});
// Render modal outside the component tree using portals
return createPortal (
< dialog
ref = { dialog }
className = "backdrop:bg-stone-900/90 p-4 rounded-md shadow-md"
>
{ children }
< form method = "dialog" className = "mt-4 text-right" >
< Button > { buttonCaption } </ Button >
</ form >
</ dialog > ,
document . getElementById ( 'modal-root' )
);
});
export default Modal ;
React Portals allow you to render components outside their parent DOM hierarchy, perfect for modals and tooltips.
Application State Structure
The entire application state is managed in a single object:
const [ projectsState , setProjectsState ] = useState ({
selectedProjectId: undefined , // undefined = no selection
// null = adding new project
// id = selected project
projects: [], // Array of project objects
tasks: [], // Array of all tasks
});
State Values
Data Structure
selectedProjectId: undefined - Show “no project selected” screen
selectedProjectId: null - Show “add new project” form
selectedProjectId: <id> - Show selected project details
{
selectedProjectId : 1 ,
projects : [
{ id: 1 , title: 'Learning React' , description: '...' , dueDate: '2026-12-31' }
],
tasks : [
{ id: 1 , text: 'Finish section 9' , projectId: 1 }
]
}
Project Structure
09-practice-project-management/
├── src/
│ ├── components/
│ │ ├── Button.jsx # Reusable button component
│ │ ├── Input.jsx # Forwarded ref input component
│ │ ├── Modal.jsx # Portal-based modal dialog
│ │ ├── NewProject.jsx # Project creation form
│ │ ├── NewTask.jsx # Task input component
│ │ ├── NoProjectSelected.jsx # Empty state component
│ │ ├── ProjectsSidebar.jsx # Project list sidebar
│ │ ├── SelectedProject.jsx # Project details view
│ │ └── Tasks.jsx # Task list component
│ ├── App.jsx # Main app with state management
│ └── main.jsx # Entry point
├── index.html # Includes modal-root div
└── tailwind.config.js # Tailwind CSS configuration
Implementation Steps
The project is built incrementally across multiple videos:
Setup and Sidebar
Create the basic layout and style the projects sidebar
Component Structure
Build reusable Button and Input components with proper ref forwarding
State Management
Implement state logic to switch between different views
Project Creation
Handle form submission and add projects to state
Validation Modal
Add modal with portals for form validation feedback
Project Selection
Make projects selectable and display details
Project Deletion
Add ability to delete projects
Task Management
Implement task addition and deletion functionality
Key Patterns & Techniques
ForwardRef Pattern
import { forwardRef } from 'react' ;
const Input = forwardRef ( function Input ({ label , textarea , ... props }, ref ) {
const classes = "w-full p-1 border-b-2 rounded-sm border-stone-300" ;
return (
< p className = "flex flex-col gap-1 my-4" >
< label className = "text-sm font-bold uppercase text-stone-500" >
{ label }
</ label >
{ textarea ? (
< textarea ref = { ref } className = { classes } { ... props } />
) : (
< input ref = { ref } className = { classes } { ... props } />
) }
</ p >
);
});
export default Input ;
Conditional Rendering
let content = (
< SelectedProject
project = { selectedProject }
onDelete = { handleDeleteProject }
onAddTask = { handleAddTask }
onDeleteTask = { handleDeleteTask }
tasks = { projectsState . tasks }
/>
);
if ( projectsState . selectedProjectId === null ) {
content = (
< NewProject onAdd = { handleAddProject } onCancel = { handleCancelAddProject } />
);
} else if ( projectsState . selectedProjectId === undefined ) {
content = < NoProjectSelected onStartAddProject = { handleStartAddProject } /> ;
}
Styling with Tailwind CSS
The project uses Tailwind CSS for styling:
Button Component
Sidebar Styling
export default function Button ({ children , ... props }) {
return (
< button
className = "px-4 py-2 text-xs md:text-base rounded-md bg-stone-700 text-stone-400 hover:bg-stone-600 hover:text-stone-100"
{ ... props }
>
{ children }
</ button >
);
}
Common Challenges
Challenge: Keeping projects and tasks synchronizedSolution: Store all data in a single state object and update immutably:setProjectsState (( prevState ) => {
return {
... prevState ,
tasks: [ newTask , ... prevState . tasks ],
};
});
Challenge: Refs not working with custom componentsSolution: Wrap components with forwardRef and pass the ref to the actual DOM element
Challenge: Modal rendered in the wrong place in the DOMSolution: Use createPortal to render it to a specific DOM node:return createPortal (
< dialog > { /* content */ } </ dialog > ,
document . getElementById ( 'modal-root' )
);
Testing Your Implementation
Next Steps
React Quiz Project Learn useEffect and side effects
Advanced State Explore Context API and useReducer