質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Next.js

Next.jsは、Reactを用いたサーバサイドレンダリングなどを行う軽量なフレームワークです。Zeit社が開発しており、nextコマンドでプロジェクトを作成することにより、開発環境整備が整った環境が即時に作成できます。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

5145閲覧

Next.jsにてuseSWRを使った値の取得が出来ない

fresh_fish

総合スコア20

Next.js

Next.jsは、Reactを用いたサーバサイドレンダリングなどを行う軽量なフレームワークです。Zeit社が開発しており、nextコマンドでプロジェクトを作成することにより、開発環境整備が整った環境が即時に作成できます。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2021/10/19 07:52

編集2021/10/19 09:56

前提・実現したいこと

フロントにNextjs,バックエンドにExpressという構成でwebアプリケーションを作成しています。
Nextjsのグローバルステート管理にはRecoilを使用しています。
jwtを使用したログイン判定をSWRを使って実装しようと思ったのですがどうにもうまくいきません
ここが原因ではないかと思いましたらコメントしていただけると嬉しいです。

発生している問題・エラーメッセージ

本来ならuserデータかisErrorデータのどちらかがすぐに返ってくるはずなのですが常にLoading状態になってしまいます。
コードはSWR公式ドキュメントから殆どコピペしてきたので原因箇所の見当がつきません

const { user, isLoading, isError } = useCheckUserRole("admin"); if (isError) return <div>failed to load</div> if (isLoading) return <div>loading...</div> if (user) return <div>get userdata</div>

イメージ説明

該当のソースコード

Next.js

useCheckUserRole

