Skip to content

Commit

Permalink
Add react-query and use it to make data requests
Browse files Browse the repository at this point in the history
  • Loading branch information
hswolff committed Jul 31, 2020
1 parent 41186b1 commit 58c542b
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 41 deletions.
56 changes: 56 additions & 0 deletions components/AddItemForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useState } from 'react';
import { useSession } from 'next-auth/client';
import { useAddItem, useClearItems } from 'hooks/api-hooks';

export default function AddItemForm() {
const [session] = useSession();

const [title, setTitle] = useState('');
const [mutate] = useAddItem();
const [clearItems] = useClearItems();

const submitForm = (e: React.SyntheticEvent) => {
e.preventDefault();
mutate({
title,
description: `Thank you ${session.user.name} for the idea!`,
});
setTitle('');
};

if (!session) {
return null;
}

return (
<form
className="flex flex-col mx-auto max-w-sm border border-gray-400 rounded p-2"
onSubmit={submitForm}
>
<input
type="text"
className="border"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<div className="flex flex-row mt-2">
<button
className={`${buttonStyles} flex-grow`}
type="submit"
onClick={submitForm}
>
Add
</button>
<button
type="button"
className={`${buttonStyles} ml-2`}
onClick={clearItems}
>
X
</button>
</div>
</form>
);
}

const buttonStyles = 'p-1 border bg-gray-400 shadow-sm';
32 changes: 1 addition & 31 deletions components/content.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,3 @@
import faker from 'faker';

faker.seed(1);

function generateItem() {
return {
_id: faker.random.uuid(),
title: faker.lorem.words(),
description: faker.lorem.paragraph(),
created: faker.date.past().toUTCString(),
updated: faker.date.past().toUTCString(),
category: faker.random.arrayElement(['Tutorial', 'Opinion', 'Vlog']),
createdBy: faker.random.uuid(),
status: faker.random.arrayElement([
'open',
'accepted',
'declined',
'completed',
]),
votes: faker.random.number(100),
// votes: {
// up: [],
// down: [],
// },
};
}

// @ts-expect-error
const items = [...Array(10).keys()].map((i) => generateItem());

function Item({ item }) {
return (
<div className="border border-gray-400 rounded-md shadow my-8 p-4 flex flex-col sm:flex-row hover:border-gray-500 ease-linear transition duration-150">
Expand All @@ -49,7 +19,7 @@ function Item({ item }) {
);
}

export default function Content() {
export default function Content({ items }) {
return (
<ul className="container mx-auto my-2 max-w-6xl">
{items.map((item) => (
Expand Down
19 changes: 10 additions & 9 deletions components/sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,19 @@ function ExampleSession({ session }) {
}

export default function SignIn() {
const [session, loading] = useSession();
const [session] = useSession();

return (
<div>
<div className="max-w-xs mx-auto m-6 pb-2 border-b flex justify-between">
{/* <ExampleSession session={session} /> */}
{session ? `Welcome ${session.user.name}` : null}
<br />
{session ? (
<button onClick={signout}>Sign out</button>
) : (
<button onClick={() => signin('github')}>Sign in</button>
)}
<div>{session ? `Welcome ${session.user.name}` : null}</div>
<div>
{session ? (
<button onClick={signout}>Sign out</button>
) : (
<button onClick={() => signin('github')}>Sign in</button>
)}
</div>
</div>
);
}
28 changes: 28 additions & 0 deletions hooks/api-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useQuery, useMutation, queryCache } from 'react-query';

export function useItems() {
return useQuery('items', () => fetch('/api/items').then((res) => res.json()));
}

const addItem = (body) => {
return fetch('/api/items', {
method: 'POST',
body: JSON.stringify(body),
});
};

export function useAddItem() {
return useMutation(addItem, {
onSuccess() {
queryCache.invalidateQueries('items');
},
});
}

export function useClearItems() {
return useMutation(() => fetch('/api/items', { method: 'DELETE' }), {
onSuccess() {
queryCache.invalidateQueries('items');
},
});
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"postcss-preset-env": "^6.7.0",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-query": "^2.5.7-tsnext.1",
"tailwindcss": "^1.6.0"
},
"devDependencies": {
Expand Down
57 changes: 57 additions & 0 deletions pages/api/items.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { NextApiRequest, NextApiResponse } from 'next';

import faker from 'faker';

faker.seed(1);

function generateItem({ title = null, description = null } = {}) {
return {
_id: faker.random.uuid(),
title: title ?? faker.lorem.words(),
description: description ?? faker.lorem.paragraph(),
created: faker.date.past().toUTCString(),
updated: faker.date.past().toUTCString(),
category: faker.random.arrayElement(['Tutorial', 'Opinion', 'Vlog']),
createdBy: faker.random.uuid(),
status: faker.random.arrayElement([
'open',
'accepted',
'declined',
'completed',
]),
votes: faker.random.number(100),
// votes: {
// up: [],
// down: [],
// },
};
}

let postRequests = [];

export default (req: NextApiRequest, res: NextApiResponse) => {
const items = postRequests
// @ts-expect-error
.concat([...Array(10).keys()])
.map((item) => generateItem(typeof item === 'number' ? undefined : item));

if (req.method === 'GET') {
return res.status(200).json(items);
}

if (req.method === 'DELETE') {
postRequests = [];
return res.status(200).send('deleted');
}

const parsedBody = JSON.parse(req.body);

postRequests.unshift(parsedBody);

// don't memory leak
if (postRequests.length > 9) {
postRequests = [];
}

res.status(200).send('added');
};
7 changes: 6 additions & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import Head from 'next/head';
import SignIn from 'components/sign-in';
import Content from 'components/content';
import AddItemForm from 'components/AddItemForm';
import { useItems } from 'hooks/api-hooks';

export default function Home() {
const { data, isSuccess } = useItems();

return (
<div>
<Head>
Expand All @@ -15,7 +19,8 @@ export default function Home() {
</main>

<SignIn />
<Content />
<AddItemForm />
{isSuccess && <Content items={data} />}
</div>
);
}
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5757,6 +5757,13 @@ react-is@16.13.1, react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==

react-query@^2.5.7-tsnext.1:
version "2.5.7-tsnext.1"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-2.5.7-tsnext.1.tgz#03d3d4092302030f1c96702e13f02b786a0172a0"
integrity sha512-FdU3vRMM5Lm2cIhFxlfZlHY0glwKtyKLV85njdYauM5otAChfQaPwdR06VY47+jG/IAPowI8Wmo6JEgFpeWQ/g==
dependencies:
ts-toolbelt "^6.9.4"

react-refresh@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
Expand Down Expand Up @@ -6729,6 +6736,11 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==

ts-toolbelt@^6.9.4:
version "6.13.36"
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.13.36.tgz#083e0ccaf610edf743cf6cb18714def45eb27d6d"
integrity sha512-TxtCLq+Hze2myCzUa31hu8cMFxYe3JpIZnCqaKvkHELEpAm81PNlaxC1UmM9cD0BqQ7XxYPIvQYBmvdnAfYzKA==

tslib@^1.8.1, tslib@^1.9.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
Expand Down

1 comment on commit 58c542b

@vercel
Copy link

@vercel vercel bot commented on 58c542b Jul 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.