150. Module Introduction & Starting Project

This section is a hands-on practice project to apply React fundamentals and some advanced concepts by building a simple Project Management app. The app will let users create and view projects, add and clear tasks, and delete projects, combining concepts like components, state, refs, portals, and styling.

A starter project is provided in both local and CodeSandbox forms (equivalent). The local version requires installing dependencies and starting the dev server; CodeSandbox runs immediately. Tailwind CSS is already set up in the starter, so learners can use its utility classes (optionally referencing the Tailwind docs), but the priority is correct React functionality over styling. Learners are encouraged to attempt building the app independently before following the instructor’s step-by-step solution starting next lecture.

151. Adding a Projects Sidebar Component

You create a new components folder under src and add a ProjectsSidebar.jsx component. That component returns an aside containing an h2 (“Your Projects”) and a button (“+ Add Project”) meant for starting a new project (project list will be added later). Then, in App, you import and render ProjectsSidebar, replacing the old h1 and wrapping the layout in a main element to hold the sidebar and future project details. After confirming it renders, you note the next step is styling.

152. Styling the Sidebar & Button with Tailwind CSS

The text describes adding Tailwind CSS classes to style a React app layout and a ProjectsSidebar:

  • App component: add h-screen so the layout fills the full viewport height (so the sidebar can later stretch full height), and my-8 for vertical margin.

  • Sidebar (aside) styling: set width to w-1/3, add padding px-8 py-16, dark background bg-stone-900, light text text-stone-50, use a fixed width on medium+ screens with md:w-72, and round only the right corners with rounded-r-xl.

  • Sidebar title (h2) styling: add bottom margin, font-bold, uppercase, larger text on bigger screens, and adjust to a lighter gray color.

  • Button styling: add padding, text-xs with md:text-base, rounded-md, bg-stone-700, text-stone-400, and hover styles hover:bg-stone-600 hover:text-stone-100.

It ends by noting the next step: make the button clickable to show a component/screen for entering details of a new project.

153. Adding the New Project Component & A Reusable Input Component

A new NewProject React component is created to collect data for a new project. It renders a div with a menu containing Cancel and Save buttons, and below that it shows inputs for Title, Description, and Due Date (with Description using a textarea).

To avoid repeating similar JSX and Tailwind classes, a reusable Input component is added (Input.jsx). It renders a label plus either an input or textarea depending on a textarea prop, and it accepts a label prop while spreading remaining props onto the rendered control for full configurability.

NewProject then imports and uses Input three times with the appropriate labels, enabling textarea for the description field. Finally, NewProject is imported into the main App component and displayed next to the sidebar by making the main container a flex layout (flex with gap-8), ensuring the sidebar stretches to full height and the new project inputs appear beside it.

154. Styling Buttons & Inputs with Tailwind CSS

  • Adds Tailwind styling to the “New Project” wrapper: sets a custom width using Tailwind’s arbitrary value syntax (w-[35rem]) and pushes the section down with mt-16 to align with the sidebar title.

  • Styles the top menu bar in the New Project component using Flexbox (flex, items-center, justify-end), spacing (gap-*) and vertical margin (my-4).

  • Styles the two action buttons differently:

    • Cancel: subtle/flat look with dark gray text (text-stone-800) and darker hover text.

    • Save: prominent button with dark background, light text, hover background change, padding/margins, and rounded corners.

  • Updates Input.jsx styling:

    • Wraps label + control in a vertical flex layout (flex, flex-col) with spacing and vertical margins.

    • Styles labels (text-sm, font-bold, uppercase, gray color).

    • Styles textarea (and then input) with full width, padding, bottom border, rounded corners, border/background/text colors, and focus states (remove default outline, change border color).

    • Refactors repeated Tailwind classes into a shared classes constant applied to both textarea and input.

  • Next planned steps: show fallback content before “Add Project” is clicked, open the form on click, then read user input values and create a new project.

155. Splitting Components to Split JSX & Tailwind Styles

  • Add a fallback UI that’s shown when no project is selected and the user is not currently adding a new project.

  • Create a new component NoProjectSelected that renders:

    • An image (no-projects.png imported from the assets folder),

    • An h2 heading (“No Project Selected”),

    • A paragraph prompting the user to select or create a project,

    • A “Create new project” button that should trigger showing the NewProject screen.

  • Apply Tailwind styling:

    • Wrapper div: top margin, centered text, width 2/3.

    • Image: w-16 h-16 object-contain mx-auto.

    • Heading: text-xl font-bold text-stone-500 my-4.

    • Text paragraph: text-stone-400 plus spacing.

  • Avoid duplicating button Tailwind classes by creating a reusable Button component:

    • Move the button markup and classes from ProjectsSidebar into Button.

    • Support flexible content via children.

    • Forward all additional props (…​props) onto the underlying <button> so it can receive handlers/attributes later.

  • Replace the existing buttons in both ProjectsSidebar and NoProjectSelected with the new Button component (imported from button.jsx), removing repeated className definitions.

  • Temporarily render NoProjectSelected in App instead of NewProject to preview it.

  • Next step: implement conditional rendering in App so clicking the “Create new project” button switches the UI to the NewProject component.

