Follow Along with Traversy Media React Crash Course 2021 GITHUB REPO FROM BRAD
- React is a library for building user interfaces
- React runs on the client as a SPA(Single Page App), but can be used to build full stack apps by communicating with a server/API (eg. MERN stack)
- React is often refereed to as a front-ent "framework" because it is capable and directly comparable to a framework such as Angular or Vue
- MVC (Model View Controller) --> Structure the "view" layer of your application
- Reusable components with their own state
- JSX - Dynamic markup --> write dynamic HTML (JS formatted like HTML)
- Interactive UIs with Virtual DOM --> Document Object Model --> Update parts of page without reloading it
- Performance & Testing --> All Data immutable (can't mutate directly), easier to work on teams
- Very Popular in the Industry
- When using React, think of your UI as a bunch of separate components
- Focus on Functions with Hooks
- JSX (JavaScript Syntax Extension) - syntactic sugar
- Can pass in "props" --> essentially attributes
- Components can have "state" which is an object that determines how a component renders and behaves (ex: collapsible menu, list of tasks, any data you bring into your compnents)
- "App" or "global" state refers to state that is available to the entire UI, not just a single component (share data with multiple components when global state gets complicated --> Redux (3rd Party Manager) or Context API)
- Prior to React 16.8, we had to use class based components to use state. Now we can use functional component with HOOKS (functional components used to be dumb components)
React Hooks are functions that le us hook into the React state and lifecyle features from function components
useState
--> Returns a stateful value and a function to update ituseEffect
--> Perform side effects in function components --> when making HTTP requestsuseContext
,useReducer
,useRef
--> Beyond the scope of this course- You can also create your own Custom Hooks
- Documentation
- React Developer Tools Chrome Extension
- To start up new Application
npx create-react-app my-app
cd my-app
npm start
- MY GITHUB REPO OF REACT TASK TRACKER APP
- Here's a look at what you get, in the package.json you have some dependencies --> if in react native you would see
react-native
instead ofreact-dom
- Look of the index.js and index.html -->
App
is the Route Component (every component we create will end up in main App component) - Use index.html to add Bootstrap CDN, etc.
- In
App.js
--> we see function component with JSX(looks like HTML)
- instead of
for
attribute usehtmlFor
- instead of
class
attribute useclassName
- dynamic --> can have JS expressions and variables
src={logo}
- Clean up some files and delete what is in the div
- CAN ONLY HAVE ONE PARENT ELEMENT, CAN NOT ADD NEW H2 AFTER THE DIV
- if you didn't want a div inside a div you can use fragments
<>
to surround the two headers. - You can create variables, ternary operators, ect. and use with JSX
- GREAT VS CODE EXTENSION
Name: ES7 React/Redux/GraphQL/React-Native snippets
Id: dsznajder.es7-react-js-snippets
Description: Simple extensions for React, Redux and Graphql in JS/TS with ES7 syntax
Version: 3.1.1
Publisher: dsznajder
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=dsznajder.es7-react-js-snippets
rcc
- class based componentrce
- class component and export at bottomrafce
- arrow function that exports at bottom
- Creates boilerplate of a function component --> DO NOT NEED IMPORT REACT ANYMORE (unless you are creating a class)
- use
_rafce
to skip import addition
//EXAMPLE OF USING A CLASS
import React, { Component } from 'react'
export class Header extends Component {
render() {
return (
<div>
</div>
)
}
}
export default Header
//EXAMPLE OF IMPORTING A CLASS
import React from 'react'
import Header from './components/Header'
class App extends React.Component {
render() {
return <h1>Hello from a class</h1>
}
}
- How to pass in props and use them
- can set default props if none is passed in
- can destructure props
- There are also Prop Types --> make code more robust --> use
impt
with extension --> you can also use Typescript with React - Use and pass in number instead of string for title, will render with warnings in the console
- Set
PropType
to be required
- Can use stylesheet
- external package --> style components
- direct or inline css in JavaScript
- Make Button Component with props, example of reusable components
- adding defaults and PropTypes to button
- Intro to setting Events --> 'onClick'
- you can pass in event object to onClick and access position of button along with other properties
- use
onClick
event as aprop
since Button is a Component
- Created Array of dummy data for tasks --> loop through information to output creating a list using the map array method
- in
Tasks.js
const Tasks = () => {
return (
<>
{tasks.map((task) => (
<h3>{task.text}</h3> //JSX
))}
</>
)
}
- in
App.js
- Get warning about key props, parent element (the
h3
) need to have a key prop --> needs to be unique
const Tasks = () => {
return (
<>
{tasks.map((task) => (
<h3 key={task.id}>{task.text}</h3>
))}
</>
)
}
- We don't want the array of tasks to be a separate from our component, we want it to be part of our state --> we are going to use the
useState
hook to use state inside of a function. Above the return: what you want to call this piece of state,tasks
, and function to update state,setTasks
, --> set that touseState
and paste in default you want to use (in this case the array of tasks)
const Tasks = () => {
const [tasks, setTasks] = useState([
{
id: 1,
text: 'DMV Alumni Meeting',
day: 'July 30th at 1pm',
reminder: true,
},
{
id: 2,
text: 'Log Coding Hours',
day: 'August 4th at 4pm',
reminder: true,
},
{
id: 3,
text: 'Send Finished Crochet Sweater',
day: 'August 6th at 11am',
reminder: false,
}
])
return (
<>
{tasks.map((task) => (
<h3 key={task.id}>{task.text}</h3>
))}
</>
)
}
- State is immutable, can't directly change, so you can't use
tasks.push()
to add new tasks, if you want to change any part of the state you usesetTasks
, you recreate it and send it down
//if you want to spread across what is already there and add a new object
setTasks([...tasks, {}])
- Normally you wouldn't have the tasks in the Task component cause we're going to want to access these from other components, use
Redux
or theContext API
, have a store that hovers over your UI that you can pull different pieces of state from - We are going to put it in the
App.js
before thereturn
and make it ourglobal state
and pass it down to our components asprops
//PASS IT INTO TASK COMPONENT IN APP.JS as PROPS
return (
<div className="container">
<Header />
<Tasks tasks={tasks} />
</div>
);
//PASS IT INTO THE TASKS.JS DESTRUCTURE AS PROPS
const Tasks = ({ tasks }) => {
return (
<>
{tasks.map((task) => (
<h3 key={task.id}>{task.text}</h3>
))}
</>
)
}
- Test by Creating a new
Task.js
component with generic h3 that will get outputted for every task for now - use the task prop you added in
Tasks.js
- use font awesome for delete icon and add the CDN into your index.html or install
react icons
npm i react-icons
--> access to multiple libraries- bring in specific icon FaTimes is the 'x' icon from 'fa' or fontawesome -->
import { FaTimes } from 'react-icons/fa'
- Add style to icon
- Using the icon to delete specific task --> with context API or Redux there are ways to access state from within components pretty easily can get complicated with redux and reducers, etc. In this case we can just use props and send down a function as a prop and fire that off when we click on task
- In
App.js
//DELETE TASK
const deleteTask = (id) => {
console.log('delete', id);
}
return (
<div className="container">
<Header />
<Tasks tasks={tasks} onDelete={deleteTask} />
</div>
);
}
- Pass prop
onDelete
intoTask.js
andTasks.js
--> log ofid
defaults to event info - use in
Task.js
as function instead foronClick
- use
setState
instead of console.log
//DELETE TASK
const deleteTask = (id) => {
//for each task you want to filter where the task id is not equal to the id
setTasks(tasks.filter((task) => task.id !== id))
}
- set default message for when there are no tasks
return (
<div className="container">
<Header />
{/* IF THERE ARE TASKS, show Tasks, else show message */}
{tasks.length > 0 ? <Tasks tasks={tasks} onDelete={deleteTask} /> : 'No Tasks To Show'}
</div>
);
- They will come back when page refreshes since this is the UI and that is what react does, you can turn into full stack app by having back end and have some kind of API you can make requests to and fetch data from --> will try out at end with
JSON Server
- Want to double click and have a class (in css under
.task.reminder
) that will change the task to the opposite of whatever is set and if it is true have border
- add a reminder toggle in
App.js
, and pass prop intoTasks
//Toggle Reminder
const toggleReminder = (id) => {
console.log(id);
}
return (
<div className="container">
<Header />
{/* IF THERE ARE TASKS, show Tasks, else show message */}
{tasks.length > 0 ? <Tasks tasks={tasks} onDelete={deleteTask} onToggle={toggleReminder} /> : 'No Tasks To Show'}
</div>
);
- catch the prop in
Tasks.js
, pass intoTask
and then add to main div inTask.js
- now we want to add logic to toggle reminder from true to false or false to true --> many ways to do this, we are going to use map
//Toggle Reminder
const toggleReminder = (id) => {
//use map to toggle --> map through `tasks` in our state and for each `task`
//where `task.id` in current iteration is equal to the id that's passed in
//then we have specific object (else the task)
//we want to copy && spread across all the task properties and values (of the task that matches) but want to change the reminder so the reminder i'm going to set is opposite of whatever that specific task reminder is
setTasks(tasks.map((task) => task.id === id ? {...task, reminder: !task.reminder } : task))
}
- now we can see our component tree --> look on App in Tree and check out State
- double click on first task and the state changes
- Nothing in UI that lets us know that the state has changed yet, use class of reminder in
Task.js
to add a border to the edge
- On
className
make it into an expression and still want the classtask
(that will be there no matter what), but add a condition in a template literal, iftask.reminder
is true then we're going to have the class ofreminder
, else nothing.
<div className={`task ${task.reminder ? 'reminder' : ''}`} onDoubleClick={() => onToggle(task.id)}>
<h3>
{task.text}
<FaTimes
style={{color: 'red', cursor: 'pointer'}}
onClick={() => onDelete(task.id)}
/>
</h3>
<p>{task.day}</p>
</div>
- still goes back to default after refresh because we are just using static data, if we had a back end you'd be making
fetch
orhttp
requests to your server as well.
- Create
AddTask.js
in Components --> Simple Form to Display for now
const AddTask = () => {
return (
<form className='add-form'>
<div className='form-control'>
<label>Task</label>
<input type='text' placeholder='Add Task' />
</div>
<div className='form-control'>
<label>Day & Time</label>
<input type='text' placeholder='Add Day & Time' />
</div>
<div className='form-control'>
<label>Set Reminder</label>
<input type='checkbox'/>
</div>
<input type='submit' value='Save Task' />
</form>
)
}
export default AddTask
- each input it going to have it's own piece of component level state NOT app level state, bring in
useState
and set up for each input with defaults (reminder = false, text and day = '')
import { useState } from 'react'
const AddTask = () => {
const [text, setText] = useState('')
const [day, setDay] = useState('')
const [reminder, setReminder] = useState(false)
- In
input
for text, thevalue
of text is going to be the text from the state but we also need anonChange
because when you start to type in the input that's going ot fire off this on change, it's a controlled component, going to have function where we pass in the event object and directly call setText from here and set it toe.target.value
which will be whatever is typed in, same forday
andsetDay
<div className='form-control'>
<label>Task</label>
<input
type='text'
placeholder='Add Task'
value={text}
onChange={(e) => setText(e.target.value)}
/>
- for the checkbox use
currentTarget.checked
--> give us either a true or false value if that is checked or not - Look in React-Dev-Tools under AddTask as we can see the state
- you can add in input text an state changes, hit save task and page back to default state right now
- in
App.js
add logic for ADD TASK --> it will take in task, and console.log task for now
//ADD TASK
const addTask = (task) => {
console.log(task)
}
- pass it into as prop on AddTask
return (
<div className="container">
<Header />
<AddTask onAdd={addTask} />
{/* IF THERE ARE TASKS, show Tasks, else show message */}
{tasks.length > 0 ? <Tasks tasks={tasks} onDelete={deleteTask} onToggle={toggleReminder} /> : 'No Tasks To Show'}
</div>
);
- in
AddTask.js
component we want to take inonAdd
and addonSubmit
event to form and set that toonSubmit
*define onSubmit above return statement, we are not calling onAdd directly
- onSubmit will take in the
event object
, we neede.preventDefault()
so it does not submit to a page - add validation for the task text --> if text is not there, do an alert --> if that passed then we are going to call
onAdd
and pass in an object with the text, day and reminder - then you also want to clear the form so call default state again
const onSubmit = (e) => {
e.preventDefault()
if (!text) {
alert('Please Add a Task')
return
}
onAdd({ text, day, reminder })
setText('')
setDay('')
setReminder(false)
}
- on checkbox reminder, set checked to reminder
<div className='form-control form-control-check'>
<label>Set Reminder</label>
<input
type='checkbox'
checked={reminder}
value={reminder}
onChange={(e) => setReminder(e.currentTarget.checked)}
/>
- Instead of console logging we want to add this to our state so in order to do that --> there are a bunch of ways --> since we are not dealing with a back end that creates an id we'll want to add an id to
addTask
function --> generate random number for id
//ADD TASK
const addTask = (task) => {
const id = Math.floor(Math.random() * 10000) + 1
console.log(id)
console.log(task)
}
7. Now we will create new task and add id, plus copy the task text, day and reminder and add to object as well.
- setTasks as array, copy tasks already there and add new task onto it
//ADD TASK
const addTask = (task) => {
const id = Math.floor(Math.random() * 10000) + 1
const newTask = {id, ...task}
setTasks([...tasks, newTask])
}
- Add new piece of state in the
App.js
forshowAddTask
and set to false as default
function App() {
const [showAddTask, setShowAddTask] = useState(false)
const [tasks, setTasks] = useState([
- embed in in AddTask, wrap in curly braces and say if
showAddTask
istrue
then show that component, shorter way of doing a ternary without an else, we just want ot see if it's true, is so do this, if not we do nothing.
return (
<div className="container">
<Header />
{showAddTask && <AddTask onAdd={addTask} />}
{/* IF THERE ARE TASKS, show Tasks, else show message */}
{tasks.length > 0 ? <Tasks tasks={tasks} onDelete={deleteTask} onToggle={toggleReminder} /> : 'No Tasks To Show'}
</div>
);
- Use button to toggle state, we need to do a few things
- Button is inside the
Header
--> add proponAdd
and add function to setShowAddTask and we want to set it to whatever the opposite is of the current state
return (
<div className="container">
<Header onAdd={() => setShowAddTask(!showAddTask)} />
{/* If showAddTask is true we show AddTask else we do nothing */}
{showAddTask && <AddTask onAdd={addTask} />}
{/* IF THERE ARE TASKS, show Tasks, else show message */}
{tasks.length > 0 ? <Tasks tasks={tasks} onDelete={deleteTask} onToggle={toggleReminder} /> : 'No Tasks To Show'}
</div>
);
- Pass into
Header.js
as prop and changeonClick
toonAdd
and button now toggles the form
const Header = ({ title, onAdd }) => {
return (
<header className='header'>
<h1>{title}</h1>
<Button color='green' text='Add' onClick={onAdd} />
</header>
)
}
- Update button style based on state
- in
App.js
, in addition to onAdd prop, pass in whatever the value of showAddTask is
return (
<div className="container">
<Header
onAdd={() => setShowAddTask(!showAddTask)}
showAdd={showAddTask}
/>
- back in
Header.js
, pass inshowAdd
prop and it will be either true or false if it is being shown or not - change button text to be dynamic, if showAdd is true then we want to show 'close' else we show 'add', do same for color
return (
<header className='header'>
<h1>{title}</h1>
<Button
color={showAdd ? 'red' : 'green'}
text={showAdd ? 'Close' : 'Add'}
onClick={onAdd}
/>
</header>
)
- this is where react is valuable because it allows you to create really dynamic interfaces and no pages are being reloaded or anything like that
- Vanilla javascript is just very messy and unorganized and much more difficult
- First how you want to build your static assets out if you are ready to deploy
yarn build
ornpm run build
depending on what you are using - will create an optimized production build in a folder called build
- this will be our static assets and what we use to deploy where you push to production
- you want to try this locally you can install the npm package
serve
globallysudo npm i -g serve
--> basic http server - now we can
serve -s build -p 8000
-p = port - SERVING UP APP LOCALLY
- Use mock backend --> JSON server NPM --> GitHub Repo for JSON Server
- can use our own data, install locally (NOT GLOBALLY)
- create
db.json
file with some data, can us POST, PUT, PATCH, etc.
npm i json-server
- create
script
inpackage.json
to run it,"server": "json-server --watch db.json --port 5000"
--> pretend it's a real backend/REST API
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"server": "json-server --watch db.json --port 5000"
},
- in terminal
npm run server
--> createsdb.json
file with some dummy data we'll delete, we also run our dev server in new terminal as wellnpm start
- we want to get data from
db.json
into dev server --> add tasks fromApp.js
and set state to empty array
function App() {
const [showAddTask, setShowAddTask] = useState(false)
const [tasks, setTasks] = useState([]);
- update tasks syntax
{
"tasks": [
{
"id": 1,
"text": "DMV Alumni Meeting",
"day": "July 30th at 1pm",
"reminder": true
},
{
"id": 2,
"text": "Log Coding Hours",
"day": "August 4th at 4pm",
"reminder": true
},
{
"id": 3,
"text": "Send Finished Crochet Sweater",
"day": "August 6th at 11am",
"reminder": false
}
]
}
- json server will automatically create id's on the back end so we won't have to worry about that anymore
- now we want to fetch the data from the backend
- if you visit
localhost:5000/tasks
you will see json of tasks - in order to load them when the page loads we are going to use a
hook
-->useEffect
--> use to create side effects or deal with side effects and if's often used if you want something to happen when the page loads --> it right below were weuseState
inApp.js
(also import like useState) --> takes in arrow function and we have to use async within the function itself --> and await the res/promise --> await data response from json --> console.log the data --> and then calfetchTasks()
--> add DEPENDENCY ARRAY at end, we don't have anything to pass in so it's just an empty array
useEffect(() => {
const fetchTasks = async () => {
const res = await fetch('http://localhost:5000/tasks')
const data = await res.json()
console.log(data)
}
fetchTasks()
}, []) //DEPENDENCY ARRAY
- As soon as page loads we are getting our data from our json server which you can replace with any backend
- we may want to use fetchTasks elsewhere so we are going to define it outside useEffect and instead have function to getTasks that will be async since fetchTasks returns a promise --> fetch tasks from server and await fetchTasks, and then setTasks and add tasks from server, and then call
getTasks()
- Brad has issue with using
task.id
askey
, and changed it toindex
instead but no issues on my app right now with how it is
- add logic to
deleteTask()
and make it async (since we'll be awaiting the server) --> don't need to save it as variable since we are not getting any data back --> add second argument of an object where we specify the method of this request to be a delete
//DELETE TASK
const deleteTask = async (id) => {
await fetch(`http://localhost:5000/tasks/${id}`, {
method: 'DELETE'
})
//for each task you want to filter where the task id is not equal to the id
setTasks(tasks.filter(task => task.id !== id));
};
- We want to be able to add a task and have it persist to our backend
- no longer need to create id in
addTask()
, because it assigns an id for us --> make function async --> await response/fetch --> route is just(server goes here)/tasks
--> with methodPOST
--> addheaders
since we are adding data and we need to specify our content type (application/json
) --> setbody
(data) that we are sending and set it to JSON.stringify which will turn it from a JS Object to JSON string and what we are sending is the task. - then we want to get the data here, data that is return is just the task that is added and then call
setTasks
again, since it is an array, take existing tasks and then add onto it, data, which is the newTask that was just created
const addTask = async (task) => {
const res = await fetch('http://localhost:5000/task', {
method: 'POST',
headers: {
'Content-type' : 'application/json'
},
body: JSON.stringify(task)
})
const data = res.json()
setTasks([...tasks, data])
}
- NOW I AM GETTING THE KEY ERROR FOR CHILD ELEMENT BRAD GOT BEFORE WITH
task.id
vsindex
--> not creating id anymore - ALSO NOT WORKING AT THE MOMENT, data in json file but not being served up to UI
- we forgot to
await
the response from json...
- will happen in
toggleReminder
but first you want to be able to get a single task from the server, copyfetchTasks
and change tofetchTask
and it's going to take in anid
--> and use id inroute
//FETCH TASK
const fetchTask = async (id) => {
const res = await fetch(`http://localhost:5000/tasks/${id}`)
const data = await res.json()
return data
}
- then go down to
toggleReminder
and we are going to update --> not updating a name but down the same way --> can add if you want - create variable to grab task we want to toggle, change to async, and await the task we are fetching, then we put in variable for updated task which is an event and going to have all same properties of
taskToToggle
and changereminder
to whatever the opposite oftaskToToggle.reminder
is - Now we save response to variable, doing update so we will want the id in route, and add method of PUT, add headers since we are sending data and need content type, Json.stringify the body/data
- then await on response and save to data and in setTasks, data.reminder to set state to updated task data.
//TOGGLE REMINDER
const toggleReminder = async (id) => {
const taskToToggle = await fetchTask(id)
const updTask = {...taskToToggle, reminder: !taskToToggle.reminder}
const res = await fetch(`http:localhost:5000/tasks/${id}`, {
method: 'PUT',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(updTask)
})
const data = await res.json()
setTasks(tasks.map((task) => task.id === id ? {...task, reminder: data.reminder } : task))
}
- Right now everything is in one single page in
App.js
- Can use
react-router-dom
package to create routes to different pages - Create Footer with Link to an About Page
- Create
Footer.js
component
const Footer = () => {
return (
<footer>
<p>Copyright © 2021</p>
<a href="/about">About</a>
</footer>
)
}
const About = () => {
return (
<div>
<h4>Version 1.0.0</h4>
,<a href="/">Go Back</a>
</div>
)
}
- Now bring about component into
App.js
and use router, so import fromreact-router-dom
--> we want to bring in two thingsBrowserRouter
(will use the HTML5 push state) --> give alias asRouter
andRoute
import { useState, useEffect } from 'react';
import { BrowserRouter as Router, Route} from 'react-router-dom'
import Header from './components/Header';
import Footer from './components/Footer';
import Tasks from './components/Tasks';
import AddTask from './components/AddTask'
import About from './components/About'
- wrap everything in our return around with
<Router>
and now that it is wrapped we can use routes, above Footer add inRoute
and pass inpath
of '/about' and pass incomponent
- right now it still all on one-page, we will need to wrap task and addTask component in it's own route
- But them in an index route
'/'
we also want to addexact
or its going to do the same thing and show the about since it's first going to match the slash, instead of component we can userender
, which takes an arrow function with props and point function to a set of parentheses and add a fragment, and add our embeddedtask
anaddTask
- now see how the page reloads now when we click on about or go back, we want to stop that from happen, instead of using
a
tag we are going to uselink
fromreact-router-dom
- import into
About.js
, replacea
withLink
andhref
withto
- Do Same thing in
Footer.js
--> now both are instant and does not reload the page - Get rid of the Add button when you click about, we can go to
Header.js
and import hookuseLocation
from react-router-dom
- allows you to look at the route we are currently on
- above return create
location
variable and set touseLocation
which gives us access tolocation.pathname
- wrap button in curly braces so we can add a condition that if equal to index then show the button
import PropTypes from 'prop-types'
import { useLocation } from 'react-router-dom'
import Button from './Button'
const Header = ({ title, onAdd, showAdd }) => {
const location = useLocation()
return (
<header className='header'>
<h1>{title}</h1>
{/* if location.pathname is equal to index then show button */}
{location.pathname === '/' && (
<Button
color={showAdd ? 'red' : 'green'}
text={showAdd ? 'Close' : 'Add'}
onClick={onAdd}
/>
)}
</header>
)
}