菊池律です。

三日坊主にならないように、三日毎に書きます。

AWSのAmplifyを使ったチュートリアルがうまくいかないので対処

菊池率です。最近Webアプリの開発をしたいと思ってとりあえずAWSを色々触れているのですが、チュートリアルを読んでいるとうまくいかない箇所があったので対処法を記しておきます。
aws.amazon.com
問題のチュートリアルは↑なのですが、具体的にはモジュール4のフロントエンドコードの記述の箇所です。
チュートリアル通りに進めていくと、
export 'API' (imported as 'API') was not found in 'aws-amplify' (possible exports: Amplify)
というエラーが表示されると思います。
↓のissueやドキュメントを読んでみると問題の原因や解決法があるそうなのですが、私の環境ではそれでもうまくいかなかったので、自分なりにどう解決したかをまとめます。
github.com
どうやら、2週間くらい前に最新バージョン6.x.xに更新されたそうなのですが、API をエクスポートしなくなり、代わりにgenerateClient()がgraphqlの呼び出しを行うようになったそうです。

src/app.jsを以下のように書き換えます

import React, { useState, useEffect, FormEvent } from "react";
import {
  Button,
  Flex,
  Heading,
  Text,
  TextField,
  View,
  WithAuthenticatorProps,
  withAuthenticator,
} from "@aws-amplify/ui-react";
import "./App.css";
import "@aws-amplify/ui-react/styles.css";
import { listNotes } from "./graphql/queries";
import {
  createNote as createNoteMutation,
  deleteNote as deleteNoteMutation,
} from "./graphql/mutations";
import { CreateNoteInput, Note } from "@/src/API";

import { Amplify } from "aws-amplify";
import { generateClient } from "aws-amplify/api";
import awsconfig from "../src/amplifyconfiguration.json";

Amplify.configure(awsconfig);

const client = generateClient();

const App = ({ signOut }: WithAuthenticatorProps) => {
  const [notes, setNotes] = useState<Note[]>([])

  useEffect(() => {
    fetchNotes();
  }, []);

  async function fetchNotes() {
    const apiData = await client.graphql({ query: listNotes });
    const notesFromAPI = apiData.data.listNotes.items;
    setNotes(notesFromAPI);
  }

  async function createNote(event: FormEvent<HTMLFormElement>) {
    const form = event.target as HTMLFormElement;

    event.preventDefault();

    const formData = new FormData(form);

    const data: CreateNoteInput = {
      name: formData.get("name") as string,
      description: formData.get("description") as string,
    };

    await client.graphql({
      query: createNoteMutation,
      variables: { input: data },
    });

    await fetchNotes();

    form.reset();
  }

  async function deleteNote({ id }: Note) {
    const newNotes = notes.filter((note) => note.id !== id);

    setNotes(newNotes);

    await client.graphql({
      query: deleteNoteMutation,
      variables: { input: { id } },
    });
  }

  return (
    <View className="App">
      <Heading level={1}>My Notes App</Heading>
      <View as="form" margin="3rem 0" onSubmit={createNote}>
        <Flex direction="row" justifyContent="center">
          <TextField
            name="name"
            placeholder="Note Name"
            label="Note Name"
            labelHidden
            variation="quiet"
            required
          />
          <TextField
            name="description"
            placeholder="Note Description"
            label="Note Description"
            labelHidden
            variation="quiet"
            required
          />
          <Button type="submit" variation="primary">
            Create Note
          </Button>
        </Flex>
      </View>
      <Heading level={2}>Current Notes</Heading>
      <View margin="3rem 0">
        {notes.map((note) => (
          <Flex
            key={note.id || note.name}
            direction="row"
            justifyContent="center"
            alignItems="center"
          >
            <Text as="strong" fontWeight={700}>
              {note.name}
            </Text>
            <Text as="span">{note.description}</Text>
            <Button variation="link" onClick={() => deleteNote(note)}>
              Delete note
            </Button>
          </Flex>
        ))}
      </View>
      <Button onClick={signOut}>Sign Out</Button>
    </View>
  );
};

export default withAuthenticator(App);

issueの解決策通りに書き換えると、useState<Note[]>([])
エラー:Parsing error: Unexpected token (30:42)が起きます。
これはTypeScriptの型注釈をJavaScriptファイルで使用しているために発生します。
ファイル形式を.js→.tsxに変更する もしくは

const [notes, setNotes] = useState([]); // 型注釈を削除

でいけるはずです。
他にもissueに書かれたコードはTypeScriptで書かれていそうなので、私はファイルの形式を変更する方を選びました。
src/index.tsxも以下のように書き換えます

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App.tsx';
import reportWebVitals from './reportWebVitals';
import { Amplify } from 'aws-amplify';
import config from './aws-exports';
Amplify.configure(config);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

実行してみます。

ちゃんとできました

以上です。