156. Managing State to Switch Between Components

  • Use React state in the App component to decide whether to show NoProjectSelected or NewProject, since conditional rendering should be controlled where the decision is made.

  • Add useState (imported from React) and manage a single state object named projectsState containing:

    • selectedProjectId: initially undefined

    • projects: initially an empty array

  • Define meaning for selectedProjectId:

    • undefined: no project selected and not adding a new one (show fallback screen)

    • null: user is starting to add a new project (show NewProject)

    • later: an actual project ID when a project is selected

  • Add handleStartAddProject in App to set selectedProjectId to null, using the functional setProjectsState(prev => ({ …​prev, selectedProjectId: null })) form to avoid losing existing state (like saved projects).

  • Pass this handler down as onStartAddProject to both NoProjectSelected and ProjectSidebar, and wire it to their “Add Project” buttons via onClick.

  • In App, compute a content variable:

    • if selectedProjectId === null → render <NewProject />

    • else if selectedProjectId === undefined → render <NoProjectSelected onStartAddProject={…​} />

    • (later, this will be extended to render a selected project view)

  • Render {content} next to the sidebar. Result: initial load shows the fallback screen; clicking either “Add Project” button switches to the NewProject component.

  • Next step mentioned: collect input values to actually create and store a new project and make the other buttons work.

157. Collecting User Input with Refs & Forwarded Refs

Goal

When clicking Save in NewProject, collect input values, validate them (show an error modal if any are empty), and on success add the new project to state so it appears in the sidebar (validation + closing view will come next; this section implements the success path first).

  • Input value collection approach: Instead of controlled inputs with onChange + state, use refs because values are only needed on save.

    • Import useRef and create three refs: title, description, dueDate.

    • Attach refs to the three input components.

  • Ref forwarding for a custom Input component:

    • In React 19+, passing a ref prop to a custom component works like a normal prop.

    • In React 18 and earlier, you must wrap the Input component with forwardRef:

      • Import forwardRef from React.

      • Wrap the component and accept (props, ref).

      • Assign ref to the underlying native <input> or <textarea> (depending on whether textarea prop is used).

  • Save handling in NewProject:

    • Add handleSave and connect it to the Save button via onClick.

    • Inside handleSave, read values via ref.current.value for all three fields.

    • Call a prop callback (e.g., onAdd) and pass an object: { title, description, dueDate }.

  • Lifting state to App to actually add projects:

    • In App, add handleAddProject(projectData) to update the projects state using the functional setState form.

    • Preserve existing state and append a new project object to the projects array.

    • Add an id to the new project (using Math.random() as a simple stand-in).

  • Wiring:

    • Pass handleAddProject to NewProject as onAdd.

    • Confirm via console.log that the projects array updates when saving.

    • Note: double logs occur due to StrictMode causing components to run twice in development.

  • Input improvements:

    • Set due date input type="date" to get a date picker.

    • Set title input type="text" for completeness.

  • Next steps (not implemented yet in the described part):

    • Validate inputs and show an error modal if invalid.

    • Render newly added projects in the sidebar.

    • Close/hide the NewProject form after a successful save.

158. Handling Project Creation & Updating the UI

  • To close the “New Project” component after saving, the App component must reset selectedProjectId away from null (because null is what keeps the new-project screen visible).

  • In handleAddProject, set selectedProjectId to undefined so the UI returns to the fallback/overview screen after saving. (Alternative mentioned: set it to the newly created project’s id to auto-select it, but that’s deferred since selection/details aren’t implemented yet.)

  • To show created projects in the sidebar:

    • Pass the projects array down to the sidebar via a projects prop (e.g., projectsState.projects).

    • In the sidebar component, map over projects to render an <li> per project, using project.id as the key.

    • Inside each list item, render a <button> that displays project.title (preparing for future “select project” behavior).

  • Add Tailwind styling to the buttons (full width, left-aligned text, padding, rounded corners, margins, light text on dark background, hover styles) and add mt-8 to the <ul> for extra spacing above the list.

  • Next steps planned: make the Cancel button work in the new-project form, and show a validation error modal if Save is clicked with missing input values.

159. Validating User Input & Showing an Error Modal via useImperativeHandle

  • Add input validation in the NewProject component: trim title, description, and date and treat them as invalid if any are empty strings. If invalid, show an error modal and return to prevent onAdd from running.

  • Create a reusable Modal.jsx component built on the native <dialog> element.

  • Render the modal via a React portal into a #modal-root div using createPortal(…​) and document.getElementById('modal-root').

  • Make the modal controllable from outside without exposing internal <dialog> details by using:

    • forwardRef (for React 18 and compatibility; not required in React 19+)

    • useImperativeHandle to expose an open() method

    • an internal useRef to call dialogRef.current.showModal()

  • In NewProject, create modalRef, attach it to <Modal ref={modalRef} />, and call modalRef.current.open() when validation fails.

  • Provide modal content in NewProject via children (e.g., “Invalid input” + explanatory paragraphs).

  • Add a close mechanism inside Modal: a <form method="dialog"> with a submit button to close the dialog.

  • Improve reusability by accepting a buttonCaption prop to customize the close button text (e.g., “Close” or “Okay”).

  • Result: modal appears on saving with empty/invalid fields; styling isn’t polished yet but functionality works.