今まで毛嫌いしてきたGrapQLに対してちょっと向き合うようにしてみました。単純にGraphQlについて何もしらなかったのと、AWS Amplifyを触ってて料金が爆上がりしたのからちょっと避けていました。
ただ最近GraphQLの記事を見たりして少しだけ触ってみようと思い、早速壁にぶち当たったので備忘録的に記事を書きました。
環境
フロント
- React 18.2.0
- Next 12.3.1
- @apollo/client 3.7.0
- graphql 16.6.0
バックエンド
- Python 3.10
- Django 4.1.2
- graphene-django 3.0.0
GraphQLサーバー
Pythonのgraphene-djangoを使って実装しました。REST APIだけじゃなくてGraphQLサーバーも立てられるとかDjango様様です。細かい処理は端折りますが、ログインの部分は以下のようなMutationを実装しました。
class LoginUserMutation(graphene.Mutation):
id = graphene.String()
username = graphene.String()
email = graphene.String()
user = graphene.Field(UserType)
class Arguments:
password = graphene.String(required=True)
email = graphene.String(required=True)
def mutate(self, _, password, email):
user = User.objects.filter(email=email).first()
if not (user and user.check_password(password)):
return GraphQLError("ユーザーが存在しません")
user.last_login = datetime.now()
user.save()
return LoginUserMutation(user=user)
画面から入力されたメールアドレスとパスワードを受け取って、一致するユーザーがなかったらエラーを、ユーザーが存在したら、最終ログイン時間を更新してユーザー情報を画面に返します。そもそもこういう実装の方向性であっているんですかね?
フロント
function Login() {
const [login] = useMutation(
LOGIN_USER,
{
onCompleted(_) {
router.push("/top")
},
onError(_) {
setErrorMessage("メールアドレス、またはパスワードが間違っています")
}
}
);
const { register, handleSubmit, formState: { errors } } = useForm<LoginInput>({ mode: "onSubmit", resolver: yupResolver(validateSchema) })
const router = useRouter()
const [errorMessage, setErrorMessage] = React.useState("")
const onSubmit: SubmitHandler<LoginInput> = async (loginInput) => {
await login({ variables: { email: loginInput.email, password: loginInput.password } })
}
return (
<div>
<Header />
<div className="h-screen w-screen flex flex-col items-center">
{errorMessage && <ErrorCard errorMessage={errorMessage} />}
<div className="bg-gray-100 border-2 border-gray-900 rounded-lg w-5/12 mt-10 mb-auto py-12">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mx-auto w-7/12">
<EmailForm {...register("email", { required: true })} error={"email" in errors} errorMessage={errors.email?.message} />
<PasswordForm {...register("password", { required: true })} error={"password" in errors} errorMessage={errors.password?.message} />
<CheckBoxForm id="checkbox" label="ログイン状態を保持する" />
<PrimaryButton type="submit" label="ログインする" onClick={() => { console.log(errors) }} />
</div>
<Divider />
<div className="flex justify-evenly">
<HyperLink href="/signon">新規登録の方はこちら</HyperLink>
<HyperLink href="/forgot">パスワードを忘れた</HyperLink>
</div>
</form>
</div>
</div>
</div>)
}
正直このやり方でいいのか今でのわかんないです。当初は
const onSubmit: SubmitHandler<LoginInput> = async (loginInput) => {
await login({ variables: { email: loginInput.email, password: loginInput.password } })
if(error){
setErrorMessage("メールアドレス、またはパスワードが間違っています")
}else{
router.push("/top")
}
}
というような処理を試みたんですが、これだとloginの処理がerrorやdataに格納される前に進んでしまうため、必ずrouter.push("/top")が動いてしまうという現象が起きました。色々漁った結果。apolloのドキュメントを参考に、今のようなソースになりました。
正直書き方としてはすごい違和感があります。ただロジックをしっかりと分割できているようにも見え、useLoginのようなカスタムフックにしてしまってもいいのかもしれません。とりあえず特段困らない限りこのままやっていこうと思います。もしやり方が間違っている、もっといい方法があればぜひコメント等で教えていただけたら幸いです。