見出し画像

【アプリ開発日記52週目】Nextjsでエクセル読み込み… がまさかのTypeScriptに移行編

 明けましておめでとうございます! 2023年もよろしくお願いいたします。

 これまで2ヶ月弱、時間はかかったもののアプリの機能も充実してきました。しかし、肝心の問題がすっからかんなのです。もちろん医学知識を効率的に覚えるために作っているのですが、何しろ量が多いので作成画面で作っていてもキリがありません。

 ということで今回は問題の作成機能を強化していきます! の予定だったのですが、途中のミスで急遽JSからTypeScriptに書き換えることになりました。今回のメインはそっちになりそうです。もちろんエクセル読み込みまで実装していきます!

1,公開/非公開機能を実装する

 まずは公開/非公開機能の実装へ。APIを使った本格的なnextjsアプリは初めてなのでつまづくところも多いですが、今回みたいなAPIの作成やフロントエンドに特化したフレームワークの経験はSvelteなどにも活かせそうなので、意外と有意義な時間なのかもしれません。

 さっそく「非公開」になっているものは自分で作成したもの以外見れないようにしていきます。「status=='public'」or「sectionid==userId」という条件をviews.py側につけてあげればよさそう。

--- views.py ---


class BookView(generics.ListAPIView): # 一覧(全ID取得するときにも使用)
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = (AllowAny,)
    def get_queryset(self):
        queryset = Book.objects.all().filter(status='public')
        return queryset

class BookViewAndMine(generics.ListAPIView): # 一覧(そのユーザーが作成したブックも追加で取得)
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = (AllowAny,)
    def get_queryset(self):
        userId = self.request.query_params.get('userid')
        queryset = Book.objects.all().filter( Q(user=userId)|Q(status='public') )
        return queryset

 事前レンダリング時はpublicのものだけ表示されるようにして、ユーザーがログインしてから「そのユーザーが作成したノート」をすべて取得・事前レンダリング時のデータに上書きします。これもuseSWRを使いますが、もともと設置していたので少し書き換えればOK。

--- index.js ---

const apiUrl = `${process.env.NEXT_PUBLIC_RESTAPI_URL}api/book/`;
const { data: book } = useSWR(apiUrl, fetcher, { fallbackData: books, }, { refreshInterval: 1000 });

▼

const apiUrl = userId ? `${process.env.NEXT_PUBLIC_RESTAPI_URL}api/book-and-mine?userid=${userId}` : null;
const { data: book, mutate } = useSWR(apiUrl, fetcher, { fallbackData: books, }, { refreshInterval: 1000 });

 これで、他の人が作成した「公開」以外のものは表示されなくなったはずです! 試しにログアウトしてみると…

ログアウト時
他のユーザーでログイン時

 さっきあったノート2だけが消えている! ログアウト時はもちろんお気に入りもしていないので、すべて"Try next"に入っています。問題なく動いていますね。

 最初はセクションと問題も表示されないようにしようかと思いましたが、よくよく考えると

・セクション一覧が表示されるのはノート詳細ページにアクセスできた時
・問題一覧が表示されるのはセクション詳細ページにアクセスできた時

なので、下記で実装していくアクセス時のリダイレクト処理があればアクセス権のないユーザーは自動的に見れなくなりそうです。見れたとしても、セクションや問題ページで結局リダイレクトされます。

 しかし、このままだと「/section/1」のようにURLを直打ちすればアクセスできてしまう。そのため、ページに入ったときにも同様の条件を満たさなければリダイレクトされるようにしましょう。

 探してみると…ちょうどいい処理が用意されていました! 戻る/進む時の手間がこれでぐっと減りますね。

document.getElementById('go-back').addEventListener('click', () => {
  history.back();
});

let userId = '';
const { data: session } = useSession();
if (session) userId = session.userId;
if (userId && book) {
  if (book.status!='public' && book.user!=userId) history.back();
}

 これで画面見れないはず…!と思っていたのですが、残念ながら0.5秒くらい、だいたい最初のセクションの名前は頑張れば読めるくらい遷移先のページが映ってしまいました。history.back()をuseRouterのrouter.back()に変えても特に変わらず。

 試しに読み込んだ瞬間にhistory.back()やrouter.back()を使ってみましたが、どちらもエラーに。そもそもレンダリングが終わってからでないと使えないらしく、これでは一瞬とは言えセクションが見えてしまいます。

 そもそもURLを直打ちすることはほとんどないため、一瞬publicのみのページが映るのは承知の上で、さきほどのノートと同じように「事前レンダリング時はpublicだけ取得、useSWRで自分が作成したものも追加でとってくる」というように書き換えることにします。なお、問題は親であるセクションのpublicで条件付けしました。

 これで非公開にしたセクション1は表示されないようになりました! これにて公開・非公開機能完了です。自分で作成したノートやセクションは編集ボタンが表示されるので、そこから公開設定も変更できます。

セクション1は非公開にしていますが、自分で作成したので編集可能&一覧に表示されました!

