見出し画像

Next.js Practice:1-4 next/routerオブジェクトの練習

【0】はじめに:今回作成してみるもの

 今回はSelectボックスの項目を選んでURLを書き換える(URLにクエリパラメータを付与する)。さらに、書き換えたURLにあるクエリパラメータを使って表示内容を切り替える(フィルタリングする)。

画像1

画像2

【1】vtuber一覧を取得する関数を作成

まずは、「vgroupname」をわたすと、その「vgroupid」に紐づくvtuber一覧を取得する関数を作成する。

【./utils/database/getVtubers.ts】
※今回はテーブルがわかれているので、VGROUPテーブルでの検索結果を使ってVTUBERテーブルを検索する挙動。

import { VtuberModel } from './../../interfaces/vtuber';
import { openDB } from "../../pages/openDB";

//SQLの結果を受け付ける型
interface Vgroup{
   vgroupid:number;
   vgroupname:string;
}

export async function getVtubers(vgroupname:string) {
   const db = await openDB();

   const result = await db.get<Vgroup|undefined>(`
       SELECT vgroupid, vgroupname
         FROM VGROUP 
           WHERE vgroupname = ? 
       `
       ,vgroupname);

       //debug
       //console.log(result?.vgroupid, result?.vgroupname);
   const vtubers = await db.all<VtuberModel[]>(`
           SELECT *
            FROM VTUBER
             WHERE vgroupid = ?
       `
       ,result?.vgroupid
   );

   return vtubers || null;

}

【2】apiで動作確認(apiの復習、クエリパラメータの取り扱い)

■apiエンドポイント作り方
・「pages/api配下」にソースコードを書く。
・URL「/api/ファイル名」がapiエンドポイントになる。
・関数の引数の型は
(req:NextApiRequest,res: NextApiResponse)

【./pages/api/vtubers.ts】※クエリパラメータへの前処理未実施

export default async function vtubers(req:NextApiRequest,res:NextApiResponse) {
   
   const vgroup = req.query.vgroup;

   const data = await getVtubers(vgroup);
   res.json(data);
}

単純にクエリパラメータ「vgroup」から値を取って、それを作成した関数に渡せばよさそうに思えるが、エディタ側で以下のようなエラーを吐く。

画像3

「string | string[]」という記述があるが、これは同じクエリパラメータから取得する値が複数あると配列が返ってくるため。
【例】
・「www.myapp.com?myparam=hello」でアクセス
 ⇒「req.query.myparam」で「hello」が返ってくる

「www.myapp.com?myparam=hello&myparam=hello2」みたいに同じパラメータ名を複数セットしてアクセス。
 ⇒「
req.query.myparam」で「[hello, hello2]」の配列が返ってくる。

そこで
「同名のクエリパラメータを複数セットされ、配列データを取得しても最初のクエリパラメータだけを取ってくる」
ようにする。

【./pages/api/vtubers.ts】※クエリパラメータへの処理追加済み

import { NextApiRequest, NextApiResponse } from 'next';
import { getVtubers } from '../../utils/database/getVtubers';


function getAsString(value:string|string[]):string {
   if(Array.isArray(value)){
       return value[0];
   }

   return value;
}


export default async function vtubers(req:NextApiRequest,res:NextApiResponse) {
   
   const vgroup = getAsString(req.query.vgroup)
   console.log(vgroup);
   const data = await getVtubers(vgroup);
   res.json(data);
}

↓ さらにユーティリティ関数として分離して使いやすくする

【./utils/getAsString.ts】

export function getAsString(value:string|string[]):string {
   if(Array.isArray(value)){
       return value[0];
   }

   return value;
}

【./pages/api/vtubers.ts】※getAsString()はimportする

import { NextApiRequest, NextApiResponse } from 'next';
import { getVtubers } from '../../utils/database/getVtubers';
import { getAsString } from '../../utils/getAsString';


