はじめに
TypeScriptととReduxを用いて、amplify GraphQLのTodoアプリチュートリアルを試してます。
mutationのdelete機能を使って、作ったtodoを削除しようとしたのですがタイトルの通り、
message: "Variable 'input' has coerced Null value for NonNull type 'ID!'
というエラーがコンソールに現れました。
該当コード
App.tsxは全体像、todoSlice.tsは一部だけ抜粋しておきますが、完成形はこのようなロジックになっております。
import DeleteForeverIcon from "@material-ui/icons/DeleteForever";
import Signin from "./Signin";
import scss from "./App.module.scss";
import {
addTodo,
AddTodosType,
deleteTodo,
fetchTodos,
IdType,
selectTodos,
} from "./features/todo/todoSlice";
import { useForm } from "react-hook-form";
import { useEffect } from "react";
import { useAppDispatch, useAppSelector } from "./app/hooks";
import { withAuthenticator } from "@aws-amplify/ui-react";
const App = () => {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm();
const dispatch = useAppDispatch();
const todos = useAppSelector(selectTodos);
useEffect(() => {
const getTodos = async () => {
await dispatch(fetchTodos());
};
getTodos();
}, []);
const handleAddTodo = async (data: AddTodosType) => {
await dispatch(addTodo(data));
await dispatch(fetchTodos());
reset();
};
const handleDelete = async (id: IdType) => {
await dispatch(deleteTodo(id));
await dispatch(fetchTodos());
console.log(id);
};
return (
<div className={scss.root}>
<div className={scss.header}>
<Signin />
</div>
<div className={scss.main_container}>
<h2>Amplify Todos</h2>
<form onSubmit={handleSubmit(handleAddTodo)}>
<input
className={scss.input}
placeholder="Name"
{...register("name", { required: true })}
/>
<input
className={scss.input}
placeholder="Description"
{...register("description", { required: true })}
/>
<button className={scss.button} type="submit">
Create Todo
</button>
</form>
<div className={scss.todo_wrapper}>
{todos.map((todo: any, index: any) => (
<div key={todo.id ? todo.id : index} className={scss.todo}>
<div className={scss.left_wrapper}>
<p className={scss.todoName}>{todo.name}</p>
<p className={scss.todoDescription}>{todo.description}</p>
</div>
<DeleteForeverIcon onClick={() => handleDelete(todo.id)} />
</div>
))}
</div>
</div>
<div className={scss.footer}>footer</div>
</div>
);
};
export default withAuthenticator(App);
export const deleteTodo = createAsyncThunk(
"todo/deleteTodo",
async (id: IdType) => {
console.log(id);
const todoDetails = {
id: id,
};
try {
await API.graphql({
query: deleteTodoGraph,
variables: { input: todoDetails },
});
} catch (err) {
console.log(err);
}
}
);
結論からいうと、
async (id: IdType) => { //ここがおかしかった
console.log(id);
const todoDetails = {
id: id,
};
↑のコードをasync({id}: IdType) => {
のように{}
を付けてしまっていて、それによって、id
の値がundifined
になってしまっていました。
console.logで確認する
出会ったことのないエラーに遭遇すると、とりあえずエラー文で検索するのですが、そこでいい感じの記事に出会えないと、軽く詰みます。
そういうときに教わった、エラー解決のための手順なのですが、
- エラー文の理解
- エラーを解決するための仮説立て
- 仮説に基づいた調査
以上3つを意識すると、漠然と答えを探し求めるより早く答えにたどり着けるそうです。
実際今回も、まず下記エラー文を日本語訳してみました。
message: "Variable 'input' has coerced Null value for NonNull type 'ID!'
変数「input」がNonNull型の「ID!」に対してNull値を強要しています。
その結果に対して、**「input: に入れている値(型?)がnullになっているのかな?」という仮設を立て、「じゃあ、その値がnullになっていることを確かめるためには、どこかでconsole.log()などでデータがきちんと入っていることを確認すればいいのかな」**という結論のもと調査に入りました。
検証
export const deleteTodo = createAsyncThunk(
"todo/deleteTodo",
async (id: IdType) => { // ここ
console.log(id);
const todoDetails = {
id: id,
};
try {
await API.graphql({
query: deleteTodoGraph,
variables: { input: todoDetails }, //ここ
});
} catch (err) {
console.log(err);
}
}
);
id
の値はApp.tsxから渡ってきています。まずこの下でconsole.log()
をすると、結果はundefined
だったので、次にApp.tsxのid
を渡す箇所でconsole.log()をすると、データは入ってました。
じゃあtodoSlice.tsxの方だな、とよくコードを見るとasync({id}: IdType)
のように{}
が付いていて、それを消したら無事データが渡されました、という流れです。
エラーときちんと向き合う
実装を先に進めたいがために、エラー文をなんとなく読んで、検索して出てきた答えと当てはまればオッケー、というような感じで進んできましたが、やっぱりきちんとエラーの意味を理解して「このエラーは何を言っているのか」、ということをしっかりと把握する必要性を感じました。
実際、エラーをきちんと理解できるようになれば、今後も自走でコードを書いていける状況になるわけなので、今のうちにエラーとしっかり向き合う癖を付けておこうと思います。