Babel is used for converting the newer versions of EcmaScript to ES5 which can run on all browsers.
- Constructor
Good place to do one time setup. Like viewDidLoad() - render()
Avoid anything besides returning JSX. - componentDidMount
Good place to do data loading. - componentDidUpdate
Good place to do more data loading when state/props change.
ex) Tapping a button and update the UI - componentWillMount
Good place to cleanup. Like viewWillDissapear().
- User types in input
- Callback gets invoked
- We call setState with the new value
- Component rerenders
- Input is told what its value is (coming from state)
class SearchBar extends React.Component {
state = { term: '' }
// e: event = an JS object which contains a bunch of information about the event just occured
render() {
return (
<div className="ui segment">
<form className="ui form">
<div className="field">
<label>Image Search</label>
<input
type="text"
value={this.state.term}
onChange={e => this.setState({ term: e.target.value })}
/>
</div>
</form>
</div>
)
}
}
React knows the value of the element. It doesn't go look into DOM in order to get the value of input.
<input value={this.state.term} />
Only HTML knows the value of the element. (Store data inside of DOM) It looks into DOM in order to get the value of input.
<input value="Hi there" />
'this' is used to refer to the instance of the component.
class Car {
setDriveSound(sound) {
this.sound = sound
}
drive() {
return this.sound
}
}
const car = new Car()
car.setDriveSound('vroom')
// 'this' inside of drive() refers to 'car' variable here.
car.drive()
const truck = {
sound: 'putputput',
driveMyTruck: car.drive // refers to 'truck' variable
}
truck.driveMyTruck()
const drive = car.drive
// drive() would be undifined because there is no object to be refered by 'this'
// drive()
- bind() in constructor
constructor() {
this.drive = this.drive.bind(this)
}
- Arrow function
Arrow function automatically bind 'this'
// Before
onFormSubmit(event) = {
// Prevent the page from refreshing
event.preventDefault()
console.log(this.state.term)
}
// After
onFormSubmit = (event) => {
// Prevent the page from refreshing
event.preventDefault()
console.log(this.state.term)
}
- Pass the function using arrow function
onSubmit={event => this.onFormSubmit(event)}
- Gives access to a single DOM element
- We create refs in the constructor, assign them to instance variables, then pass to a particular JSX element as props
A function that is going to create or return a plain Javascript object (refered as an action) which has type property and a payload property. The type property on in-action describes some change that we want to make inside of our data. The payload property describes some contexts around the change that we want to make.
The purpose of an action is to describe some changes we want to make to the data inside of our application. The action is the only way to change our redux app's state.
The dispatch function takes in an action, and makes a copy of the object, then pass it off to a bunch of different places inside of our application.
The function that is responsible for taking in an action and some amount of existing data. It's going to process that action, and make some changes to the data, then return it so that it can be centralized in some other location.
- Must return any value besides 'undefined'
- Produces 'state', or data to be used inside of the app using only previous state and action
- Must not return reach 'out of itself' to decide what value to return (reducers are pure)
Only be allowed to use app's state and action. - Must not mutate its input 'state' argument
This is because if we mutate the state argument inside of the reducer, the entire app will rerender. We want to definitely avoid that.
Mutation:
// Array: push
const colors = ['red', 'blue']
colors.push('green')
// Array: pop
colors.pop('green')
// Object: Update an object
const person = { name: 'Sam' }
person.name = 'Alex'
// Object: Delete a key/value pair
delete person.name
Manipulation: // Create a brand new array, object, etc.
// Array: push
const colors = ['red', 'blue']
[...colors, 'green']
// Array: pop
colors.filter(color => color !== 'green') // return a brand new array without 'green'
// Object: Update an object
const person = { name: 'Sam' }
{ ...person, name: 'Alex', age: 30 } // Create a brand new object with different values
// Object: Delete a key/value pair
_.omit(person, 'name')
The central repository of all information that has been created by a reducer.
console.clear()
// Action creators (people dropping off a form)
const createPolicy = (name, amount) => {
return { // Return an action (a form)
type: 'CREATE_POLICY',
payload: {
name: name,
amount: amount
}
}
}
const deletePolicy = name => {
return {
type: 'DELETE_POLICY',
payload: {
name: name
}
}
}
const createClaim = (name, amountOfMoneyToCollect) => {
return { // Return an action (a form)
type: 'CREATE_CLAIM',
payload: {
name: name,
amountOfMoneyToCollect: amountOfMoneyToCollect
}
}
}
// Reducers (each departments)
// Initialize with '= []' when there is no data
const claimsHistory = (oldListOfClaims = [], action) => {
if (action.type === 'CREATE_CLAIM') {
// We care about this action (form)
// '...' means taking out the all elements and add them to a new array with action.payload
// const numbers = [1, 2, 3]
// [...numbers, 4] -> [1, 2, 3, 4]
return [...oldListOfClaims, action.payload]
}
// // We don't care about this action (form)
return oldListOfClaims
}
const accounting = (bagOfMoney = 100, action) => {
if (action.type === 'CREATE_CLAIM') {
// We care about this action (form)
return bagOfMoney - action.payload.amountOfMoneyToCollect
} else if (action.type === 'CREATE_POLICY') {
return bagOfMoney + action.payload.amount
}
// // We don't care about this action (form)
return bagOfMoney
}
const policies = (oldListOfPolicies = [], action) => {
if (action.type === 'CREATE_POLICY') {
// We care about this action (form)
return [...oldListOfPolicies, action.payload.name]
} else if (action.type === 'DELETE_POLICY') {
return oldListOfPolicies.filter(name => name !== action.payload.name)
}
// // We don't care about this action (form)
return oldListOfPolicies
}
const { createStore, combineReducers } = Redux
// Wire up all the functions using combineReducers()
const departments = combineReducers({
accounting: accounting,
claimsHistory: claimsHistory,
policies: policies
})
const store = createStore(departments)
const action = createPolicy('Alex', 20)
store.dispatch(action)
store.dispatch(createPolicy('Jim', 40))
store.dispatch(createPolicy('Bob', 30))
store.dispatch(createClaim('Alex', 120))
store.dispatch(createClaim('Jim', 50))
store.dispatch(deletePolicy('Bob'))
console.log(store.getState())
- Call an action creator to change state of our app.
- The action creator produce an action.
- The action gets fed to a dispatch.
- The dispatch forwards the action to reducers.
- The reducers create new state.
- The state wait until we need to update state again.
-
Provider
The store from redux is passed into a Provider as props. The Provider offers the states from Redux to App component. -
Connect
Create an instance of connect component which communicates with the Provider via context system.
function connect() {
return function() {
return 'Hi, there!'
}
}
connect()()
Redux-thunk is a middleware to help us make requests in a redux application.
It's essencially a bunch of functions to change redux store behavior, or adding additional functionalities.
Redux-thunk makes the rules around Action Creator more flexible.
- Action creators must return action objects
- Actions must have a type property
- Actions can optionally have a 'payload'
- Action creators can return actions objects or functions
- If an action object gets returned, it must have a type property
- If an action object gets returned, it can optionally have a 'payload'
- Components gets rendered onto the screen
- Component's 'componentDidMount' lifecycle method gets called
- We call an action creator from 'componentDidMount'
- Action creator runs code to make an API request
- API responds with data
- Action creator returns an 'action' with the fetched data on the 'payload' property
- Some reducer sees the action, returns the data off the 'payload'
- Because we generated some new state object, redux/react-redux cause our React app to be rendered
-
1 - 3
Components are generally responsible for fetching data they need by calling an action creator -
4 - 6
Action creators are responsible for making API requests (This is where redux-thunk comes into play) -
7 & 8
We get fetched data into a component by generating new state in our redux store, then getting that into our component through mapStateProps
await
and async
doesn't work with an action creator basically, since it it is a new feature in a higher versions. When it gets converted to ES2015 by Babel, it cannot simply return a plain JS object.
export const fetchPosts = async () => {
const response = await jsonPlaceholder.get('/posts')
return {
type: 'FETCH_POSTS',
payload: response
}
}
Even though we don't use await
and async
, we still might not work as we expect. The example below doesn't work because there is no data available by the time reducers run.
export const fetchPosts = () => {
const promise = jsonPlaceholder.get('/posts')
return {
type: 'FETCH_POSTS',
payload: promise
}
}
- It has to return an plain JS object.
Middlewares are needed to make asyncronous action creators in Redux.
-
Synchronous action creator
Instantly returns an action with data ready to go -
Asyncronous action creator Takes some amount of time for it to get its data ready to go
- Function that gets called with every action we dispatch
- Has the ability to STOP, MODIFY, or otherwise mess around with actions
- Tons of open source middleware exist
- Most popular use of middleware is for dealing with async actions
- We are going to use a middleware called 'redux-thunk' to solve our async issues
Navigate around using <a></a>
is a really bad practice in React/Redux app.
The reason is that when the browser receives index.html file from the server, the browser dumps the old html file including React/Redux state data
Use <Link></Link>
in React/Redux app to navigate.
- It prevents the browser from navigating to the new page and fetching new index.html file
NOTE: With react-router, each component needs to be designed to work in isolation (fetch its own data)
Only shows one route that is first found.
<Switch>
<Route path="/" exact component={StreamList} />
<Route path="/streams/new" exact component={StreamCreate} /> // First
<Route path="/streams/edit/:id" exact component={StreamEdit} />
<Route path="/streams/delete/:id" exact component={StreamDelete} />
<Route path="/streams/:id" exact component={StreamShow} /> // Second
</Switch>
- Store a record in a database with the user's email and password
- When the user tries to login, we compare email/pw with whats stored in DB
- A user is 'logged in' when they enter the correct email/pw
- User authentications with outside service provider (Google, Facebook, Twitter)
- User authorizes our app to access their info
- Outside provider tells us about the user
- We are trusting the outside provider to correctly handle identification of a user
- OAuth can be used for (1) user identification in our app and (2) our app making actions on behalf of user
- Results in a 'token' that a server can use to make requests on behalf of the user
- Usually used when we have an app that needs to access user data when they are not logged in
- Difficult to setup because we need to store a list of info about the user
- Results in a 'token' that a browser app can use to make requests on behalf of the user
- Usually used when we have an app that only needs to access user data while they are logged in
- Very easy to setup thanks to Google's JS lib to automate flow
- Create a new project at console.developers.google.com/
- Set up an OAuth confirmation screen
- Generate an OAuth Client ID
- Install Google's API library, initialize it with the OAuth Client ID
- Make sure the lib gets called any time the user clicks on the 'Login with Google' button
renderInput(formProps) {
return (
<input
onChange={formProps.input.onChange}
value={formProps.input.value}
/>
)
}
// Shorthand 1
renderInput(formProps) {
return <input {...formProps.input} />
}
// Shorthand 2
renderInput({ input }) {
return <input {...input} />
}
Rest conventions, or restful conventions are a predefined system for defining different routes on an API that work with a given type of records.
-
Action
- List all records
- Get one record
- Create record
- Update a record
- Delete a record
-
Method
- GET
- GET
- POST
- PUT
- DELETE
-
Route
- /streams
- /streams:id/
- /streams
- /streams:id/
- /streams:id/
Note:
PUT will update all properties.
PATCH will some properties.
// Array-based
const streamReducer = (state=[], action) => {
switch (action.type) {
case EDIT_STREAM:
return state.map(stream => {
if (stream.id === action.payload.id) {
return action.payload
} else {
return stream
}
})
default:
return state
}
}
// Object-based
// Use [] for the key interpolation
const streamReducer = (state={}, action) => {
switch (action.type) {
case EDIT_STREAM:
return { ...state, [action.payload.id]: action.payload }
default:
return state
}
}
Potal allows us to render some element not as a direct child. We can render that element or that component as a child of some other element inside of our HTML structure. (Most commonly the <body>
tag.)
- Use ReactDOM.createPortal() to create a modal
- Need another
<div>
inside of the root index.html to place the modal directly to<body>
const Modal = props => {
return ReactDOM.createPortal(
<div className="ui dimmer modals visible active">
<div className="ui standard modal visible active">
fdafaweofjaoif
</div>
</div>,
document.querySelector('#modal')
)
}
React fragment is a JSX looking element that allows us to return multiple elements to a single variable. When it gets rendered onto the screen, it doesn't produce any HTML. Therefore, it doesn't have any affects on CSS or something.
It allows us to get data from a parent component to any nested child component.
- Default value
- Make the parent component
Provider
this.context
Consumer
- Method 1
class Button extends React.Component {
static contextType = LanguageContext
}
- Method 2
class Button extends React.Component {
}
Button.contextType = LanguageContext
Provides values to the child's context. It creates different pipe with a different values depending on the number of <Provider>
// First
<LanguageContext.Provider value={this.state.language}>
<UserCreate />
</LanguageContext.Provider>
// Second (Separate from the first one)
<LanguageContext.Provider value="english">
<UserCreate />
</LanguageContext.Provider>
<LanguageContext.Consumer>
{(value) => value === 'english' ? 'Name' : 'Voorleggen'}
</LanguageContext.Consumer>
Hooks are used to use state in functional components.
useState()
allows a functional component to use component-level state. useState()
returns an array of two values which are a state name and a named function to set a value to the state. const [resource, setResoource] = useState('posts')
uses the ES6 destructuring to extract the values and set 'posts'
as a default value.
import React, { useState } from 'react'
const App = () => {
const [resource, setResoource] = useState('posts')
return (
<div>
<div>
<button onClick={() => setResoource('posts')}>Posts</button>
<button onClick={() => setResoource('todos')}>ToDos</button>
</div>
{resource}
</div>
)
}
useEffect()
allows a functional component to use 'lifecycle methods'.
// [resource] means that if there is a change, make a request
useEffect(() => {
fetchResource(resource)
}, [resource])
The code which causes infinite loop.
async componentDidMount() {
const response = await axios.get(`https://jsonplaceholder.typicode.com/${this.props.resource}`)
this.setState({ resources: response.data })
}
async componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.resource !== this.props.resource) {
const response = await axios.get(`https://jsonplaceholder.typicode.com/${this.props.resource}`)
this.setState({ resources: response.data })
}
}
A better way using useEffect()
const [resources, setResoources] = useState([])
const fetchResource = async (resource) => {
const response = await axios.get(`https://jsonplaceholder.typicode.com/${resource}`)
setResoources(response.data)
}
useEffect(() => {
fetchResource(resource)
}, [resource])
// Equal to componentDidMount()
useEffect(() => {}, [])
// Control how many times the callback gets called
useEffect(() => {}, [1])
NOTE: The callback of useEfect()
itself cannot be async
.
// Correct: Separate function
const fetchResource = async (resource) => {
const response = await axios.get(`https://jsonplaceholder.typicode.com/${resource}`)
setResoources(response.data)
}
useEffect(() => {
fetchResource(resource)
}, [resource])
// Correct: Invoking immediately
useEffect(() => {
(async resource => {
const response = await axios.get(`https://jsonplaceholder.typicode.com/${resource}`)
setResources(response.data)
})(resource)
}, [resource])
// Wrong
useEffect( async () => {
const response = await axios.get(`https://jsonplaceholder.typicode.com/${resource}`)
setResoources(response.data)
}, [resource])