This project focus on:
- Build, Style, Configure & Re-use
Components. - Manage
State. - Access DOM Elements & Browser APIs with
Refs. - Manage JSX Rendering Positions with
Portals.
- Read the instructions next to the code.
-
Create component folder.
-
Add
ProjectSidebarcomponent- Inside create
<aside>element. - Styling the Sidebar & Button with Tailwind CSS.
- Inside create
-
Add
NewProjectcomponent for gathering all project data.- Creating 3
<input>elements for Title, Description, and Due Date.
- Creating 3
-
Because they look the same, Add reusable
Inputcomponent.- Inside adding
<label>, <textarea>, and {...props}. - After that adding tailwind classes.
- Inside adding
-
Add
<ProjectSidebar/>and<NewProject/>to App.jsx.
- By hover over tailwind classes, I can see the styles that are applied to the elements.
For now the app looks like this:
- Split components: Creating a new component:
NoProjectSelectedto display when no project is selected.
- First, Create
<img>, <h2>, <p>, and <button>elements. - Then, add the styling for each.
Looks like this:
- Create a
Button.jsxcomponent to use in multiple places, to avoid repeating the same code.
- The content should be flexible and passed as
children. Button should collect all other...propsthat might be set, and then spread them on the button element. So later we can add for example theonChangeprop.
- Inside App: Add
NoProjectSelected, but renderNewProject, orNoProjectSelected, depending if the button got clicked or not. Therefore, addstateto manage this situation.
- New state is an object include:
property; to indicate if project is new or not existence, andarray; for all the projects. - Then render the corresponding component conditionally.
- Add and pass
handleStartAddProjectfunction toonStartAddProjectprop with<NoProjectSelected>, and<ProjectsSidebar>component (that contain button for adding new project).
- Inside each component (
NoProjectSelected, andProjectsSidebar), accept and destructure the proponStartAddProjectfrom App (that connect tohandleStartAddProjectfunction). - Then add
onStartAddProjectprop, toonCilckprop in Button.
-
Next step is to collect the user input and validate them.
-
Validate user input & showing error
ModalviauseImperativeHandle;
- Valaidate input fields, and Show the error modal in
<NewProject>. - Create
Modalcomponent, and return built in<dialog>. - Wrap it in
forwardRef, extract thechildrenandbuttonCaptionproperties. children(from props object), makes this component flexible.childrenis pass to every component, so I can useModalas a wrapper to any content I want. And that content will be wrapped by thedialogelement.- Add a
ref. - Add
useImperativeHandle, definingopenfunction. To expose a function that can be called from outside the component. - Inside, pass it a ref object and a function that returns an object.
- Inside open, I want to call
showModalmethod on the dialog element. I do it with useRef nameddialog.
- Styling
Modalvia tailwind - add styles to dialog, and form, and use the Button component.
- Text style is changed in
NewProject.jsx.
- Add Cancel option to New Project screen;
- When clicking cancel button, the user will go back to home screen.
- Inside App: Add
handleCancelAddProjectfunction for cancel the creation of new project. (Same code ashandleStartAddProjectfunction, but selectedProjectId is set to undefined instaed of null).
- Render
<NewProject>withonCancel={handleCancelAddProject}. - Inside
NewProject.jsx- acceptonCancelprop. - Make sure
onCancelconnected to cancel button.
- Making projects selectebale & viewing project details:
- Create
SelectedProjectcomponent, return<header>,<h1>, and<button>to delete this project, in one<div>. - Expect to get
projectprop, and in the rendered code output {project.title}, formated{project.dueDate}, and{project.description}. - Add style via tailwind.
- I must sure that project can be selected in the sidebar, for that i must make sure that the button change some
stateinApp,jsx. - I add another function in App:
handleSelectProject, with projectidas a value, and update the state ofselectedProjectIdwith theid. - This
handleSelectProjectpass to in the rendered code, with a newonSelectProjectprop. - Now, inside
ProjectsSidebari should extractselectedProjectIdfrom the incoming props, and connect it to the button in this component, withonClickprop withonSelectProjectas a value. - In addition, I also want to highlight the button of the project that was selected so I will extract also
selectedProjectId, so I will use this prop that contain theidof the project that was selected. - I will add return statement inside map, that I can conveniently add more code in the function that pass to map, depending if the element should be highlighted or not.
- Create a variable to store css classes, check if the
project.idI currently outputting is equal to theselectedProjectIdI get as a prop. Now i use{cssClasses}as a value for{className}prop. - Last step, is to add the newly added
selectedProject componenetand output it in theAppcomponent, if a project was selected; - I declare
contentvariable to be equal to<SelectedProject project={selectedProject} />, now I need to derive the selected project from thestate; - I find it with
const selectedProject = projectsState.projects.find((project) => project.id === projectsState.selectedProjectId);. - Now, if i click on a project in
side bar, the app failed. The reason is insideApp. - Project Details: For now i just passing
onSelectProjectfunction toonSelectProjectprop, but inProjectsSidebar.jsxI useonSelectProjectprop that contain the function mention above, and I just pass this ahead to the button. And the button don't give theidof the selected project. Therefore, more control of how it will be executed is needed; - By wrapping this with a function, and manually calling it inside of this function so I can pass the
idof the project that currently being rendered as a value toonSelectProject, and therfore as a value tohandleSelectProjectfunction.
- Create
// So this:
onClick={onSelectProject}
// Become this:
onClick={() => onSelectProject(project.id)}-
Handling Project Deletion:
- As before, I need to add a functn to
App.jsx, a function which update thestate, and remove the project from the array, and I need to pass that function toSelectedProjectcomponent. - Add
handleDeleteProjectfunction, that update the state as the other function. Then filter out the selected project from the projects array. - Pass a pointer to
handleDeleteProjectfunction withonDeleteprop. So I can call this function through theonDeleteprop from insideSelectedProjectcomponent. - Inside
SelectedProjectI extract this newly addedonDeleteprop, and than connect it to thedelete button, like this:onClick={onDelete}.
- As before, I need to add a functn to
-
Adding "Project Tasks" & A Tasks Component:
- Add new
Taskcomponent withh2, NEW TASK placeholder (which will be replaced by input and button),p,ul, and styling. - Then i use
<Tasks />insideSelctedProjectcomponent. - Now i replace NEW TASK placeholder with actual input, and a button.
- Add
NewTaskcomponent. Includinginput,button, and styling including flex-box since I want the 2 elements next to each other.
- Add new
-
Managing Tasks & Understanding Prop Drilling;
- Now I want to make sure that the added task will be shown. I can do it with a
ref, but i will usestate([enteredTask, setEnteredTask]), insideNewTask. - Add
handleChangefunction that connect to the input field to update the entered task. This function get aneventobject as a parameter, that havetargetwhich is the input field, and value which is thevalueof the input field. (event.target.value). - Then I add
onChangeprop to connect it to thehandleChangefunction. (<input onChange={handleChange}/>). - To complete the two way binding, using
value={enteredTask}, by feeding the entered task text into the input field. (<input value={enteredTask}/>). - Now I want to make sure that when pressing the
Add Taskbutton the entered task is added to a place it can be stored. - It will be in
App, as I already store there all the projects. - So inside
AppI addtasks: [],to theprojectsStateobject. - Therefore I will add 2 new function for handling tasks:
handleAddTask(), andhandleDeleteTask(). - Inside
NewTaskI addhandleClick()function, as long asonClick={handleClick}. - Inside
handleClick(), I want to forward the entered value to the app component (onAdd(enteredTask);), and then I want to reset it back to an empty string (setEnteredTask('');). - Now i need to get the entered task to the app component.
- I need to pass
handleAddTask()toNewTaskcomponent. AndNewTaskis inTaskcomponent, andTaskcomponent is insideSelectedProjectcomponent. It's invovled prop drilling.- So let's start: In
App, addhandleAddTaskas a value toSelectedProject, inonAddTaskprop. The same forhandleDeleteTask. - I will use this function in
Task, that called fromSelectedProject, that's why I write it there. - On
SelectedProjectextractonAddTask, andonDeleteTask, in order to forward them inside toTask. - In
Taskalso destructingonAddTask, andonDeleteTaskprops that just being added. - Then
onAddTaskwill be forward to<NewTask />, andonDeleteTaskwill be use inside the current component. - Inside
NewTaskdestrcuturing theonAddfunction from the props object. - Inside
handleClickinNewTask, forward the entered task to the onAdd function. (That will go toTask, which in the end is inSelectedProject, which than is inApp)Χ₯ - Few more steps with
tasks...
- So let's start: In
- Now I want to make sure that the added task will be shown. I can do it with a
- Clearing Tasks & Fixing Minor Bugs:
- Now i want to make sure that
clearbutton on each task will perform his purpose. AddhandleDeleteTaskinApp. - Add
handleDeleteTaskfunction toonDeleteTaskprop, as value to<SelectedProject />. - Inside
TasksdestructonDeleteprop, in order to connect it todeletebutton. - On
buttonaddonClick={onDelete}, but wrap it in a function for full control of execution like this:onClick={() => onDelete(task.id)}.
- Now i want to make sure that