2,Nextjs をTypeScriptに変換する

 このまま次に進もう…としていた矢先、突然の大きな変化に見舞われました。というのもまさかのバグ発生。この先触れる予定だったExcel.jsを試すためにコードをコピペしていたところ、そのコードがTypeScriptだったのです。

 そのためビルドもストップし、デバックできなくなったのでnextを一度アンインストールしてバージョン下げているうちに、もとに戻らなくなってしまいました(そりゃそうだ)。

 この時点で、パッケージを一度すべてアンインストールし、一から再インストールすることに決定。でもさきほどのストップの根源がTypeScriptエラーだったので、おそらく同じエラーに遭遇する可能性もあります。

 これまでずっと気になっていたTypeScript。もう仕方ないということで、一応バックアップだけとってTypeScriptに書き換えていきます!

 まずはTypeScriptに書き換える公式チュートリアルから。ここでは、jsで作成したnextjsアプリを .tsx に書き換えました。といってもtxとtsxの違いがわからないくらいの初心者です。

 本当はだめだと思いますが(エラーも出てくる)、今回のチュートリアルでは敢えてjsとtxsを両方並べました。これから書き換えていく際、これを参考にしながら進めていきます。

 そして一応GitHubにも最新のjsたちをプッシュ・コピーを別の場所にした上で、バージョン云々の問題をリセットするため、この際一度アプリフォルダごと削除・create-next-appで再生成しました。さようならjavascript。

 またexceljs導入時にエラーになるのも怖いので、今までのコードをtypescriptに変える前に、まずアップロードしたエクセルのデータを移す実験をしてみます。下記のサイトのコードをnextjsに書き直せば…

 無事表示できました! 地味に一安心。ちゃんとtypescript動きました。ほんとよかった。

 さて、今の画面がもとのメイン画面です。同じアプリとは思えないくらい。なので、いよいよ少しずつ確認しながら実装していきます。

 まず_app.tsxと認証。[…nextauth].ts は別ファイルも必要で公式ドキュメントだけだとわからなかったので、下のサイトを参考にさせていただきました。ソースコードも公開されていて本当に有り難いです!

 d.tsも初めてだったため、手探りながら作成。これは敢えてimportとかする必要ないんですね。保存せずとも反映まで確認できるため、非常に快適です!

declare…?

 改めてサーバーを起動し、セッションを取得できるか確認してみると無事OK。ログインもできました。さらにgetStaticProps、専用の型が用意されてるくらいなので慎重に書き換えてみると…

 データも取得できました! APIは今回別サーバーに作っているので、再構築する必要もありません。

 一方で、沼にハマったDOM操作も数しれず。例えば、以下のようにcheckedを操作したい場合は敢えて()で囲む必要があるなど、typescript特有の文法になれるのに苦労しました。

○ (document.getElementById('check1') as HTMLInputElement).checked = bool;

× document.getElementById('check2').checked = bool;

 そんなこんなで…

 js → typescript への書き換え完了しました!!

3,ローディング処理を追加

 ずっとつけたかった&やや複雑だった表示問題を解決したので、だいぶすっきりしました。でもgetStaticPropsからブラウザを開いたときのデータフェッチに切り替えたため、取得したデータが反映された瞬間、何もなかったところに「ダダンッ!」と取得データが挟み込んでくるんですよね。

 もちろん機能的には何の問題もありません。が、テストする度にこうなるので、なんだか酔いそう。ということで「最初は空白のカラムを入れておく。空でもいいのでデータが入ってきたら置き換わる」という処理を追加していきます!

読み込み中
まだノートを追加していない時

 このローディングのくるくるは、下記サイトを参考にさせていただきました。CSSはほんとできないのでずっと頼りっきりです。たぶんこれからも。

 いいんじゃ! 自分は自分の得意なところを…、、、

4,エクセルを読み込んで確認画面・保存処理を作成する

 いよいよtypescriptに切り替えるきっかけにもなったExceljsの登場です。

 今までcsvをPythonでデータベースに出力、などはありましたが、ユーザーが用意したオリジナルなデータフォーマットを加工するのは今回が初めて。よくクラウドなどで見かける「ここにファイルをドロップして!」というアレです。

 と言っても動作確認を兼ねてさきほど作成してしまったので、ここでは参考にさせていただいたサイトと結果を。

デフォルトでもドラッグ&ドロップできました

 続いて、ドラッグ&ドロップの範囲を拡張。

 さらにフォームとPOST処理を整備。ドロップダウンメニューとその内容、POST時のIDを組み合わせによって変わるようにするのが地味に大変でした。さっそくエクセルをドラッグ&ドロップすると…

 読み込み完了! 最後に作成ボタンを押せば…

POST前
コンソールで無事POSTされたことが確認できます!

 保存されました!! この一覧ページのタブを開いた瞬間、画面ごとリロードせず問題がヒュッと増えたのも「nextjsとちょっと仲良くなれた気がする!」と感動した瞬間です。

 これでエクセルからもドラッグ&ドロップ+ワンタッチで問題をまとめて保存できるようになりました!

おわりに

 ついに公開/非公開・問題をまとめて作成する機能を実装。問題作れて見れて、実際に解ける・復習できる…(しかもデプロイ試験済み!)段階まで来たので、この問題アプリも一段落というところでしょうか。

 ただ、お気づきでしょうか。いたるところにバグが残ってるんですよね。。。さっきの画像でも履歴の色が反映されなくなっています。おそらく非公開にしたはずみで履歴まで取得制限がかかってしまったのでしょうが。

 もちろん次はこういった細かなバグを修正していくのですが、別に新しい機能を加えたりするわけではないので、作業も日記としても非常に地味なんですよね。これが最大の悩みです。どうしよう。

 というわけで次は地味な回になるかもしれませんが!

 ではでは!

この記事が気に入ったらサポートをしてみませんか?