// export function getAsString(value:string|string[]):string {
//     if(Array.isArray(value)){
//         return value[0];
//     }

//     return value;
// }


export default async function vtubers(req:NextApiRequest,res:NextApiResponse) {
   
   const vgroup = getAsString(req.query.vgroup)
   //console.log(vgroup);
   const data = await getVtubers(vgroup);
   res.json(data);
}

■実行結果
「/api/vtubers?vgroup=VOMS」とかでアクセスする

画像4

【3】getServerSideProps内で複数回のデータ取得を実施する

「getServerSideProps()」内で「 getVgroupCount()」、「getVtubers()」の2つのデータ取得関数をコールする。
 この時に注意すべきは非同期処理。
⇒ 両方のデータ取得が完了した後に
propsとしてコンポーネントに渡す

【./pages/home.tsx】

import { GetServerSideProps } from "next";
import { getVgroupCount, Vcount } from "../utils/database/getVgroupsCount";
import {Formik,Form} from 'formik';

import {Paper, Grid,Button} from '@material-ui/core';
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import { Field } from "formik";
import { useRouter } from "next/dist/client/router";
import { getVtubers } from "../utils/database/getVtubers";
import { getAsString } from "../utils/getAsString";
import { VtuberModel } from "../interfaces/vtuber";

export interface HomeProps{
   vgcounts:Vcount[];
   vtubers:VtuberModel[];
}


//css:マージン調整など
const useStyles = makeStyles((theme: Theme) =>
 createStyles({
   paper: {
     margin: 'auto',
     maxWidth: 500,
     padding: theme.spacing(3) //padding設定
   }
 }),
);


const Home = ({vgcounts,vtubers}:HomeProps) =>{


   const classes = useStyles();
   const {query} = useRouter();


   //Formikフォームの初期値
   const initialValues ={
       vgroup: query.vgroup || '' //selectボックスの初期値
   }

   return(
       <div>
           <Formik
            initialValues={initialValues} 
            onSubmit={(values,formikHelpers)=>{
                //alert(values.vgroup);
                //console.log(values);
                //console.log(formikHelpers);
                //console.log("--------");
           }}>
               {(values)=>(
               <Form>
                   <Paper elevation={5} className={classes.paper}>
                   <FormControl fullWidth variant="outlined">
                       <InputLabel id="search-vgroup">Vgroup</InputLabel>

                       <Field name="vgroup" as={Select} labelId="search-vgroup" label="Vgroup">
                           <MenuItem value="">
                               <em>None</em>
                           </MenuItem>
                           {
                               vgcounts.map((vgcount)=>{
                                   return(
                                       <MenuItem value={vgcount.vgroupname} key={vgcount.vgroupname}>
                                           {`${vgcount.vgroupname} (${vgcount.count})`}
                                       </MenuItem>
                                   );
                               })
                           }
                       </Field>
                   </FormControl>
                   <Button type="submit" variant="contained" color="primary">Submit(Debug)</Button>
                   </Paper>
               </Form>
               )}
           </Formik>
           <h1>{JSON.stringify(vgcounts,null,4)}</h1>
           <h1>{JSON.stringify(vtubers,null,4)}</h1>
       </div>
   );
}

export default Home


export const getServerSideProps:GetServerSideProps = async(ctx)=>{
   //const vgcounts = await getVgroupCount();

   const vgroup = getAsString(ctx.query.vgroup);
   const [vgcounts, vtubers] = await Promise.all(
       [
           getVgroupCount(),
           getVtubers(vgroup)
       ]
   );


   //return {props:{vgcounts}};
   return { props:{vgcounts,vtubers}};
}

画像5

ここまでで、「URL直打ち」+[Enter]でクエリパラメータを使ったフィルタリングみたいなことができた。

【4】URLを書き換える処理(next/routerオブジェクトの練習)

