-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feature/add-updated-at
- Loading branch information
Showing
15 changed files
with
573 additions
and
161 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export default async function fetcher(...args) { | ||
const res = await fetch(...args) | ||
if (!res.ok) throw new Error('Failed to fetch') | ||
return res.json() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import '../styles.css' | ||
|
||
export default function App({ Component, pageProps }) { | ||
return <Component {...pageProps} /> | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
let todos = [] | ||
const delay = () => new Promise(res => setTimeout(() => res(), 1000)) | ||
|
||
async function getTodos() { | ||
await delay() | ||
return todos.sort((a, b) => (a.text < b.text ? -1 : 1)) | ||
} | ||
|
||
async function addTodo(todo) { | ||
await delay() | ||
// Sometimes it will fail, this will cause a regression on the UI | ||
if (Math.random() < 0.2 || !todo.text) | ||
throw new Error('Failed to add new item!') | ||
todo.text = todo.text.charAt(0).toUpperCase() + todo.text.slice(1) | ||
todos = [...todos, todo] | ||
return todo | ||
} | ||
|
||
export default async function api(req, res) { | ||
try { | ||
if (req.method === 'POST') { | ||
const body = JSON.parse(req.body) | ||
return res.json(await addTodo(body)) | ||
} | ||
|
||
return res.json(await getTodos()) | ||
} catch (err) { | ||
return res.status(500).json({ error: err.message }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,110 @@ | ||
import React from 'react' | ||
import useSWR from 'swr' | ||
import React, { useState } from 'react' | ||
|
||
import fetch from '../libs/fetch' | ||
|
||
import useSWR, { mutate } from 'swr' | ||
|
||
export default function Index() { | ||
const [text, setText] = React.useState(''); | ||
const { data } = useSWR('/api/data', fetch) | ||
|
||
async function handleSubmit(event) { | ||
event.preventDefault() | ||
// Call mutate to optimistically update the UI. | ||
mutate('/api/data', [...data, text], false) | ||
// Then we send the request to the API and let mutate | ||
// update the data with the API response. | ||
// Our action may fail in the API function, and the response differ | ||
// from what was optimistically updated, in that case the UI will be | ||
// changed to match the API response. | ||
// The fetch could also fail, in that case the UI will | ||
// be in an incorrect state until the next successful fetch. | ||
mutate('/api/data', await fetch('/api/data', { | ||
method: 'POST', | ||
body: JSON.stringify({ text }) | ||
})) | ||
setText('') | ||
} | ||
|
||
return <div> | ||
<form onSubmit={handleSubmit}> | ||
<input | ||
type="text" | ||
onChange={event => setText(event.target.value)} | ||
value={text} | ||
/> | ||
<button>Create</button> | ||
</form> | ||
<ul> | ||
{data ? data.map(datum => <li key={datum}>{datum}</li>) : 'loading...'} | ||
</ul> | ||
</div> | ||
export default function App() { | ||
const [text, setText] = useState('') | ||
const { data, mutate } = useSWR('/api/todos', fetch) | ||
|
||
const [state, setState] = useState(<span className="info"> </span>) | ||
|
||
return ( | ||
<div> | ||
{/* <Toaster toastOptions={{ position: 'bottom-center' }} /> */} | ||
<h1>Optimistic UI with SWR</h1> | ||
|
||
<p className="note"> | ||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> | ||
<path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm1 18h-2v-8h2v8zm-1-12.25c.69 0 1.25.56 1.25 1.25s-.56 1.25-1.25 1.25-1.25-.56-1.25-1.25.56-1.25 1.25-1.25z" /> | ||
</svg> | ||
This application optimistically updates the data, while revalidating in | ||
the background. The <code>POST</code> API auto capitializes the data, | ||
and only returns the new added one instead of the full list. And the{' '} | ||
<code>GET</code> API returns the full list in order. | ||
</p> | ||
|
||
<form onSubmit={ev => ev.preventDefault()}> | ||
<input | ||
value={text} | ||
onChange={e => setText(e.target.value)} | ||
placeholder="Add your to-do here..." | ||
autoFocus | ||
/> | ||
<button | ||
type="submit" | ||
onClick={async () => { | ||
setText('') | ||
setState( | ||
<span className="info">Showing optimistic data, mutating...</span> | ||
) | ||
|
||
const newTodo = { | ||
id: Date.now(), | ||
text | ||
} | ||
|
||
try { | ||
// Update the local state immediately and fire the | ||
// request. Since the API will return the updated | ||
// data, there is no need to start a new revalidation | ||
// and we can directly populate the cache. | ||
await mutate( | ||
fetch('/api/todos', { | ||
method: 'POST', | ||
body: JSON.stringify(newTodo) | ||
}), | ||
{ | ||
optimisticData: [...data, newTodo], | ||
rollbackOnError: true, | ||
populateCache: newItem => { | ||
setState( | ||
<span className="success"> | ||
Succesfully mutated the resource and populated cache. | ||
Revalidating... | ||
</span> | ||
) | ||
|
||
return [...data, newItem] | ||
}, | ||
revalidate: true | ||
} | ||
) | ||
setState(<span className="info">Revalidated the resource.</span>) | ||
} catch (e) { | ||
// If the API errors, the original data will be | ||
// rolled back by SWR automatically. | ||
setState( | ||
<span className="error"> | ||
Failed to mutate. Rolled back to previous state and | ||
revalidated the resource. | ||
</span> | ||
) | ||
} | ||
}} | ||
> | ||
Add | ||
</button> | ||
</form> | ||
|
||
{state} | ||
|
||
<ul> | ||
{data ? ( | ||
data.length ? ( | ||
data.map(todo => { | ||
return <li key={todo.id}>{todo.text}</li> | ||
}) | ||
) : ( | ||
<i> | ||
No todos yet. Try adding lowercased "banana" and "apple" to the | ||
list. | ||
</i> | ||
) | ||
) : ( | ||
<i>Loading...</i> | ||
)} | ||
</ul> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
html { | ||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, | ||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | ||
text-align: center; | ||
} | ||
|
||
body { | ||
max-width: 600px; | ||
margin: auto; | ||
} | ||
|
||
h1 { | ||
margin-top: 1em; | ||
} | ||
|
||
.note { | ||
text-align: left; | ||
font-size: 0.9em; | ||
line-height: 1.5; | ||
color: #666; | ||
} | ||
|
||
.note svg { | ||
margin-right: 0.5em; | ||
vertical-align: -2px; | ||
width: 14px; | ||
height: 14px; | ||
margin-right: 5px; | ||
} | ||
|
||
form { | ||
display: flex; | ||
margin: 8px 0; | ||
gap: 8px; | ||
} | ||
|
||
input { | ||
flex: 1; | ||
} | ||
|
||
input, | ||
button { | ||
font-size: 16px; | ||
padding: 6px 5px; | ||
} | ||
|
||
code { | ||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, | ||
Liberation Mono, Courier New, monospace; | ||
font-feature-settings: 'rlig' 1, 'calt' 1, 'ss01' 1; | ||
background-color: #eee; | ||
padding: 1px 3px; | ||
border-radius: 2px; | ||
} | ||
|
||
ul { | ||
text-align: left; | ||
list-style: none; | ||
padding: 0; | ||
} | ||
|
||
li { | ||
margin: 8px 0; | ||
padding: 10px; | ||
border-radius: 4px; | ||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12), 0 0 0 1px #ededed; | ||
} | ||
|
||
i { | ||
color: #999; | ||
} | ||
|
||
.info, | ||
.success, | ||
.error { | ||
display: block; | ||
text-align: left; | ||
padding: 6px 0; | ||
font-size: 0.9em; | ||
opacity: 0.9; | ||
} | ||
|
||
.info { | ||
color: #666; | ||
} | ||
.success { | ||
color: #4caf50; | ||
} | ||
.error { | ||
color: #f44336; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.