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
ProjectSidebar
component- Inside create
<aside>
element. - Styling the Sidebar & Button with Tailwind CSS.
- Inside create
-
Add
NewProject
component for gathering all project data.- Creating 3
<input>
elements for Title, Description, and Due Date.
- Creating 3
-
Because they look the same, Add reusable
Input
component.- 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:
NoProjectSelected
to 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.jsx
component 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...props
that might be set, and then spread them on the button element. So later we can add for example theonChange
prop.
- Inside App: Add
NoProjectSelected
, but renderNewProject
, orNoProjectSelected
, depending if the button got clicked or not. Therefore, addstate
to 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
handleStartAddProject
function toonStartAddProject
prop with<NoProjectSelected>
, and<ProjectsSidebar>
component (that contain button for adding new project).
- Inside each component (
NoProjectSelected
, andProjectsSidebar
), accept and destructure the proponStartAddProject
from App (that connect tohandleStartAddProject
function). - Then add
onStartAddProject
prop, toonCilck
prop in Button.
-
Next step is to collect the user input and validate them.
-
Validate user input & showing error
Modal
viauseImperativeHandle
;
- Valaidate input fields, and Show the error modal in
<NewProject>
. - Create
Modal
component, and return built in<dialog>
. - Wrap it in
forwardRef
, extract thechildren
andbuttonCaption
properties. children
(from props object), makes this component flexible.children
is pass to every component, so I can useModal
as a wrapper to any content I want. And that content will be wrapped by thedialog
element.- Add a
ref
. - Add
useImperativeHandle
, definingopen
function. 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
showModal
method on the dialog element. I do it with useRef nameddialog
.
- Styling
Modal
via 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
handleCancelAddProject
function for cancel the creation of new project. (Same code ashandleStartAddProject
function, but selectedProjectId is set to undefined instaed of null).
- Render
<NewProject>
withonCancel={handleCancelAddProject}
. - Inside
NewProject.jsx
- acceptonCancel
prop. - Make sure
onCancel
connected to cancel button.
- Making projects selectebale & viewing project details:
- Create
SelectedProject
component, return<header>
,<h1>
, and<button>
to delete this project, in one<div>
. - Expect to get
project
prop, 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
state
inApp,jsx
. - I add another function in App:
handleSelectProject
, with projectid
as a value, and update the state ofselectedProjectId
with theid
. - This
handleSelectProject
pass to in the rendered code, with a newonSelectProject
prop. - Now, inside
ProjectsSidebar
i should extractselectedProjectId
from the incoming props, and connect it to the button in this component, withonClick
prop withonSelectProject
as 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 theid
of 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.id
I currently outputting is equal to theselectedProjectId
I get as a prop. Now i use{cssClasses}
as a value for{className}
prop. - Last step, is to add the newly added
selectedProject componenet
and output it in theApp
component, if a project was selected; - I declare
content
variable 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
onSelectProject
function toonSelectProject
prop, but inProjectsSidebar.jsx
I useonSelectProject
prop that contain the function mention above, and I just pass this ahead to the button. And the button don't give theid
of 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
id
of the project that currently being rendered as a value toonSelectProject
, and therfore as a value tohandleSelectProject
function.
- 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 toSelectedProject
component. - Add
handleDeleteProject
function, that update the state as the other function. Then filter out the selected project from the projects array. - Pass a pointer to
handleDeleteProject
function withonDelete
prop. So I can call this function through theonDelete
prop from insideSelectedProject
component. - Inside
SelectedProject
I extract this newly addedonDelete
prop, 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
Task
component withh2
, NEW TASK placeholder (which will be replaced by input and button),p
,ul
, and styling. - Then i use
<Tasks />
insideSelctedProject
component. - Now i replace NEW TASK placeholder with actual input, and a button.
- Add
NewTask
component. 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
handleChange
function that connect to the input field to update the entered task. This function get anevent
object as a parameter, that havetarget
which is the input field, and value which is thevalue
of the input field. (event.target.value
). - Then I add
onChange
prop to connect it to thehandleChange
function. (<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 Task
button 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
App
I addtasks: [],
to theprojectsState
object. - Therefore I will add 2 new function for handling tasks:
handleAddTask()
, andhandleDeleteTask()
. - Inside
NewTask
I 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()
toNewTask
component. AndNewTask
is inTask
component, andTask
component is insideSelectedProject
component. It's invovled prop drilling.- So let's start: In
App
, addhandleAddTask
as a value toSelectedProject
, inonAddTask
prop. The same forhandleDeleteTask
. - I will use this function in
Task
, that called fromSelectedProject
, that's why I write it there. - On
SelectedProject
extractonAddTask
, andonDeleteTask
, in order to forward them inside toTask
. - In
Task
also destructingonAddTask
, andonDeleteTask
props that just being added. - Then
onAddTask
will be forward to<NewTask />
, andonDeleteTask
will be use inside the current component. - Inside
NewTask
destrcuturing theonAdd
function from the props object. - Inside
handleClick
inNewTask
, 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
clear
button on each task will perform his purpose. AddhandleDeleteTask
inApp
. - Add
handleDeleteTask
function toonDeleteTask
prop, as value to<SelectedProject />
. - Inside
Tasks
destructonDelete
prop, in order to connect it todelete
button. - On
button
addonClick={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