最後に、「submitボタンをおすとURLを書き換える処理」を追加する。
ここで使うのが「next/router」の「router.push」。

submitボタンを押すとURLを書き換える
 ⇒ 「Formik」の「onSubmit」内に記述する。

【./pages/home.tsx】
※必要なクエリパラメータ以外に、動作確認用にクエリパラメータ:「test:1」を仕込んでみている

import { GetServerSideProps } from "next";
import { getVgroupCount, Vcount } from "../utils/database/getVgroupsCount";
import {Formik,Form} from 'formik';

import {Paper, Grid,Button} from '@material-ui/core';
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import { Field } from "formik";
import  router, {useRouter } from "next/router";
import { getVtubers } from "../utils/database/getVtubers";
import { getAsString } from "../utils/getAsString";
import { VtuberModel } from "../interfaces/vtuber";

export interface HomeProps{
   vgcounts:Vcount[];
   vtubers:VtuberModel[];
}


//css:マージン調整など
const useStyles = makeStyles((theme: Theme) =>
 createStyles({
   paper: {
     margin: 'auto',
     maxWidth: 500,
     padding: theme.spacing(3) //padding設定
   }
 }),
);


const Home = ({vgcounts,vtubers}:HomeProps) =>{


   const classes = useStyles();
   const {query} = useRouter();


   //Formikフォームの初期値
   const initialValues ={
       vgroup: query.vgroup || '' //selectボックスの初期値
   }

   return(
       <div>
           <Formik
            initialValues={initialValues} 
            onSubmit={(values,formikHelpers)=>{
               router.push({
                   pathname:'/home',
                   query:{...values,test:1} //付与するクエリストリング
               }
               , undefined, {shallow:false} //trueだとgetServerSidePropsが動かないshallowになる
               )
           }}>
               {(values)=>(
               <Form>
                   <Paper elevation={5} className={classes.paper}>
                   <FormControl fullWidth variant="outlined">
                       <InputLabel id="search-vgroup">Vgroup</InputLabel>

                       <Field name="vgroup" as={Select} labelId="search-vgroup" label="Vgroup">
                           <MenuItem value="">
                               <em>None</em>
                           </MenuItem>
                           {
                               vgcounts.map((vgcount)=>{
                                   return(
                                       <MenuItem value={vgcount.vgroupname} key={vgcount.vgroupname}>
                                           {`${vgcount.vgroupname} (${vgcount.count})`}
                                       </MenuItem>
                                   );
                               })
                           }
                       </Field>
                   </FormControl>
                   <Button type="submit" variant="contained" color="primary">Submit(Debug)</Button>
                   </Paper>
               </Form>
               )}
           </Formik>
           <h1>{JSON.stringify(vgcounts,null,4)}</h1>
           <h1>{JSON.stringify(vtubers,null,4)}</h1>
       </div>
   );
}

export default Home


export const getServerSideProps:GetServerSideProps = async(ctx)=>{
   //const vgcounts = await getVgroupCount();

   const vgroup = getAsString(ctx.query.vgroup);
   const [vgcounts, vtubers] = await Promise.all(
       [
           getVgroupCount(),
           getVtubers(vgroup)
       ]
   );


   //return {props:{vgcounts}};
   return { props:{vgcounts,vtubers}};
}

画像6

■Shallow Routingについて

router.pushの記述の「shallow」という項目について。

router.push(
   {
       pathname:'/home',
       query:{...values,test:1} //付与するクエリストリング
   }
   , undefined
   , {
       shallow:false //trueだとgetServerSidePropsが動かないshallowになる
     } 
)

 これをtrueにするとgetServerSidePropsやgetStaticPropsといったデータフェッチを動かさずに、URLを書き換えるようになる。

例えば、今回の例で「shallow:true」としてみる。

画像7

こんな感じでパス名とクエリパラメタの書き換えだけをしてstateを変更しないような挙動をさせるときにshallow routingを使う。

もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。