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!
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
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
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
Data a component is given and uses but cannot change.
Data a component owns and can change.
What we've used so far. These components cannot have state.
We define our components as classes. These components can have state.
In React 16.7 and before
This ensures React knows about our state change, and can act accordingly.
this.setState({
newValue: 3,
})
In React 16.7 and before
Hooks!
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]
these are equivalent
but destructuring is much nicer
const [name, setName] = useState(0)
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
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
Take state that's in the parent, and pass it as a prop to a child (or many children).
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
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>
<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!)
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!
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.
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/
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 :)
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.
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
https://reactjs.org/docs/react-component.html#the-component-lifecycle
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.
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.
<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')
})
by default, useEffect runs on every single render
✋🛑
There are gotchas here!
const [posts, setPosts] = useState(null)
useEffect(() => {
console.log('I get run on every render')
})
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')
})
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')
}, [])
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
const [posts, setPosts] = useState(null)
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
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
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
fetches just posts for that user
PS: this is not a very secure API... 😱
const userIdForName = (name) => {
return {
'alice': 1,
'bob': 2,
'charlotte': 3
}[name]
}
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
export default class Post extends Component {
...
}
import Post from './post'
posts.js = Posts component
Exercise 16
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?
Can you introduce some state and a UI to the <Post /> component to make this happen?
Exercise 18
How do people feel about hooks?
useState?
useEffect?
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
Or, since hooks: the useContext hook.
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>
The component that makes a value accessible to all consumers in the component tree.
The component that can read the value of a piece of state.
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
import { createContext } from 'react'
const AuthContext = createContext({
loggedInUserName: null,
})
export default AuthContext
const [name, setName] = useState('')
const authContextValue = {
loggedInUserName: name,
}
<AuthContext.Provider value={authContextValue}>
<JournalHeader name={name} setName={setName} />
...
</AuthContext.Provider>
import AuthContext from './auth-context'
import React, { useContext } from 'react'
const authContext = useContext(AuthContext)
const name = authContext.loggedInUserName
Exercise 19
Exercise 20
console.log('Updating the context')
const authContextValue = {
loggedInUserName: name,
setLoggedInUser: setName,
}
console.log('Updating the context')
const authContextValue = {
loggedInUserName: name,
setLoggedInUser: setName,
}
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])
the values that, when changed, should cause the context to be recalculated
(just like with useEffect!)
const authContextValue = useMemo(() => {
}, [name])
Exercise 21
(This is going to not be a proper implementation - just a simple one for demo purchases!)
(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
const Toggle = props => {
const [on, setOn] = useState(false)
useEffect(() => {
props.onToggleChange(on)
}, [on, props])
return <span onClick={() => setOn(o => !o)}>{on ? 'YES' : 'NO'}</span>
}
const [publishedOnly, setPublishedOnly] = useState(false)
Only published posts? <Toggle onToggleChange={setPublishedOnly} />
const [publishedOnly, setPublishedOnly] = useState(false)
Only published posts? <Toggle onToggleChange={setPublishedOnly} />
the same state is in two places
this is a recipe for bugs!
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
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
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
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.
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.
const inputEl = useRef(null)
<input
type="text"
ref={inputEl}
value={loginName}
placeholder="jack"
onChange={e => setLoginName(e.target.value)}
/>
useEffect(() => {
if (inputEl.current && props.isShowing) {
inputEl.current.focus()
}
}, [inputEl, props.isShowing])
useEffect(() => {
if (inputEl.current && props.isShowing) {
inputEl.current.focus()
}
}, [inputEl, props.isShowing])
Exercise 24
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)
})
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)
})
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)
})
we can store the timer ID in a ref!
const stopCounting = () => {
clearTimeout(countTimerId.current)
}
we can store the timer ID in a ref!
Exercise 25
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
const AuthContext = createContext()
const useAuth = () => {
const context = useContext(AuthContext)
return context
}
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
<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
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.
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>
)
}
ReactDOM.render(
<AuthProvider>
<JournalApp />
</AuthProvider>,
document.getElementById('react-root')
)
Exercise 27
there's no TODO here - we're going to walk through the code on screen and play with it.
const adder = x => y => x + y
const adder = function(x) {
return function(y) {
return x + y
}
}
const adder = x => y => x + y
const addTwo = adder(2)
addTwo(3) // 5
A higher order component is (slightly confusingly) a function that returns a React component.
Why is this useful?
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 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
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
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.
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
Which user of our system is currently logged in.
The posts that we are showing to the given user.
these two bits of state are quite directly related.
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.
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.
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
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,
})
}
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,
})
}
const logInAs = username => event => {
event.preventDefault()
updateLastLoggedInTime(username);
dispatch({
type: 'logUserIn',
newUserName: username,
})
}
Where do you think it should go? What are our options?
Exercise 31
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
Add code to ensure that our array contains only unique items 🐛
Change our data structure to forbid duplicate values 🐛👌
const [tags, setTags] = React.useState(new Set(props.initialTags))
Exercise 32
PS: the MDN docs on Sets are very good 👌✅
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"
}
]
}
]
}
}
(port might vary - please check the output of `npm start`)
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)
})
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
What would that look like?