From c8fdb91df07305a22a20e00836def901b8bd9c92 Mon Sep 17 00:00:00 2001
From: Resi Respati <resir014@gmail.com>
Date: Tue, 15 Oct 2019 00:52:51 +0700
Subject: [PATCH] [with-typescript] Updated TypeScript example to use API
 routes

Next.js 9.0.0 has been out for a while, which supports API routes, but
the examples were never updated to make use of it. This PR adds a simple
example of an API route which also makes use of dynamic routing.

A simple `fetch()` wrapper is also added for example purposes, and the
pages structure have also been updated to dynamic routing.
---
 .../with-typescript/components/Layout.tsx     |  4 +-
 .../with-typescript/components/ListItem.tsx   |  2 +-
 examples/with-typescript/package.json         |  1 +
 .../with-typescript/pages/api/users/[id].ts   | 18 ++++++++
 .../with-typescript/pages/api/users/index.ts  | 14 ++++++
 .../pages/{detail.tsx => users/[id].tsx}      | 13 +++---
 .../{initial-props.tsx => users/index.tsx}    | 22 ++++++----
 examples/with-typescript/utils/sample-api.ts  | 43 +++++--------------
 examples/with-typescript/utils/sample-data.ts |  9 ++++
 9 files changed, 77 insertions(+), 49 deletions(-)
 create mode 100644 examples/with-typescript/pages/api/users/[id].ts
 create mode 100644 examples/with-typescript/pages/api/users/index.ts
 rename examples/with-typescript/pages/{detail.tsx => users/[id].tsx} (66%)
 rename examples/with-typescript/pages/{initial-props.tsx => users/index.tsx} (57%)
 create mode 100644 examples/with-typescript/utils/sample-data.ts

