Title Text
The React fundamentals.
We're going to talk about only React for now.
By learning the core React framework and ideas, you'll be equipped to pick up, learn and work with any additional React libraries.
Components
React + the DOM
- Tell React what each component should render.
- React takes care of the DOM for you.
Let's get started!
Today we're going to be building a journal app that will let you keep a daily diary.
We'll start small and build it out as we learn more about React and what it can do for us.
Here's the code for exercise 1. Let's walk through it together first.
const JournalApp = () => {
// TODO: can you change the h1 to another element?
// how would we give the h1 a class name?
return React.createElement('h1', null, 'Hello World')
}
Let's render our first component and style the header correctly.
Time for exercise 1!
React.createElement is quite verbose...
const HelloWorld = () => {
// TODO: can you change the h1 to another element?
// how would we give the h1 a class name?
return <h1>Hello World</h1>
}
ReactDOM.render(<HelloWorld />,
document.getElementById('react-root')
)
JSX!
remember, JSX gets compiled to React.createElement.
It's just JavaScript!
Exercise 2
<HelloWorld /> is equivalent to: React.createElement(HelloWorld)
<h1 className="foo">Hello</h1> is equivalent to React.createElement('h1', { className: 'foo' }, 'Hello')
const HelloWorld = props => {
// TODO: pass through another prop to customise the greeting
// rather than it always be hardcoded as Hello
return <h1>Hello, {props.name}</h1>
}
ReactDOM.render(
<HelloWorld name="Jack" />,
document.getElementById('react-root')
)
we can pass properties (or, props) through to components to configure them or change their behaviour
Exercise 3
remember, props are all just JavaScript, so you can pass through any data type - these aren't HTML attributes
<HelloWorld
someProp={() => console.log('I am a function')}
someOtherProp={['an array', 'of things']}
someNumber={4}
someString="4"
/>
remember, this is not HTML and you are not limited to passing around strings.
Exercise 3
const bunchOfProps = {
name: 'Jack',
age: 25,
colour: 'blue',
}
ReactDOM.render(
<HelloWorld
name={bunchOfProps.name}
age={bunchOfProps.age}
colour={bunchOfProps.colour}
/>,
document.getElementById('react-root')
)
this is quite a lot to type and means manual work if the object gets more properties that you want to be props
const bunchOfProps = {
name: 'Jack',
age: 26,
colour: 'blue',
}
ReactDOM.render(
<HelloWorld {...bunchOfProps} />,
document.getElementById('react-root')
)
Exercise 4
the JSX spread syntax saves us manually listing all props and lets us apply an object as props to a component
it's easy to forget to pass a prop that a component needs, or make a typo
import ReactDOM from 'react-dom'
import React from 'react'
import PropTypes from 'prop-types'
const JournalApp = props => {
...
}
JournalApp.propTypes = {
// TODO: define the prop type for the name, age and location prop
}
HelloWorld.propTypes = {
name: PropTypes.string.isRequired,
colour: PropTypes.oneOf(['blue', 'red']).isRequired,
}
Exercise 5
Documenting your components with prop types
This seems like a chore, but trust me, you'll thank yourself in the future!
https://reactjs.org/docs/typechecking-with-proptypes.html
note that not all my examples use prop-types today, but that's just to keep them focused. My real code always uses them!
const AskQuestion = () => {
return <p>How is your day going today?</p>
}
const HelloWorld = props => {
return (
<div>
<AskQuestion />
<h1>
{props.greeting}, {props.name}
</h1>
</div>
)
}
Exercise 6
Components can render other components.
React components must start with a capital letter.
defining and then using a component
Managing state
Props
Data a component is given and uses but cannot change.
State
Data a component owns and can change.
Who has heard of React hooks?
Functional components
What we've used so far. These components cannot have state.
Class components
We define our components as classes. These components can have state.
In React 16.7 and before
Updating state
To update the state, we have to use functions that React provides.
This ensures React knows about our state change, and can act accordingly.
Updating state
this.setState({
newValue: 3,
})
In React 16.7 and before
React 16.8
Hooks!
Hooks solve a wide variety of seemingly unconnected problems in React that we’ve encountered over five years of writing and maintaining tens of thousands of components.
We are going to exclusively use hooks for this workshop
Because they have been widely adopted and are considered superior to the class based approach.
And conceptually they are easier to learn and understand :)
Hook one: useState
import {useState} from 'react'
const [name, setName] = useState()
const [name, setName] = useState(0)
const nameState = useState(0)
const name = nameState[0]
const setName = nameState[1]
useState
these are equivalent
but destructuring is much nicer
const [name, setName] = useState(0)
useState
the value itself
a function we use to set the value
const JournalHeader = () => {
// TODO: can you get rid of the `name` constant, and instead create some
// state using useState ? and then when the login button is clicked, set the
// name of the logged in person
const name = 'Jack'
const login = () => {
console.log('I was clicked!')
}
return (
<div className="journal-header-wrapper">
<h1 className="journal-header">Journal App</h1>
<h2 className="journal-subheader">Journal for {name}</h2>
<button className="journal-login" onClick={login}>
Login
</button>
</div>
)
}
Exercise 7
Passing state to child components
Lifting state up
const Parent = () => {
return <Child />
}
const JournalApp = () => {
return <JournalHeader />
}
Parent
Child
logged in name
it makes more sense for the parent component (JournalApp) to know the logged in user's name
Parent
Child
logged in name
this technique is known as lifting state up, and it's an important one to be comfortable with.
Parent
Child 1
parent owns the name state
Child 2
so now both its children can access it
<div>
<JournalHeader name={name} />
</div>
Exercise 8
If we have `name` as a piece of state, we can pass that as a prop to the journal header
but: how can we now have the login button update the state? Don't worry about this for now! That's the next exercise!
for now: const [name, setName] = useState('Jack') lets you define the starting value of a piece of state
This is a very common, and powerful pattern.
Take state that's in the parent, and pass it as a prop to a child (or many children).
Parent and child communication
Sometimes we'll have state in the parent
that we give to the child
And sometimes the child needs to let us know that the state has changed.
JournalApp
JournalHeader
name
hey parent, name changed!
Exercise 9
<JournalHeader
name={name}
onNameChange={setName}
/>
here's some data for you to render
and when that data changes, this is how you tell me about it
Rendering lists of data
const posts = [
{ id: 1, title: 'A fun day at the beach', date: '2019-04-10', body: '...' },
{ id: 2, ... },
...
]
often we have lists of data that we want to render
and we want to automatically render new posts if the length changes
const numbers = [1, 2, 3]
const newNumbers = numbers.map(function(x) {
return x * 2
})
// or
const newNumbers = numbers.map(x => x * 2)
newNumbers === [2, 4, 6]
mapping over arrays in JavaScript
Exercise 10
so could we take our array of posts, and map them into an array of <li>s ?
<ul>{posts.map((post, index) => {
return (
<li>{post.title}</li>
)
})}</ul>
Creating a <Post /> component to render a post
<ul>
{posts.map(post => {
return <li key={post.id}>{post.title}</li>
})}
</ul>
<ul>
{posts.map(post => {
return <Post key={post.id} post={post} />
})}
</ul>
Exercise 11
(We will start splitting up our components into multiple files soon!)
Creating a <Post /> component to render a post
you should get familiar with spotting when a bunch of HTML that's being rendered belongs in a separate component. Don't fear having lots of little components!
Quick aside
Tooling
This workshop purposefully avoids talking about tooling and set up. We're focusing purely on React today (but questions are welcome!)
But I want to take a few minutes to quickly talk about some of the things going on in the workshop behind the scenes.
Bundler
A tool that takes all of our code and generates a bundle of HTML, CSS and JS for us.
Today I'm using https://parceljs.org/, but I'm also a big fan of https://webpack.js.org/
Transpiler
A tool that takes our modern JS and converts it back into JS that older browsers can use. Depending on what browsers you support, these will do different transforms.
Today we're using https://babeljs.io/ with some presets.
Talk to me about this later if you're interested :)
Code formatting
I find formatting code rather mundane and boring - so I let tools do it for me!
https://prettier.io/ is fantastic and there are editor plugins available for all popular editors.
Getting data from a source.
so-fetch-js
import fetch from 'so-fetch-js'
fetch('/users').then(response => {
console.log(response.data)
})
You all have a local API running on your machine :)
http://localhost:3000/posts
Component lifecycle
https://reactjs.org/docs/react-component.html#the-component-lifecycle
Before React hooks
componentDidMount
https://reactjs.org/docs/react-component.html#componentdidmount
If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
Before React hooks
useEffect
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
WITH React hooks 😻
side effects
<Post id={1} />
<li>Post: ...</li>
React takes our JSX and renders HTML onto the screen. It will do this every time any data updates.
useEffect(() => {...})
The process of rendering and re-rendering can trigger side effects, which we define via the useEffect hook.
const [posts, setPosts] = useState(null)
useEffect(() => {
console.log('I get run on every render')
})
useEffect
by default, useEffect runs on every single render
useEffect
✋🛑
There are gotchas here!
const [posts, setPosts] = useState(null)
useEffect(() => {
console.log('I get run on every render')
})
useEffect
this can be dangerous and lets you easily get into an infinite loop situation!
const [posts, setPosts] = useState(null)
useEffect(() => {
console.log('I get run on every render')
})
useEffect
so useEffect takes a second argument: a list of dependencies, or:
things that if they change, we should re-run our effect.
const [posts, setPosts] = useState(null)
useEffect(() => {
console.log('I get run on every render')
}, [])
useEffect
empty array = there is nothing that would cause this effect to have to run again.
so it will only run once!
Exercise 12
let's use useEffect to fetch some posts
modelling the loading state and showing a spinner
const [posts, setPosts] = useState(null)
default state of posts
default state of null, not of an empty array
Conditional rendering in JSX
return post ? (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
) : (
<p>Loading</p>
)
you get used to the ternaries! They fit really nicely into JSX.
Update our journal to show a loading spinner whilst the posts are loading.
Hint: test the spinner by removing the `setPosts` call - else the posts load too quickly for you to see the spinner!
Exercise 13
forms in React
Controlled inputs
React controls the value of an input
And React controls the onChange event of an input.
<input
type="text"
name="post-id"
value={userPostInput}
onChange={e => setUserPostInput(e.target.value)}
/>
value: the actual value of the input box
onChange: what React calls when the user types in the box, so we can update the value
<input
type="text"
name="post-id"
value={userPostInput}
onChange={e => setUserPostInput(e.target.value)}
/>
e.target.value : the value inside the input at the time of the event
let's let the user log in and tell us their name!
Exercise 14
<input
type="text"
name="post-id"
value={userPostInput}
onChange={e => setUserPostInput(e.target.value)}
/>
hook up the form in the login modal to log the user in
Getting posts per user.
/posts?userId=1
fetches just posts for that user
PS: this is not a very secure API... 😱
so, when a user logs in, let's get their posts
const userIdForName = (name) => {
return {
'alice': 1,
'bob': 2,
'charlotte': 3
}[name]
}
how do we make useEffect re-run when something changes?
the dependency array!
useEffect(() => {
const userId = userIdForName(name)
if (!userId) return
fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
setPosts(response.data)
})
}, [name])
this is the important bit
Exercise 15
useEffect is incredibly powerful
multiple components in multiple files
ES2015 Modules
export default class Post extends Component {
...
}
import Post from './post'
My convention
File is named with the same name as the component, in lowercase.
posts.js = Posts component
Your convention
You should come up with your own rules that suit you and your team!
Let's tidy up our code
Can you extract the JournalHeader component into its own file?
Exercise 16
Extracting components
The journal header now contains a modal which could exist on its own
This would be cleaner and nicer:
const JournalHeader = props => {
const [isShowingModal, setIsShowingModal] = useState(false)
const showModal = () => setIsShowingModal(true)
return (
<div className="journal-header-wrapper">
<h1 className="journal-header">Journal App</h1>
<h2 className="journal-subheader">Journal for {props.name}</h2>
<button className="journal-login" onClick={showModal}>
Login
</button>
<LoginModal
isShowing={isShowingModal}
onSubmit={name => props.setName(name)}
onClose={() => setIsShowingModal(false)}
/>
)}
</div>
)
}
const JournalHeader = props => {
const [isShowingModal, setIsShowingModal] = useState(false)
const showModal = () => setIsShowingModal(true)
return (
<div className="journal-header-wrapper">
<h1 className="journal-header">Journal App</h1>
<h2 className="journal-subheader">Journal for {props.name}</h2>
<button className="journal-login" onClick={showModal}>
Login
</button>
<LoginModal
isShowing={isShowingModal}
onSubmit={name => props.setName(name)}
onClose={() => setIsShowingModal(false)}
/>
)}
</div>
)
}
Exercise 17
can you extract LoginModal so this works?
Showing posts
We'd like to update the <Post /> component so you can click on a post and expand it to see its contents.
We'd like to update the <Post /> component so you can click on a post and expand it to see its contents.
Can you introduce some state and a UI to the <Post /> component to make this happen?
Exercise 18
Advanced React
If you stopped now, you'd be set to build lots of React apps.
Welcome to Advanced React!
All the exercises for the fundamentals + advanced live in the same repository.
Today the exercises are much more discussion based - many approaches today have pros and cons.
Hooks
How do people feel about hooks?
useState?
useEffect?
Custom hooks
Logging in every time is annoying, isn't it?
Let's store the logged in username in local storage*
What would be nice if is we had a custom hook that did this for us:
const [username, setUsername] = useLocalStorage('')
*PS: this is not a good secure solution but it'll do for our needs!
Exercise 18
React's context API
Or, since hooks: the useContext hook.
What is context?
A way to share data in a big tree of components
state: foo = 1
state: foo = 1
hey, I need to know `foo` !
this.state.foo = 1
foo={foo}
foo={props.foo}
foo={props.foo}
foo={props.foo}
state: foo = 1
foo={foo}
foo={props.foo}
foo={props.foo}
foo={props.foo}
state: foo = 1
foo={...}
the 3 middle components
don't know or care about foo.
const Context = React.createContext(defaultValue);
React lets us define contexts that allow data to be shared without having to pass it through every layer on the component tree.
<ThemeContext.Provider value={'light'}>
<MyApp />
</ThemeContext.Provider>
// and then later on in any child component
<ThemeContext.Consumer>
{theme => (
<h1 style={{ color: theme === 'light' ? '#000' : '#fff' }}>
Hello world
</h1>
)}
</ThemeContext.Consumer>
Provider
The component that makes a value accessible to all consumers in the component tree.
Consumer
The component that can read the value of a piece of state.
Creating a context gives you:
and you can read from a piece of context using the useContext hook! 🎉
<MyBlog /> signedIn
<Posts />
<Post post={post} />
<UserActions />
<MyBlog /> signedIn
<Posts />
<Post post={post} />
<UserActions />
user actions needs to know if the user is signed in, to know if they can perform the actions
import React from 'react'
const AuthContext = React.createContext(false)
export default AuthContext
default value
import AuthContext from './auth-context'
<div>
<h1>Blog posts by Jack</h1>
<AuthContext.Provider value={signedIn}>
<Posts />
</AuthContext.Provider>
</div>
value for any consumers
any consumer in <Posts /> or below can now consume our auth context
import AuthContext from './auth-context'
import React, { useContext } from 'react'
const signedIn = useContext(AuthContext)
get the latest value of the AuthContext
let's rework our user login system using context
import { createContext } from 'react'
const AuthContext = createContext({
loggedInUserName: null,
})
export default AuthContext
AuthContext for our Journal
const [name, setName] = useState('')
const authContextValue = {
loggedInUserName: name,
}
Create the context based off the name state.
<AuthContext.Provider value={authContextValue}>
<JournalHeader name={name} setName={setName} />
...
</AuthContext.Provider>
Wrap our app in the provider.
import AuthContext from './auth-context'
import React, { useContext } from 'react'
const authContext = useContext(AuthContext)
const name = authContext.loggedInUserName
Update <JournalHeader /> to read the name from the context, and not be passed it as a prop
Exercise 19
Q: what are the pros and cons of context over passing props?
Updating the name via the context
Right now we read the name from context, but set it via a prop that's passed down. Let's fix that.
Update our AuthContext so it exposes the logged in name and a function that <JournalHeader /> can call to set it.
Exercise 20
Avoiding additional work when possible.
Only updating the context if values we care about change.
console.log('Updating the context')
const authContextValue = {
loggedInUserName: name,
setLoggedInUser: setName,
}
Calculate context on every render.
console.log('Updating the context')
const authContextValue = {
loggedInUserName: name,
setLoggedInUser: setName,
}
But we only want to update the context if the `name` changes. So we're wasting effort here.
useMemo to the rescue
memo = memoization
useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
const authContextValue = useMemo(() => {
console.log('Updating the context')
return {
loggedInUserName: name,
setLoggedInUser: setName,
}
}, [name])
useMemo to the rescue
the values that, when changed, should cause the context to be recalculated
(just like with useEffect!)
const authContextValue = useMemo(() => {
}, [name])
can you update the code to use useMemo?
Exercise 21
More custom hooks
Hooks are powerful, but we can create our own hooks too 🔥
Let's say we want to build a hook that fetches posts and caches them
(This is going to not be a proper implementation - just a simple one for demo purchases!)
We've built a tool to let us "login" as a specific user.
(Our app is very secure...)
const posts = usePostsLoader(userId)
let's think about what we'd like our API to be.
Hooks are great for pulling out "boring" details and lets you not worry about how they work behind the scenes.
const usePostsLoader = userId => {
const [postsCache, setPostsCache] = useState({})
useEffect(() => {
...
}, [userId])
return postsCache[userId]
}
custom hooks can use other hooks internally!
postsCache: {
1: [...],
2: [...]
}
/* if we have the cache, return it
* else make a network request.
*/
custom hooks can use other hooks internally!
const usePostsLoader = userId => {
const [postsCache, setPostsCache] = useState({})
useEffect(() => {
if (!userId) return
if (postsCache[userId]) {
return
} else {
fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
setPostsCache(cache => ({
...cache,
[userId]: response.data,
}))
})
}
}, [userId, postsCache])
return postsCache[userId]
}
the last line of a custom hook should be what the hook returns. This can be anything you want!
const usePostsLoader = userId => {
const [postsCache, setPostsCache] = useState({})
useEffect(() => {
if (!userId) return
if (postsCache[userId]) {
return
} else {
fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
setPostsCache(cache => ({
...cache,
[userId]: response.data,
}))
})
}
}, [userId, postsCache])
return postsCache[userId]
}
your task: implement the usePostsLoader!
Exercise 22
Controlled vs uncontrolled components
An uncontrolled component controls its own state.
A controlled component is told what its state is.
Why is this important?
Our app now lets us toggle between showing all posts, or just published posts.
we have an uncontrolled toggle component
const Toggle = props => {
const [on, setOn] = useState(false)
useEffect(() => {
props.onToggleChange(on)
}, [on, props])
return <span onClick={() => setOn(o => !o)}>{on ? 'YES' : 'NO'}</span>
}
so in the main app we have the `publishedOnly` state
const [publishedOnly, setPublishedOnly] = useState(false)
Only published posts? <Toggle onToggleChange={setPublishedOnly} />
spot the problem here
const [publishedOnly, setPublishedOnly] = useState(false)
Only published posts? <Toggle onToggleChange={setPublishedOnly} />
the same state is in two places
this is a recipe for bugs!
lets make the component controlled
const Toggle = props => {
return (
<span onClick={() => props.onChange(!props.on)}>
{props.on ? 'YES' : 'NO'}
</span>
)
}
no state in sight!
a prop tells us if we are on or not
we have a prop to tell our parent when we should change the state
lets make the component controlled
const Toggle = props => {
return (
<span onClick={() => props.onChange(!props.on)}>
{props.on ? 'YES' : 'NO'}
</span>
)
}
no state in sight!
a prop tells us if we are on or not
we have a prop to tell our parent when we should change the state
lets make the component controlled
no state in sight!
a prop tells us if we are on or not
a prop to tell our parent when we should change the state
Exercise 23
More hooks
useRef
useRef to get at DOM elements
If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes.
Autofocusing the login box
If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes.
Autofocusing the login box
const inputEl = useRef(null)
Autofocusing the login box
<input
type="text"
ref={inputEl}
value={loginName}
placeholder="jack"
onChange={e => setLoginName(e.target.value)}
/>
And how do we focus it? useEffect!
useEffect(() => {
if (inputEl.current && props.isShowing) {
inputEl.current.focus()
}
}, [inputEl, props.isShowing])
Focus the input when I load the modal
useEffect(() => {
if (inputEl.current && props.isShowing) {
inputEl.current.focus()
}
}, [inputEl, props.isShowing])
Exercise 24
useRef can hold onto any value
From the docs...
The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class.
useEffect(() => {
const timer = setTimeout(() => {
setState(count => count + 1)
}, 1000)
return () => clearTimeout(timer)
})
Why is this useful?
normally when you clear out timers via useEffect, you do so by returning an unsubscribe function.
useEffect(() => {
const timer = setTimeout(() => {
setState(count => count + 1)
}, 1000)
return () => clearTimeout(timer)
})
But what if we want to allow the user to click to clear the timer?
how do we get at the timer ID from an event handler?
const countTimerId = useRef(null)
useEffect(() => {
const timer = setTimeout(() => {
setState(count => count + 1)
}, 1000)
countTimerId.current = timer
return () => clearTimeout(timer)
})
But what if we want to allow the user to click to clear the timer?
we can store the timer ID in a ref!
const stopCounting = () => {
clearTimeout(countTimerId.current)
}
But what if we want to allow the user to click to clear the timer?
we can store the timer ID in a ref!
Exercise 25
Extracting context usage into hooks
Hooks are great for hiding implementation details away.
const { loggedInUserName, setLoggedInUser } = useContext(AuthContext)
this doesn't feel like we're hiding things away.
const { loggedInUserName, setLoggedInUser } = useContext(AuthContext)
// compared to:
const { loggedInUserName, setLoggedInUser } = useAuth()
this feels better (fewer implementation details)
import { useContext } from 'react'
import AuthContext from './auth-context'
const useAuth = () => {
const context = useContext(AuthContext)
return context
}
export { useAuth }
let's use our useAuth hook
Exercise 26
Hiding more context details in the hook.
Hiding the creation of AuthContext
const AuthContext = createContext()
const useAuth = () => {
const context = useContext(AuthContext)
return context
}
We'll need to create a provider.
return (
<div>
<AuthContext.Provider value={authContextValue}>
<JournalHeader />
...
we don't have this Provider available in our components now the context is created in use-auth.js
props.children
<MyCustomComponent>
<p>hello world</p>
</MyCustomComponent>
you can refer to the given children as props.children within a component
this allows your custom component to take children and render them, but wrapped in something
our AuthProvider
1. Declare the initial value for the auth context
2. Have a piece of state that can be updated to update the context.
3. Wraps the children it is given in the AuthContext.Provider component.
our AuthProvider
const AuthProvider = props => {
const [loggedInUserName, setLoggedInUserName] = useState('')
const authContext = useMemo(() => {
return {
loggedInUserName,
setLoggedInUserName,
}
}, [loggedInUserName])
const { children, ...otherProps } = props
return (
<AuthContext.Provider value={authContext} {...otherProps}>
{children}
</AuthContext.Provider>
)
}
And then wrap our app in this provider
ReactDOM.render(
<AuthProvider>
<JournalApp />
</AuthProvider>,
document.getElementById('react-root')
)
let's walk through this in code
Exercise 27
there's no TODO here - we're going to walk through the code on screen and play with it.
Higher order components
Higher order functions
Higher order functions
const adder = x => y => x + y
const adder = function(x) {
return function(y) {
return x + y
}
}
Higher order functions
const adder = x => y => x + y
const addTwo = adder(2)
addTwo(3) // 5
Higher order components
A higher order component is (slightly confusingly) a function that returns a React component.
Higher order components
Why is this useful?
Remember the AuthProvider from the previous exercise?
Rather than make the end user wrap their component in <AuthProvider />, we could provide a function that does this for them.
const wrapWithAuth = Component => {
return Component
}
const wrapWithAuth = Component => {
return Component
}
// usage:
const JournalWithAuth = wrapWithAuth(JournalApp)
const wrapWithAuth = Component => {
const ComponentWrapped = props => {
}
return ComponentWrapped
}
const wrapWithAuth = Component => {
const ComponentWrapped = props => {
return (
<AuthProvider>
<Component {...props} />
</AuthProvider>
)
}
return ComponentWrapped
}
const wrapWithAuth = Component => {
const ComponentWrapped = props => {
return (
<AuthProvider>
<Component {...props} />
</AuthProvider>
)
}
return ComponentWrapped
}
Exercise 28
useReducer
Redux?
From the docs
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Fragment>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</Fragment>
);
}
A counter component in React
Don't reach for this too soon!
Don't assume you need this: start with useState and go for this if you then realise you need the structure.
One place where we have slightly more complex state is in our use-posts-loader
So let's update it to use a reducer internally.
const initialState = {}
const reducer = (state, action) => {
}
const usePostsLoader = userId => {
const [postsCache, setPostsCache] = useState({})
const [ state, dispatch ] = useReducer(reducer, initialState)
const reducer = (state, action) => {
switch (action.type) {
case 'newPostsForUser':
return {
...state,
[action.userId]: action.posts,
}
}
}
// usage:
dispatch({
type: 'newPostsForUser',
userId: 1,
posts: [...]
})
const reducer = (state, action) => {
switch (action.type) {
case 'newPostsForUser':
return {
...state,
[action.userId]: action.posts,
}
}
}
// usage:
dispatch({
type: 'newPostsForUser',
userId: 1,
posts: [...]
})
const reducer = (state, action) => {
switch (action.type) {
case 'newPostsForUser':
return {
...state,
[action.userId]: action.posts,
}
}
}
// usage:
dispatch({
type: 'newPostsForUser',
userId: 1,
posts: [...]
})
useEffect(() => {
if (!userId) return
if (state[userId]) {
return
} else {
fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
dispatch({
type: 'newPostsForUser',
userId: userId,
posts: response.data,
})
})
}
}, [userId, state])
const usePostsLoader = userId => {
const [postsCache, setPostsCache] = useState({})
const [state, dispatch] = useReducer(reducer, initialState)
useEffect(() => {
...
}, [userId, state])
return state[userId]
}
notice we now return from the redux state
And it works 🎉
Worth realising: we just entirely changed how we load and store our posts cache, and none of our components even know about it. This is one of the most powerful things about hooks.
Your turn ⬇
useEffect(() => {
if (!userId) return
if (state[userId]) {
return
} else {
fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
dispatch({
type: 'newPostsForUser',
userId: userId,
posts: response.data,
})
})
}
}, [userId, state])
Exercise 29
Lifting state up: reducer style.
User
Which user of our system is currently logged in.
Posts
The posts that we are showing to the given user.
these two bits of state are quite directly related.
So let's lift the state up
Our main component should house all this state.
We'll say goodbye to the use-posts-loader for now, but we can talk about how you'd bring it back once we've done the work.
We're also going to say goodbye to the useAuth context loader, and use the reducer state instead.
Both useReducer and useContext are useful tools
This exercise isn't me saying that I prefer useReducer over useContext, but showing you a different way you could solve the same problem. Each problem warrants a different solution - there is no direct rule on useReducer vs useContext
const initialState = {
loggedInUser: {
name: '',
},
postsForUser: [],
}
const reducer = (state, action) => {
switch (action.type) {
case 'logUserIn':
return {
...state,
loggedInUser: {
name: action.newUserName,
},
}
case 'gotPostsForUser':
return {
...state,
postsForUser: action.posts,
}
default: {
console.error(`Unknown action! ${action}`)
return state
}
}
}
useEffect(() => {
const userId = userIdForName(state.loggedInUser.name)
if (!userId) return
fetch(`http://localhost:3000/posts?userId=${userId}`).then(response => {
dispatch({
type: 'gotPostsForUser',
posts: response.data,
})
})
}, [state.loggedInUser.name])
we now move loading posts back into a useEffect block that dispatches an action
<JournalHeader loggedInUser={state.loggedInUser} dispatch={dispatch} />
and we now pass through the loggedIn state and the dispatch function to the JournalHeader
this is important! This is how you allow child components to update the state.
const loggedInUserName = props.loggedInUser.name
const setLoggedInUser = name => {
props.dispatch({
type: 'logUserIn',
newUserName: name,
})
}
And within JournalHeader we can dispatch an action when someone wants to log in.
No exercise here, just have a play with the code locally.
What do you think are the pros and cons of useReducer at the top level like this?
Should our publishedOnly toggle state live in the reducer state? What do you think?
Exercise 30
Components are for UI, not for logic.
const logInAs = username => event => {
event.preventDefault()
const currentTime = Date.now()
const userId = userIdForName(username)
fetch(`http://localhost:${apiPort}/users/${userId}`, {
method: 'PUT',
body: JSON.stringify({
lastLogInTime: currentTime,
name: username,
}),
headers: {
'Content-Type': 'application/json',
},
})
dispatch({
type: 'logUserIn',
newUserName: username,
})
}
Logging when a user logs in
Code that doesn't relate to the UI should not be in your React components.
const logInAs = username => event => {
event.preventDefault()
const currentTime = Date.now()
const userId = userIdForName(username)
fetch(`http://localhost:${apiPort}/users/${userId}`, {
method: 'PUT',
body: JSON.stringify({
lastLogInTime: currentTime,
name: username,
}),
headers: {
'Content-Type': 'application/json',
},
})
dispatch({
type: 'logUserIn',
newUserName: username,
})
}
vs:
const logInAs = username => event => {
event.preventDefault()
updateLastLoggedInTime(username);
dispatch({
type: 'logUserIn',
newUserName: username,
})
}
Extract out the logging user in functionality
Where do you think it should go? What are our options?
Exercise 31
Choosing data structures that eliminate bugs.
Our journal lets us tag our posts.
But there's a bug!
const onTagSubmit = event => {
event.preventDefault()
// you would make an API call here but we're ignoring this for now
setTags(oldTags => [...oldTags, newTagValue])
setNewTagValue('')
}
Text
How do we fix it?
Add code to ensure that our array contains only unique items 🐛
Change our data structure to forbid duplicate values 🐛👌
What if we used a set?
const [tags, setTags] = React.useState(new Set(props.initialTags))
Exercise 32
PS: the MDN docs on Sets are very good 👌✅
GraphQL
GraphQL
One API endpoint.
Describe what data you want, and get back exactly that.
No extra data that you don't need. One request for all the data your page requires.
query {
users{
id
name
}
}
{
"data": {
"users": [
{
"id": "1",
"name": "alice"
},
{
"id": "2",
"name": "bob"
},
{
"id": "3",
"name": "charlotte"
}
]
}
}
query {
users{
id
name
posts {
title
}
}
}
{
"data": {
"users": [
{
"id": "1",
"name": "alice",
"posts": [
{
"title": "Watched the new Game of Thrones!"
},
{
"title": "Went to a cracking react workshop"
}
]
},
{
"id": "2",
"name": "bob",
"posts": [
{
"title": "Attended the React workshop"
},
{
"title": "Played football in the park"
},
{
"title": "Checked my credit score"
},
{
"title": "Watched the 2018 world cup higlights again"
}
]
},
{
"id": "3",
"name": "charlotte",
"posts": [
{
"title": "did some tweeting"
}
]
}
]
}
}
localhost:4000/graphql
(port might vary - please check the output of `npm start`)
Talking to GraphQL from React
It's just an HTTP request!
fetch(`http://localhost:${graphqlPort}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query {
posts {
id
title
date
body
published
}
}
`,
}),
}).then(result => {
console.log('result', result.data)
})
Posts per user and query variables
query postsForUser($id:ID!) {
userById(id:$id){
posts {
id
title
}
}
}
fetch(`http://localhost:${graphqlPort}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query postsForUser($id: ID!) {
userById(id: $id) {
posts {
id
title
date
body
published
}
}
}
`,
variables: { id: userId },
}),
fetch(`http://localhost:${graphqlPort}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query postsForUser($id: ID!) {
userById(id: $id) {
posts {
id
title
date
body
published
}
}
}
`,
variables: { id: userId },
}),
Exercise 33
Could we create a useGraphQL hook?
What would that look like?
Advanced React: fin!
React Fundamentals 2019
By Jack Franklin
React Fundamentals 2019
- 856