1import useSWR from 'swr' 2import useAxios from './useAxios' 3 4const useCheckUserRole = (role: string) => { 5 const axios = useAxios(); //カスタマイズした設定のaxiosインスタンスを取得 6 7 const fetcher = (url: string) => { 8 axios.get(url).then((res => { 9 console.log(res.data) //画像のブラウザコンソールに出力されているものです。ここの出力からリクエストは成功していることが確認できます 10 res.data 11 })) 12 } 13 14 const { data, error } = useSWR(`auth/currentuser/${role}`, fetcher, { revalidateIfStale: true }) //テスト用にキャッシュをオフにしています 15 return { 16 user: data, 17 isLoading: !error && !data, 18 isError: error 19 } 20} 21 22export default useCheckUserRole;

SignUpPage

1import Layout from '../../components/Layout'; 2import useAxios from '../../hooks/useAxios'; 3import useCheckUserRole from '../../hooks/useCheckUserRole' 4 5const SignUpPage = () => { 6 const axios = useAxios(); 7 const { user, isLoading, isError } = useCheckUserRole("admin"); 8 9 if (isError) return <div>failed to load</div> 10 if (isLoading) return <div>loading...</div> 11 if (user) return <div>get userdata</div> 12 return ( 13 <Layout title="タイトル"> 14 <h1>サインアップ</h1> 15 </Layout> 16 ) 17} 18 19export default SignUpPage

useAxios

1import { useRecoilState } from "recoil"; 2import { accessTokenState } from "../components/atoms"; 3import { useRouter } from 'next/router' 4import Cookies from 'js-cookie' 5import axios from 'axios' 6 7//ここはあまり関係ないと思いますが一応載せておきます 8 9 10const useAxios = () => { 11 const router = useRouter(); 12 const [accessToken, setAccessToken] = useRecoilState(accessTokenState); 13 const refreshToken = Cookies.get("refreshToken") 14 15//リクエスト時にaccessTokenをAuthorizationヘッダーにセット 16 const api = axios.create({ 17 baseURL: process.env.NEXT_PUBLIC_API_HOST, 18 timeout: 1000, 19 withCredentials: true 20 }); 21 api.interceptors.request.use( 22 (config: any) => { 23 config.headers.common['Authorization'] = 'Bearer ' + accessToken; 24 return config; 25 } 26 ); 27 28//レスポンス時にAccessTokenが期限切れになっていた場合自動でrefreshTokenを使用してAccessTokenを更新した後リクエストをリトライする 29 api.interceptors.response.use((response) => { 30 return response; 31 }, async (error) => { 32 if (error.config && error.response && error.response.data.message === "jwt expired") { 33 try { 34 const result: any = await axios.post(`${process.env.NEXT_PUBLIC_API_HOST}auth/refresh`, { refreshToken: refreshToken }) 35 setAccessToken(result.data.accessToken) 36 const config = error.config; 37 config.headers['Authorization'] = 'Bearer ' + result.data.accessToken; 38 return axios.request(error.config); 39 } catch (err) { 40 router.push("/auth/login") 41 } 42 } 43 return Promise.reject(error); 44 }) 45 return api; 46} 47 48export default useAxios;

Express

authRouter

1import express from 'express'; 2import { currentUser } from '../../controllers/authController' 3const router = express.Router(); 4 5router.get("/currentuser/:role", currentUser) 6 7export default router; 8

authController

1import express from 'express'; 2import jwt from 'jsonwebtoken'; 3import HttpException from '../exceptions/HttpException'; 4 5//トークンを検証してログインしているかどうか、またURLのクエリで受け取ったroleと一致しているか判定するAPIです。 6export const currentUser = (req: express.Request, res: express.Response, next: express.NextFunction) => { 7 const accessTokenSecret: jwt.Secret = process.env.ACCESS_TOKEN_SECRET ?? "defaultaccesssecret" 8 const authHeader = req.headers["authorization"]; 9 const accessToken = authHeader && authHeader.split(" ")[1]; 10 if (accessToken == null) return next(new HttpException(403, "AccessToken is null")) 11 jwt.verify(accessToken, accessTokenSecret, (err: any, user: any) => { 12 if (err) return next(err) 13 if (req.params.role !== user.role) return next(new HttpException(402, "Permission error")) 14 return res.json(user); 15 }); 16}

### コンソールの出力
ExpressAPIへのリクエスト自体は成功しており、200statusとjsonデータが返ってきています。
問題はReact側、useSWRの方にあると推測しております。

イメージ説明
イメージ説明

補足情報(FW/ツールのバージョンなど)

node --version
v16.9.1

npm list --depth 0
├── @material-ui/core@4.12.3
├── @material-ui/icons@4.11.2
├── @types/js-cookie@3.0.0
├── @types/node@12.20.33
├── @types/react-dom@17.0.9
├── @types/react@17.0.30
├── axios@0.23.0
├── js-cookie@3.0.1
├── next@11.1.2
├── node-fetch@3.0.0
├── react-dom@17.0.2
├── react-hook-form@7.17.4
├── react@17.0.2
├── recoil-persist@3.0.0
├── recoil@0.4.1
├── swr@1.0.1
└── typescript@4.0.8

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miyabi-sun

2021/10/19 08:59 編集

問題を切り分けていきましょう。 誰(どのファイル)がやらかしているのかを突き止めないと問題は解決しません。 まずは「裏でAjax通信本当にちゃんとやってるのか?」を確認してみてください。 デベロッパーツールのコンソールタブからネットワークタブに切り替えてリロードしましょう。 例えばChromeならXHRタブで更に絞り込みが可能で Ajax通信による裏でHTTPリクエスト飛ばして受け取った流れが閲覧できます。 もし正しくHTTP通信が行われているのであれば、 ステータスコードの200を返して黒字になっており、 中を開くと良い感じのJSON文字列を受け取った事を確認出来るはずです。 その場合Expressのバックエンド側の問題ではなく、Next.jsの使い方が悪いという問題になります。 このHTTP通信のステータスコードが200意外だったり、 結果を返してないとかであればExpress側が悪いんじゃないかという推測が出来ます。 Express側でconsole.log(xxx)をあちこちに仕込んで、 正常な流れで結果を作って返しているのかを確認してみてください。
fresh_fish

2021/10/19 10:02

修正依頼ありがとうございます。 リクエスト自体は成功しているのでNextjs側、というかSWRライブラリの使い方に問題があると思っております。 質問をするにあたって必要な情報が足りていませんでした。 以後気を付けます。
miyabi-sun

2021/10/19 10:09

早速の対応ありがとうございます。 これは私の考えですが「質問者さんに不備があるのは当然というか仕方ない」と考えています。 質問前から過不足ない全ての情報を出して 「はい、これで回答者さん答え出せるでしょ?」って言われても 「なんでそんなこと分かるの?過不足なく全て用意出来るならお前分かってて聞いてるだろ」ってなりますよ。 「頑張って揃える努力はしてくれてる」という前提の元なので 継続して頑張ってください。 バシッと出揃えられると自力でドンドン解決出来るようになると思いますしね。
guest

回答1

0

ベストアンサー

useCheckUserRoleかuseAxiosの二択ですねぃ。
ソースコード見直したらuseCheckUserRoleにダメな箇所見つけました。

質問文のコード読む限りの懸念がこの2点

  • Promiseとasync/awaitまで消化しきれてないのでは?
  • fetcherの作り方分かってないのでは?

Promiseは単体では非常に読み辛いです。
async/awaitを駆使しながらPromiseを意識せず使えるようになるとめっちゃ便利!ってなる機能です。
もしasync/awaitの勉強がまだなのであれば優先して勉強してみてください。

次にfetcherの使い方を見ていきましょう。
SWRを使おうぜという話 - Zenn
この記事ではfetcherは下記のように定義しています。

js

1 async function fetcher(url: string): Promise<boolean | null> { 2 const response = await fetch(url); 3 return response.json(); 4 }

つまりfetcherはPromiseを返す関数でなければならない。
(async関数を定義すると必ずPromiseのインスタンスを返り値として返す関数になる)
Promiseの返り値でresponse.json()を実行している。

js

1import useSWR from 'swr' 2import useAxios from './useAxios' 3 4// 君たちリクエストが来てから泥縄のように関数作らなくていいよね?って事で外に移植 5const axios = useAxios(); //カスタマイズした設定のaxiosインスタンスを取得 6const fetcher = async (url: string) => { 7 // async関数を使う事でawait構文が利用可能になる。 8 // await構文の効果は、Promiseインスタンスの.then(val => {})を実行してvalを取り出す 9 const res = await axios.get(url); 10 11 console.log(res.data); // 受け取ったデータをconsole.logとして表示するコードはそのままにしておいた 12 13 // Promiseインスタンスでres.dataを返すように変更 14 return res.data; 15} 16 17const useCheckUserRole = (role: string) => { 18 const { data, error } = useSWR(`auth/currentuser/${role}`, fetcher, { revalidateIfStale: true }) //テスト用にキャッシュをオフにしています 19 return { 20 user: data, 21 isLoading: !error && !data, 22 isError: error 23 } 24} 25 26export default useCheckUserRole;

とりあえずこれで進むと思うので修正してみてください。

投稿2021/10/19 10:14

編集2021/10/19 10:47
miyabi-sun

総合スコア21203

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fresh_fish

2021/10/19 11:41

丁寧に何度も答えていただきありがとうございます fetcher関数をasyncでラップすることで動かすことができました。 回答を貰ってからPromise,async/awaitについて改めて調べて自分で書いてみたところ、then,catch構文ではPromiseオブジェクトを外に出せないことがわかりました。 fetcher関数がPromiseオブジェクトを返さなければならないとするとasync/awaitを使わなければならないのは今考えれば当然ですね。 ただまだ一つ疑問がありまして、公式ドキュメント https://swr.vercel.app/ja/docs/data-fetching がthen catch 構文を使用しているのは何故でしょうか。 私はここを参考にしてコードを書いていたのですが実際には動きませんでした。 公式ドキュメントが間違ったコードを載せていることはないと思うので、 promise.then.catch構文でも書き方によってpromiseオブジェクトを返すことが可能ということなのでしょうか。 コードは動いたのでベストアンサーにして質問を解決済みにしますが、もしまだお時間があれば教えていただけると嬉しいです。 解決していただきありがとうございました。
miyabi-sun

2021/10/19 12:32 編集

> const fetcher = url => axios.get(url).then(res => res.data) 公式サイトのアロー関数はこれですよね。 アロー関数を定義する時、1行で終わる時は{}を省略する事ができます。 その{}を省略した場合の挙動に関しては、1行目の結果をreturnするよう変化します。 つまり、このfetcher関数の返り値は`axios.get().then()`をまるっと包んだPromiseインスタンスです。 一目でわかりますし、あえてasync/awaitを使うまでもない。 ワンライナー(1行)でさくっと済ませちゃおうと上級JSerが書いた記事だからこういう表現になったのでしょう。 それに対して質問文のコードは{}を省略していませんね。 この場合、returnで何かを明示的に返すようにしなければundefinedを返す事になります。 知らないとハマる箇所なので要注意です。 > 公式ドキュメントがthen catch 構文を使用しているのは何故でしょうか。 URLこっちですよね? https://swr.vercel.app/ja/docs/error-handling 内容を見る限りtry-catch構文を利用したいがためにasync/awaitを活用しています。 この辺はちゃんとした理解がないと混乱しがちになります。 アロー関数の{}省略版、async/awaitのあるなしに着目しながら見てください。
fresh_fish

2021/10/19 13:16

またしても丁寧な回答ありがとうございます。 まさか{}の有無で挙動が変わるとは思ってもいませんでした。 ES6の文法については勉強していたつもりでしたがまだまだ理解が足りなかったようですね。精進します。 バグの解消だけでなく細かな質問にも答えていただいて本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問