diff --git a/examples/with-typescript/components/Layout.tsx b/examples/with-typescript/components/Layout.tsx
index b55cbb6afce05..12c97489b86ca 100644
--- a/examples/with-typescript/components/Layout.tsx
+++ b/examples/with-typescript/components/Layout.tsx
@@ -26,8 +26,8 @@ const Layout: React.FunctionComponent<Props> = ({
           <a>About</a>
         </Link>{' '}
         |{' '}
-        <Link href="/initial-props">
-          <a>With Initial Props</a>
+        <Link href="/users">
+          <a>Users List</a>
         </Link>
       </nav>
     </header>
diff --git a/examples/with-typescript/components/ListItem.tsx b/examples/with-typescript/components/ListItem.tsx
index c38b3f3181e50..a11a90ab93b91 100644
--- a/examples/with-typescript/components/ListItem.tsx
+++ b/examples/with-typescript/components/ListItem.tsx
@@ -8,7 +8,7 @@ type Props = {
 }
 
 const ListItem: React.FunctionComponent<Props> = ({ data }) => (
-  <Link href={`/detail?id=${data.id}`}>
+  <Link href={`/users/${data.id}`}>
     <a>
       {data.id}: {data.name}
     </a>
diff --git a/examples/with-typescript/package.json b/examples/with-typescript/package.json
index 97d051b76a57d..4c88befeee37f 100644
--- a/examples/with-typescript/package.json
+++ b/examples/with-typescript/package.json
@@ -8,6 +8,7 @@
     "type-check": "tsc"
   },
   "dependencies": {
+    "isomorphic-unfetch": "3.0.0",
     "next": "latest",
     "react": "^16.10.1",
     "react-dom": "^16.10.1"
diff --git a/examples/with-typescript/pages/api/users/[id].ts b/examples/with-typescript/pages/api/users/[id].ts
new file mode 100644
index 0000000000000..80961bbbd4c41
--- /dev/null
+++ b/examples/with-typescript/pages/api/users/[id].ts
@@ -0,0 +1,18 @@
+import { NextApiRequest, NextApiResponse } from 'next'
+import { sampleUserData } from '../../../utils/sample-data'
+
+export default (req: NextApiRequest, res: NextApiResponse) => {
+  try {
+    const { id } = req.query;
+    const selected = sampleUserData.find(data => data.id === Number(id))
+
+    if (!selected) {
+      throw new Error('Cannot find user')
+    }
+
+    res.status(200).json(selected)
+  } catch (err) {
+    res.status(404).json({ statusCode: 404, message: err.message })
+  }
+}
+
diff --git a/examples/with-typescript/pages/api/users/index.ts b/examples/with-typescript/pages/api/users/index.ts
new file mode 100644
index 0000000000000..28c815c21d23a
--- /dev/null
+++ b/examples/with-typescript/pages/api/users/index.ts
@@ -0,0 +1,14 @@
+import { NextApiRequest, NextApiResponse } from 'next'
+import { sampleUserData } from '../../../utils/sample-data'
+
+export default (_: NextApiRequest, res: NextApiResponse) => {
+  try {
+    if (!Array.isArray(sampleUserData)) {
+      throw new Error('Cannot find user data')
+    }
+
+    res.status(200).json(sampleUserData)
+  } catch (err) {
+    res.status(500).json({ statusCode: 500, message: err.message })
+  }
+}
diff --git a/examples/with-typescript/pages/detail.tsx b/examples/with-typescript/pages/users/[id].tsx
similarity index 66%
rename from examples/with-typescript/pages/detail.tsx
rename to examples/with-typescript/pages/users/[id].tsx
index e78cd3bf5d397..06b64e6b91f8b 100644
--- a/examples/with-typescript/pages/detail.tsx
+++ b/examples/with-typescript/pages/users/[id].tsx
@@ -1,9 +1,10 @@
 import * as React from 'react'
 import { NextPageContext } from 'next'
-import Layout from '../components/Layout'
-import { User } from '../interfaces'
-import { findData } from '../utils/sample-api'
-import ListDetail from '../components/ListDetail'
+
+import { User } from '../../interfaces'
+import Layout from '../../components/Layout'
+import ListDetail from '../../components/ListDetail'
+import { sampleFetchWrapper } from '../../utils/sample-api'
 
 type Props = {
   item?: User
@@ -14,7 +15,7 @@ class InitialPropsDetail extends React.Component<Props> {
   static getInitialProps = async ({ query }: NextPageContext) => {
     try {
       const { id } = query
-      const item = await findData(Array.isArray(id) ? id[0] : id)
+      const item = await sampleFetchWrapper(`http://localhost:3000/api/users/${Array.isArray(id) ? id[0] : id}`)
       return { item }
     } catch (err) {
       return { errors: err.message }
@@ -36,7 +37,7 @@ class InitialPropsDetail extends React.Component<Props> {
 
     return (
       <Layout
-        title={`${item ? item.name : 'Detail'} | Next.js + TypeScript Example`}
+        title={`${item ? item.name : 'User Detail'} | Next.js + TypeScript Example`}
       >
         {item && <ListDetail item={item} />}
       </Layout>
diff --git a/examples/with-typescript/pages/initial-props.tsx b/examples/with-typescript/pages/users/index.tsx
similarity index 57%
rename from examples/with-typescript/pages/initial-props.tsx
rename to examples/with-typescript/pages/users/index.tsx
index 4283a86bf5666..67638d0507988 100644
--- a/examples/with-typescript/pages/initial-props.tsx
+++ b/examples/with-typescript/pages/users/index.tsx
@@ -1,9 +1,10 @@
 import { NextPage } from 'next'
 import Link from 'next/link'
-import Layout from '../components/Layout'
-import List from '../components/List'
-import { User } from '../interfaces'
-import { findAll } from '../utils/sample-api'
+
+import Layout from '../../components/Layout'
+import List from '../../components/List'
+import { User } from '../../interfaces'
+import { sampleFetchWrapper } from '../../utils/sample-api'
 
 type Props = {
   items: User[]
@@ -11,8 +12,11 @@ type Props = {
 }
 
 const WithInitialProps: NextPage<Props> = ({ items, pathname }) => (
-  <Layout title="List Example (as Functional Component) | Next.js + TypeScript Example">
-    <h1>List Example (as Function Component)</h1>
+  <Layout title="Users List | Next.js + TypeScript Example">
+    <h1>Users List</h1>
+    <p>
+      Example fetching data from inside <code>getInitialProps()</code>.
+    </p>
     <p>You are currently on: {pathname}</p>
     <List items={items} />
     <p>
@@ -24,10 +28,12 @@ const WithInitialProps: NextPage<Props> = ({ items, pathname }) => (
 )
 
 WithInitialProps.getInitialProps = async ({ pathname }) => {
-  // Example for including initial props in a Next.js function compnent page.
+  // Example for including initial props in a Next.js function component page.
   // Don't forget to include the respective types for any props passed into
   // the component.
-  const items: User[] = await findAll()
+  const items: User[] = await sampleFetchWrapper(
+    'http://localhost:3000/api/users'
+  )
 
   return { items, pathname }
 }
diff --git a/examples/with-typescript/utils/sample-api.ts b/examples/with-typescript/utils/sample-api.ts
index a326dfa976fa2..3f193c2046687 100644
--- a/examples/with-typescript/utils/sample-api.ts
+++ b/examples/with-typescript/utils/sample-api.ts
@@ -1,34 +1,13 @@
-import { User } from '../interfaces'
-
-/** Dummy user data. */
-export const dataArray: User[] = [
-  { id: 101, name: 'Alice' },
-  { id: 102, name: 'Bob' },
-  { id: 103, name: 'Caroline' },
-  { id: 104, name: 'Dave' },
-]
-
-/**
- * Calls a mock API which finds a user by ID from the list above.
- *
- * Throws an error if not found.
- */
-export async function findData(id: number | string) {
-  const selected = dataArray.find(data => data.id === Number(id))
-
-  if (!selected) {
-    throw new Error('Cannot find user')
-  }
-
-  return selected
-}
-
-/** Calls a mock API which returns the above array to simulate "get all". */
-export async function findAll() {
-  // Throw an error, just for example.
-  if (!Array.isArray(dataArray)) {
-    throw new Error('Cannot find users')
+import fetch from 'isomorphic-unfetch'
+
+export async function sampleFetchWrapper(
+  input: RequestInfo,
+  init?: RequestInit
+) {
+  try {
+    const data = await fetch(input, init).then(res => res.json())
+    return data
+  } catch (err) {
+    throw new Error(err.message)
   }
-
-  return dataArray
 }
diff --git a/examples/with-typescript/utils/sample-data.ts b/examples/with-typescript/utils/sample-data.ts
new file mode 100644
index 0000000000000..1dd38ecc424dd
--- /dev/null
+++ b/examples/with-typescript/utils/sample-data.ts
@@ -0,0 +1,9 @@
+import { User } from '../interfaces'
+
+/** Dummy user data. */
+export const sampleUserData: User[] = [
+  { id: 101, name: 'Alice' },
+  { id: 102, name: 'Bob' },
+  { id: 103, name: 'Caroline' },
+  { id: 104, name: 'Dave' },
+]