next.js の アプリをサブディレクトリで動作させる

● nginxの設定ファイル

      location /app/ {
          proxy_pass http://localhost:3000/;
      }

● next.config.js

assetPrefix を追加します

const SUB_DIRECTORY = "/app";

const isProduction = process.env.NODE_ENV === "production";

/** @type {import('next').NextConfig} */
const nextConfig = {
  assetPrefix: isProduction ? SUB_DIRECTORY : "/" ,
  reactStrictMode: true,
  swcMinify: true,
};

module.exports = nextConfig;
No.2213
09/13 15:56

edit

next.js の ルートURL を変更する

● Next.jsでURLのルートを変更する

next.config.js

module.exports = {
  assetPrefix: '/hoge'
};

● run dev / run build で自動的に瀬戸される NODE_ENV を使用して設定を分ける場合

next.config.js

const isProduction = process.env.NODE_ENV === "production";
module.exports = {
  assetPrefix: isProduction ? '/app' : '/'
};
No.2212
09/13 13:48

edit

Remix

● Remixのインストール(対話形式)

npx create-remix@latest

いろいろ質問されるので選択していきます。

? What type of app do you want to create? (Use arrow keys)

テンプレートを選択するにはこちらを選択します

 A pre-configured stack ready for production 

● Remixのインストール(テンプレート指定方式)

1. テンプレート(Remix-stack)こちらから選択する

https://github.com/topics/remix-stack

2. テンプレートを指定して Remix をインストールする

例 : franck-boucher / mantine-stack をインストールする場合

npx create-remix --template franck-boucher/mantine-stack
No.2210
09/08 12:57

edit

typescript + dayjs で 日付を扱う

● dayjsのインストール

npm install dayjs

● timezoneを扱う .tz() メソッドを使用できるようにする

import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";

dayjs.extend(timezone);
dayjs.extend(utc);

● 現在時刻を表示する

console.log(dayjs().format('YYYY-MM-DD HH:mm:ss'))

● 現在時刻を UTC で表示する

console.log(dayjs().utc().format('YYYY-MM-DD HH:mm:ss'))

参考 : https://bit.ly/3cywKQ8
https://qiita.com/taisuke-j/items/58519f7ecd5ae3a1db0c

No.2207
08/31 10:29

edit

next.js で cypress の E2Eテスト

● nextjsアプリと cypress のインストール

npx create-next-app@latest --ts cypress-testing-app
cd cypress-testing-app

npm install cypress --save-dev
npm install @testing-library/cypress --save-dev

package.json に以下を追加

  "scripts": {
    ......
    "cy:open": "cypress open",
    "cy:run": "cypress run"
  },

● nextjsアプリの実行

あらかじめ実行しておきます

npm run dev

● cypressの起動

最初に一度起動します

npm run cy:run

● テストスクリプトを追加

cypress\e2e\0-my-tests\0-my-sample.cy.js

describe('example to-do app', () => {

  it('ルートパスに訪問できるか', () => {
    cy.visit('http://localhost:3000/')
  })
  
})

● テストを実行(GUI)

npm run cy:open

● テストを実行(CUI)

npm run cy:run
No.2206
08/30 17:05

edit

react で テストを jest or vitest で比較してみる

● A. react + typescript + jest アプリでテストを実行する

アプリの初期化

npx create-react-app react-jest-app --template typescript

その他パッケージのインストール

npm install recoil @types/recoil
npm install -D jest @types/jest ts-jest 
npm install -D @babel/preset-react
npm install -D @testing-library/react

アプリの作成

(割愛します。ボタンをクリックすると数字が1つずつ増えていくボタンとそれをRecoilを通して表示すると言う簡単なアプリです)

app.test.tsx を作成して実行する

import App from "../App";
import {
  render,
  screen,
  fireEvent,
  RenderResult,
} from "@testing-library/react";

describe("App.tsx", () => {
  test("アプリをマウントすると Counterに「ボタン 0」が表示される", () => {
    render(<App />);
    expect(screen.getByRole("button").innerHTML).toEqual("ボタン 0");
  });

  test("アプリをマウントすると Viewerに「表示 0」が表示される", () => {
    render(<App />);
    expect(screen.getByTestId("viewer-count-value").innerHTML).toEqual(
      "表示 0"
    );
  });

  test("ボタンをクリックすると 「ボタン 1」に表示が変わる", async () => {
    render(<App />);
    const button = screen.getByRole("button");
    await fireEvent.click(button);
    // console.log(button.innerHTML);
    expect(screen.getByRole("button").innerHTML).toEqual("ボタン 1");
  });

  test("ボタンを5回クリックすると 「ボタン 5」に表示が変わる", async () => {
    render(<App />);
    const button = screen.getByRole("button");

    await fireEvent.click(button);
    await fireEvent.click(button);
    await fireEvent.click(button);
    await fireEvent.click(button);
    await fireEvent.click(button);

    expect(screen.getByRole("button").innerHTML).toEqual("ボタン 5");
  });
});

テストの実行

npx react-scripts test 

● B. react + typescript + vite + vitest + happy-dom アプリでテストを実行する

アプリの初期化

(react を選択してから、react-tsを選択します)

npm init vite@latest react-vitest-app

その他パッケージのインストール

npm install recoil @types/recoil
npm install -D vitest
npm install -D @testing-library/react
npm install happy-dom

vitest 用に vite.config.ts を変更

vite.config.ts

/// <reference types="vitest" />

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'happy-dom',
  },
})

テストの実行

npx vitest --reporter verbose

参考 : https://bit.ly/3JuAaPV
参考 : https://dev.classmethod.jp/articles/intro-vitest/

● jest , vitest の違いについて

vitestの場合

  afterEach(() => {
    cleanup();
  })

を実行して、

    render(<App />);

を毎回初期化する必要があるようです。

速度について

速度についてはテストケースが少ない場合はさほど違いはありませんがvitestの方が若干早いです

No.2197
08/31 21:51

edit

No.2192
07/21 15:29

edit

How to use async and await inside a React fc?

import axios from "axios";
import React, { useEffect, useState } from "react";

export default function App() {
  const [val, setVal] = useState();

  const getAnswer = async () => {
    const { data } = await axios("https://yesno.wtf/api");
    setVal(data.answer);
  };

  useEffect(() => {
    getAnswer();
  }, []);

  return <div>{val}</div>;
}

https://bit.ly/3RKVCDZ

No.2190
07/19 20:56

edit

ReactNode や ReactChild の別の型との関係性

● ReactNode

    type ReactNode = ReactElement | string | number | ReactFragment | ReactPortal | boolean | null | undefined;

● ReactChild

    type ReactChild = ReactElement | string | number;

参考 : https://dackdive.hateblo.jp/entry/2019/08/07/090000

No.2189
07/19 15:45

edit

nextjs middleware

middlewareはサーバサイドです

import { NextRequest, NextResponse } from 'next/server';

const isClient = () => typeof window !== 'undefined';

export const middleware = (req: NextRequest) => {
  console.log('isClient ?');
  console.log(isClient());
  return NextResponse.next();
};
No.2184
07/12 11:33

edit

Next.js で 画面遷移、1つ前の履歴に戻る

● 1. Link を使用する

import Link from 'next/link'
<Link href="/about"><a>About Us</a></Link>

● 2. onClick など、メソッドで画面遷移したい場合は 「useRouter」または「Router」を使用する

useRouter

import { useRouter } from 'next/router';

const router = useRouter();
router.push('/home'); // '/home'へ遷移

Router

import Router from 'next/router';

Router.push('/home');  // '/home'へ遷移

Next.jsのRouterとuseRouterは何が違うのか - Qiita

● Next.js で 1つ前の履歴に戻る

      <button onClick={() => router.back()}>
        戻る
      </button>
No.2168
10/04 17:17

edit

Next.js + SQLite

nextjsアプリの初期化

npx create-next-app@latest --ts sample-app
cd sample-app

パッケージのインストール

npm install --save typescript ts-node
npm install --save typeorm sqlite3
npm install sqlite3 --save
npm install reflect-metadata --save
npm install @types/node --save
yarn typeorm init --database sqlite3

ormconfig.json が 自動生成されますので、以下のように追記します。

   "type": "sqlite",
   "database": "data/dev.sqlite",    
{
   "type": "sqlite",
   "database": "data/dev.sqlite",    

   "synchronize": true,
   "logging": false,
   "entities": [
      "src/entity/**/*.ts"
   ],
   "migrations": [
      "src/migration/**/*.ts"
   ],
   "subscribers": [
      "src/subscriber/**/*.ts"
   ],
   "cli": {
      "entitiesDir": "src/entity",
      "migrationsDir": "src/migration",
      "subscribersDir": "src/subscriber"
   }
}

テーブル Postを追加してみます

/src/entity/Post.ts

import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  readonly id: number;

  @Column('varchar', { length: 255, nullable: false })
  name: string;

  @Column('int', { nullable: true })
  sort_no: string;

  @CreateDateColumn()
  readonly created_at?: Date;

  @UpdateDateColumn()
  readonly updated_at?: Date;
}

マイグレーションの実行

Windowsのターミナルから

node_modules\.bin\ts-node ./node_modules/typeorm/cli.js  migration:generate -n Post
node_modules\.bin\ts-node ./node_modules/typeorm/cli.js  migration:run

と実行します

https://www.wakuwakubank.com/posts/730-typeorm-custom-naming/

elastomer_appま

●マイグレーション

yarn typeorm init

1. DB接続の確認 と マイグレーションの確認

yarn ts-node node_modules/.bin/typeorm migration:show

2. マイグレーションファイルの自動生成

エンティティ Post の マイグレーションファイルを自動生成する

yarn ts-node node_modules/.bin/typeorm migration:generate -n Post

src/migration/1647220932735-Post.ts といった命名のファイルが自動生成されます

3. マイグレーションの実行

yarn ts-node node_modules/.bin/typeorm migration:run
No.2162
03/14 10:24

edit

Next.js で import による ReferenceError: window is not defined を回避する

Next.js で import による ReferenceError: window is not defined を回避する

import MyComp from "../components/MyComp";

  ↓

import dynamic from "next/dynamic";
let MyComp = dynamic(() => import("../components/MyComp"), {
  ssr: false,
});

以上で、このようなエラーが解消されます。

オプション With no SSR

const DynamicComponentWithNoSSR = dynamic(
  () => import('../components/hello3'),
  { ssr: false }
)

オプション With suspense

React18以上が必要です。必ずバージョンを確認しましょう

const DynamicLazyComponent = dynamic(() => import('../components/hello4'), {
  suspense: true,
})

https://nextjs.org/docs/advanced-features/dynamic-import

添付ファイル1
No.2158
02/24 11:16

edit

添付ファイル

React の if文、for文 in JSX

● (React の if文)論理演算子&& を利用する

例2: isAdmin == true の場合に 私は管理者ですと表示します。

{isAdmin && <div>私は管理者です</div>}

例2: 未ログインの場合に class="hidden" とします。

className={! isLogin && 'hidden'}>

● (React の if文)三項演算子を利用する

<div>
    {isLogin
        ? 'ログイン済み'
        : '未ログイン'
    }
</div>

● (React の if文の入れ子)「三項演算子+論理演算子&&」を利用する

ログインしつつ、管理者の場合に「管理者です」を表示する

          {isLogin ? ( isAdmin && <div>管理者です</div> )
                : (null)
          }

● (React の for文)配列.map と 無名関数を利用する

const data = [
    { text: "hogehoge" },
    { text: "fugafuga" }
  ];
<ul>
  {data.map((v,i) => {
    return <li>{v.text} {i}番目</li>;
  })}
</ul>

i は 0から始まります

配列じゃないものが渡ってくる可能性がある場合は事前にチェックします

{Array.isArray( data ) &&
  data.map((v,i) => {
    return <li>{v.text} {i}番目</li>;
  })
}

Missing "key" prop for element in iterator のアラートが出る場合はkeyを渡します

<ul>
  {data.map((v) => {
    return <li key={v.id}>{v.text}</li>;
  })}
</ul>

また return と {} は省略できます。

      <ul>
      {data.map((v) => 
        <li key={v.id}>{v.name}</li>
      )}
    </ul>

開発中に console.log を使用する場合は return と {} は残しておいた方が便利かもしれません。

<ul>
  {data.map((v) => {
    console.log( v.id );
    return <li key={v.id}>{v.text}</li>;
  })}
</ul>
No.2057
02/24 09:08

edit

Next.js アプリの初期化

● Next.js アプリの初期化(JavaScript)

npx create-next-app@latest  my-app

● Next.js アプリの初期化(TypeScript)

npx create-next-app@latest --ts my-app

● Next.js アプリの初期化(JavaScript + TailWind CSS を追加)

npx create-next-app -e with-tailwindcss my-project

-e オプションはこちらのリポジトリからデータを持ってきます https://github.com/vercel/next.js/tree/master/examples

● Next.js アプリの初期化(TypeScript + TailWind CSS を追加)

公式のリポジトリにサンプルがないためまずTypeScriptでアプリを作成してその後にTailWindを追加します

・1. Next.js アプリの初期化(TypeScript)

npx create-next-app@latest --ts my-app

・2. TailWindのインストールと設定ファイルの初期化

cd my-app
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p

・3. tailwind.config.js に mode と purge を追記する

module.exports = {
  mode: 'jit',
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

・4. tailwindを _app.ts から読み込む

import 'tailwindcss/tailwind.css';

・5. index.tsx の JSXを試しに以下のようにする

  return (
    <div className="text-red-500 text-4xl sm:text-6xl lg:text-7xl leading-none font-extrabold tracking-tight mt-10 mb-8 sm:mt-14 sm:mb-10">テストです</div>
  )

● VS Code の IntelliSense for CSS class names in HTML が重い場合

IntelliSense for CSS class names in HTML を無効にしましょう
No.2070
02/24 08:49

edit

Next.js で クライアントサイドfetch を サーバーサイドへ移す

●クライアントサイド

import useSWR from "swr";
import axios from "axios";

export default function GoogleBooks() {
  console.log("😀");

  const fetcher = (url: string) =>
    axios(url).then((res) => {
      return res.data;
    });

  const { data, error } = useSWR(
    `https://www.googleapis.com/books/v1/volumes?q=typescript`,
    fetcher
  );

  if (error) return <div>failed to load</div>;
  if (!data) return <div>now loading...</div>;

  // この console.log は ブラウザのコンソールに表示される。
  console.log("===== data =====");
  console.log(data);

  return (
    <div>
      <ul>
        {data.items.map((item: any, i: number) => (
          <li key={i}>{item.volumeInfo.title}</li>
        ))}
      </ul>
    </div>
  );
}

●サーバーサイド

getServerSideProps() を使用するとサーバーサイド処理になります。

// Server Side Rendering
export async function getServerSideProps() {
  const response = await fetch(
    encodeURI("https://www.googleapis.com/books/v1/volumes?q=typescript")
  );
  return {
    props: {
      bookList: await response.json(),
    },
  };
}

export default function GoogleBooksSSR(props: any) {
  console.log("😎");

  // この console.log は サーバー側のターミナルに表示される。
  console.log("===== props =====");
  console.log(props);

  return (
    <div>
      <ul>
        {props.bookList.items.map((item: any, i: number) => (
          <li key={i}>{item.volumeInfo.title}</li>
        ))}
      </ul>
    </div>
  );
}

なお useSWR もサーバーサイドで利用できるようです
【React】useSWRはAPIからデータ取得をする快適なReact Hooksだと伝えたい - パンダのプログラミングブログ

No.2155
02/16 08:55

edit

Next.js で ホットリロードが聞かない時の変更箇所

next.config.js

module.exports = {
  webpack: (config, { dev }) => {
    if (dev) {
      config.watchOptions = {
        poll: 1000,
        aggregateTimeout: 200,
      };
    }

    return config;
  },
};
No.2154
02/15 17:07

edit

Reactで各種lint ( eslint , stylelint , husky + lint-staged )

● 1. Reactアプリの初期化

npx create-react-app sample-ts-app-lint --template typescript
cd sample-ts-app-lint

● 2. ESlintのインストール

yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn add -D eslint-plugin-react eslint-plugin-react-hooks

● 設定ファイル(.eslintrc.js)の自動生成

yarn eslint --init

質問に答えていくとファイルが(.eslintrc.js)生成されます。 そこに自分でオプションを書き加えて行きます

.eslintrc.jsの例

module.exports = {
    "env": {
        "browser": true,
        "es2021": true,
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:react-hooks/recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "@typescript-eslint"
    ],
    "rules": {
    }
}
  1:1  error  'module' is not defined  no-undef

を消すために

"node": true

を記述します

● ESlintの実行

eslint を実行

eslint  . 

eslint を実行(デバッグ実行)

eslint  --debug . 

.gitignore に記載されているパスを除外しながら拡張子 .js , .jsx .ts , .tsx に対して eslint を実行

eslint --ext .js,.ts,.jsx,.tsx --ignore-path .gitignore . 

.gitignore に記載されているパスを除外しながら拡張子 .js , .jsx .ts , .tsx に対して eslint を実行(デバッグ表示)

DEBUG=eslint:*  eslint --ext .js,.ts,.jsx,.tsx --ignore-path .gitignore .

.gitignore に記載されているパスを除外しながら拡張子 .js , .jsx .ts , .tsx に対して eslint を実行(対象ファイル表示)

(Macの場合です。 /User でgrep をかけています)

DEBUG=eslint:*  eslint --ext .js,.ts,.jsx,.tsx --ignore-path .gitignore . 2>&1 | grep /User

eslintの現在の設定を確認する

eslint --print-config src/index.ts

package.jsonにも記述しておきます

  "scripts": {
    "lint:js": "eslint --ext .js,.ts,.jsx,.tsx --ignore-path .gitignore . ",
  },

↑ yarn run lint:js で起動します

● 3. stylelintのインストール

yarn add -D stylelint stylelint-config-prettier stylelint-config-standard

● stylelint設定ファイルの作成

stylelint.config.js

module.exports = {
  extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
  rules: {},
}

● stylelintの実行

yarn stylelint **/*.{scss,css} --ignore-path .gitignore

package.jsonにも記述しておきます

"lint:css": "stylelint **/*.{scss,css} --ignore-path .gitignore",

yarn run lint:css で実行できるようになります

● 4. husky + lint-staged でコミット前にステージングされているファイルを対象にeslintを自動実行する

huskyで消耗したくない人はこちら
husky v5 で消耗している人には simple-git-hooks がお薦め - Qiita

husky lint-staged npm-run-all のインストール

yarn add -D husky lint-staged npm-run-all

package.json へ設定を追加

  "lint-staged": {
    "*.{ts,tsx}": "eslint",
    "*.{css,scss}": "stylelint"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "scripts": {
    "lint:js": "eslint --ext .js,.ts,.jsx,.tsx --ignore-path .gitignore . ",
    "lint:css": "stylelint **/*.{scss,css} --ignore-path .gitignore",
    "lint": "npm-run-all lint:js lint:css"
  },

● lint-staged の実行

yarn lint-staged

● lint-staged の実行(デバッグ表示)

動作が思ってたものと違う場合はこちらで実行して確認します

yarn lint-staged --debug

● lint-staged のオプション表示

yarn lint-staged --help

● Git hooks の有効化

npx husky-init && yarn

.husky/pre-commit が自動生成されます。

● 5. git commit でエラーが出ることを確認する

1. eslint . でエラーが出る状態にして
2. git aad -A
3. git commit

としてエラーが出ることを確認する

● vscode の設定

.vscode/settings.json

{
    "tslint.enable": false,
    "eslint.enable": true,
    "eslint.validate": [
      "javascript",
      "javascriptreact",
      { "language": "typescript", "autoFix": true },
      { "language": "typescriptreact", "autoFix": true }
  ],
}

参考 : https://qiita.com/karak/items/12811d235b0d8bc8ad00

No.2153
02/16 13:16

edit

環境変数を dotenv-cli で変更する

yarn add -D dotenv-cli

package.json

  "scripts": {
    "build:development": "dotenv -e .env.development react-scripts build",
    "build:staging": "dotenv -e .env.staging react-scripts build",
    "build:production": "dotenv -e .env.production react-scripts build",
  },
No.2152
02/04 16:18

edit

mui

● MUI v5 のインストール

( styled-components )で使用する場合

yarn add @mui/material @mui/styled-engine-sc styled-components

( emotion )で使用する場合

yarn add @mui/material @emotion/react @emotion/styled

エラーが出る場合は以下もインストールします

yarn add @emotion/react @emotion/styled

● MUI SVG icons のインストール

yarn add @mui/icons-material
No.2150
02/02 17:06

edit

frourio

● frourioアプリの初期化

(インストールには yarn 必須のようです)

yarn create frourio-app

(アプリ名は後ほどブラウザ画面から入力するのでここではこのまま入力します)

ブラウザ画面が立ち上がって構成セットを選択してインストールを実行します

● frourioの起動方法

vscode からフォルダを開いて、「NPMスクリプト」のメニューから「dev」を起動します

ちなみにpackage.json は以下のようになっています

  "scripts": {
    "dev": "npm run migrate:dev && run-p dev:*",
    "dev:client": "next dev -p 8001",
    "dev:server": "npm run dev --prefix server",
    "dev:aspida": "aspida --watch",
    ..................

● Prismaでテーブルを作成する

server/prisma/schema.prisma に記述を追加して、

// ● 追加
model Post {
  id            Int      @id @default(autoincrement())
  name          String
  content_name  String?
  created_at    DateTime @default(now())
  updated_at    DateTime
}

マイグレーションの実行

cd server/prisma/
npx prisma migrate dev --name <マイグレーションにつける名前>

例:

npx prisma migrate dev --name add_model_post

実行後

1. server/prisma/migrations/<実行日時>_add_model_post/migration.sql というファイルが新規作成されます  
2. postテーブルがdbに追加されます  

Prisma Clientコードを自動で作成

npx prisma generate

実行後

1. server/node_modules/.prisma/client/index.d.ts に post の型が自動生成されます

server/node_modules/.prisma/client/index.d.ts

/**
 * Model post
 */

export type post = {
  id: number
  name: string
  content_name: string | null
  created_at: Date
  updated_at: Date
}

● aspida の APIを書く

例えばエンドポイントが /post/ なら、対応する定義は server/api/post/index.ts に書きます。

1. ディレクトリ server/api/posts/ を新規作成する

cd app/server/api
mkdir posts

ディレクトリを作成すると作成したディレクトリ以下に3ファイルが自動生成されます

server/api/post/$relay.ts
server/api/post/controller.ts
server/api/post/index.ts

frourioの自動生成処理

server/api/ にディレクトリが追加されたらファイル群を自動生成
server/api/ に変更があったら $server.ts を自動生成

APIのURLをディレクトリとファイルの階層で表現する

パス変数を含むパス /tasks/{taskId}/ は、server/api/tasks/_taskId/index.ts のようになります。
_ から始まるパス変数のデフォルトの型は number | string です。明示的に指定する場合は変数名の後に @number などをつけ、_taskId@number と指定します。

引用: https://bit.ly/35x5YE3

2. server/api/post/index.ts を次の内容で保存する

server/api/posts/index.ts

import { defineController } from './$relay'
import type { Post } from '$prisma/client'

export default defineController(() => ({
  get: () => {
    return { status: 200, body: indexPost() }
  },
}))

const indexPost = (): Post[] => {
  return [
    {
      id: 1,
      name: 'string',
      content_name: 'string',
      created_at: new Date,
      updated_at: new Date,
    }
  ]
}

(あとで修正しますが一旦これで作成します)

アクセスして確認します。

server/api/$api.ts に サーバーのポートが記述されているのでそれを参考にアクセスします

http://localhost:10928/api/posts

TypeScript エラーが出ている場合は一度サーバーをストップして再起動すると直ることがあります

● クライアント側のページを作成する

pages/post/index.tsx

import useAspidaSWR from '@aspida/swr'
import { apiClient } from '~/utils/apiClient'

const Index = () => {

  const { data } = useAspidaSWR(
    apiClient.posts, {}
  );

  return (
    <>
      <h1>post/index</h1>
      {data &&
        <ul>
          {data.map((v, i) => {
            return <li key={v.id}>{v.id} : {v.name}</li>;
          })}
        </ul>
      }
    </>
  )
}
export default Index

アクセスして確認します。
http://localhost:8000/post

● server/api/post/index.ts を Prismaを使って実際にDBからデータをSELECTするように修正

Prismaの findMany メソッドを使ってデータ一覧を取得するように修正します

import { defineController } from './$relay'
import type { Post } from '$prisma/client'
import { PrismaClient } from '@prisma/client'
import { depend } from 'velona'

export default defineController(() => ({
  get: async () => {
    return { status: 200, body: await getIndexPost() }
  },
}))

const prisma = new PrismaClient()

export const getIndexPost = depend(
  { prisma: prisma as { post: { findMany(): Promise<Post[]> } } },
  () => {
    return prisma.post.findMany()
  }
)
No.2147
01/28 22:45

edit

React Hook Form ( + Yup + schema-to-yup )で バリデーションの設定をjsonで書いてフォームを作成する

● Yup + schema-to-yup のインストール

yarn add @hookform/resolvers yup
yarn add schema-to-yup

● 設定の記述例

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';

MyLogin.tsx

  const validationSchema = {
    $schema: "http://json-schema.org/draft-07/schema#",
    $id: "http://example.com/person.schema.json",
    type: "object",
    properties: {
      email: {
        type: "string",
        format: "email",
        required: true,
      },
      password: {
        type: "string",
        required: true,
      },
    }
  };

  const validationConfig = {
    errMessages: {
      email: {
        required: "メールアドレスを入力してください",
        format: "メールアドレスの形式が正しくありません",
      },
      password: {
        required: "パスワードを入力してください",
      },
    },
  };
  const { buildYup } = require("schema-to-yup");
  const yupSchema = buildYup(validationSchema, validationConfig);

  const formOptions = { resolver: yupResolver(yupSchema) };
  const { register, handleSubmit, setValue, formState: { errors } } = useForm(formOptions);
  const onSubmit = async (data: any) => {
    alert('form送信されました');
  }

jsxはとてもシンプルに記述できます

return (
<form onSubmit={handleSubmit(onSubmit)}>
    <input id="email" type="email" {...register('email')} />
    <div>{errors.email?.message}</div>

    <input id="password" type="text" {...register('password')} />
    <div>{errors.password?.message}</div>
</form>
)
No.2144
01/20 17:40

edit

Recoil

Reduxよりシンプルに扱えるステート管理ツールです。
ただし、ブラウザリロードするとあたりがリセットされてしまうので永続化は別途設定する必要があります。

・1. Recoilのインストール

yarn add recoil @types/recoil

知っておくといい概念

Recoliには大きく2つの概念があります。
AtomとSelectorです。
Atomは状態を管理します。状態の保持と状態の更新ができます。
SelectorはAtomで管理している状態を加工して取得することができます。

引用 : https://bit.ly/3Si4otw

・2. データストアの作成

認証に関するデータストアを以下のファイル名で作成してみます

src/Recoil/atoms/Auth.ts

import { atom } from 'recoil';

export interface Auth {
  displayName: string | null;
  email: string | null;
  isLogined: boolean;
  isAuthChecked: boolean;
}

const initialAuth: Auth = {
  displayName: null,
  email: null,
  isLogined: false,
  isAuthChecked: false,
};

export const authState = atom({
  key: 'auth',
  default: initialAuth,
})

・3. RecoilRoot の設置

import { RecoilRoot } from 'recoil';
<RecoilRoot>
  <App />
</RecoilRoot>

・4.値の取得

useRecoilState  : 1. 値の取得とSetter取得
useRecoilValue  : 2. 値の取得のみ
useSetRecoilState : 3. Setterのみ(useSetRecoilStateはあくまでSetter関数だけなので、Stateの値自体を参照していない限りComponentにはRe-Renderが走らない)

1. useRecoilState ( 値の取得と更新 )

import { myState } from "../recoil/atoms/myState"; // 自分で作成したRecoilState
import { useRecoilState } from 'recoil'
const [recoilAuth, setRecoilAuth] = useRecoilState(myState);

2. useRecoilValue ( 値の取得のみ )

import { myState } from "../recoil/atoms/myState"; // 自分で作成したRecoilState
import { useRecoilValue } from 'recoil'
  const recoilAuth = useRecoilValue(myState);    // 値のみ

3. useSetRecoilState ( setterのみ )

import { myState } from "../recoil/atoms/myState"; // 自分で作成したRecoilState
import { useSetRecoilState } from 'recoil'
  const setRecoilAuth = useSetRecoilState(myState);   // setterのみ(Re-Renderしない)

● recoil-persist を使ってRecoilのステートの永続化設定する

recoil-persistはデフォルトだとlocalStorageに保存されますが storageオプションを設定することで任意のStorageを利用することができます。

yarn add recoil-persist

例 : src/recoil/atoms/authState.tsx

import { recoilPersist } from 'recoil-persist'
const { persistAtom } = recoilPersist({
  key: 'recoil-persist',
  storage: sessionStorage,
});
export const authState = atom({
  key: 'authState',
  default: initialAuth,
  effects_UNSTABLE: [persistAtom],  // この行を追加
})

注意 : ログインしているかどうかの情報は絶対にローカルストレージやセッションストレージには保存しないようにしましょう。 (必ずサーバー側に情報を持たせるべきです)

No.2140
08/04 10:01

edit

Next.jsで作成したサイト に sitemap.xml を追加する

● Next.jsで作成したサイト に sitemap.xml を追加する

引用元 : Next.js に next-sitemap を導入して超手軽にサイトマップ sitemap.xml を生成しよう | fwywd(フュード)powered by キカガク

・1. next-sitemapのインストール

npm install --save-dev next-sitemap

・2. sitemap.config.jsの作成

sitemap.config.js

// config for next-sitemap

module.exports = {
  siteUrl: 'https://YOUR-SITE.com/',
  generateRobotsTxt: true,
  sitemapSize: 7000,
  outDir: './public',
};

・3. ビルドスクリプトにサイトマップを作成するコマンドを追加

package.json

  "scripts": {
    "build": "next build && next-sitemap --config sitemap.config.js",
  },

・4. ビルドの実行

npm run build

/public/sitemap.xml が生成されるので Google Search Console からGoogleに読み込ませます

・5. git管理に含めたくない場合は.gitignoreに記述して除外します

.gitignore

# next-sitemap が自動生成するファイルは除外する
/public/sitemap.xml
/public/robots.txt
No.2139
01/15 20:42

edit

React で スマホ / pc 判別

● React で スマホ / pc 判別

npm install react-device-detect
              {isMobile ? (
                <h1>スマホ</h1>
              ) : (
                <p>pc</p>
              )}
No.2138
01/14 16:12

edit

React で イベント実行関数(メソッド)に引数を渡す。間違えて渡す。

● React で イベント実行関数(メソッド)に引数を渡す。

<a className='btn' onClick={myMethod.bind(this,'aiueo')}>ボタン</a>

または

<a className='btn' onClick={ (e) => {myMethod('aiueo',e)} }>ボタン</a>
// 短く書きたいなら
<a className='btn' onClick={ (e) => myMethod('aiueo',e) }>ボタン</a>

● React で イベント実行関数(メソッド)に引数を間違えて渡す。

<a className='btn' onClick={myMethod('uso800')}>ボタン</a>

このように間違った技術が存在するとどういう動作となるでしょうか?
答えはコンポーネント読み込み時に myMethod('uso800') が実行されてしまう
です。 充分気をつけましょう。

No.2137
01/14 15:44

edit

React で DOM オブジェクトを取得する

● React で DOM オブジェクトを取得する

アンチパターンですが、DOMを取得して attribute を変更してみます

import React from 'react'
const inputRefDrawerCheck = React.createRef();
let inputDOM = inputRefDrawerCheck.current
inputDOM.setAttribute('id', 'sample2');
<input type="checkbox" id="drawer-check" ref={inputRefDrawerCheck} />

IDが 「drawer-check」→「sample2」に変更されます。
このように React.createRef(); から DOM オブジェクトが取得できます。(通常あまりやりませんが)

No.2136
01/14 13:23

edit

Next.js で メソッドからページ移動する

● Next.js で メソッドからページ移動する

import { useRouter } from 'next/router'
  const router = useRouter()

  const navigatePage = () => {
    alert('トップページに戻ります);
    router.push('/');
  }
<a onClick={navigatePage}>戻る</a>
No.2135
01/14 13:02

edit

React で Firebase の Google Auth

● React で Firebase の Google Auth

.env.local


REACT_APP_FIREBASE_API_KEY="XXXXXXXXXXXXXXXXX"
REACT_APP_FIREBASE_AUTH_DOMAIN="XXXXXXXXXXXXXXXXX"
REACT_APP_FIREBASE_PROJECT_ID="XXXXXXXXXXXXXXXXX"
REACT_APP_FIREBASE_STORAGE_BUCKET="XXXXXXXXXXXXXXXXX"
REACT_APP_FIREBASE_MESSAGE_SENDER_ID="XXXXXXXXXXXXXXXXX"
REACT_APP_FIREBASE_APP_ID="XXXXXXXXXXXXXXXXX"
REACT_APP_FIREBASE_MEASUREMENT_ID="XXXXXXXXXXXXXXXXX"

src/FirebaseApp.tsx

import { initializeApp } from "firebase/app";
import { getAuth, GoogleAuthProvider } from "firebase/auth";

const firebaseConfig = {
  apiKey              : process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain          : process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  projectId           : process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket       : process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId   : process.env.REACT_APP_FIREBASE_MESSAGE_SENDER_ID,
  appId               : process.env.REACT_APP_FIREBASE_SENDER_ID,
};

initializeApp(firebaseConfig);

export const auth = getAuth();
export const provider = new GoogleAuthProvider();

src/App.tsx

import { auth, provider } from './FirebaseApp';
import { signInWithPopup, GoogleAuthProvider } from "firebase/auth";


function App() {

  const doLoginWithGoogle = () => {
    signInWithPopup(auth, provider)
      .then((result) => {
        // This gives you a Google Access Token. You can use it to access the Google API.
        const credential = GoogleAuthProvider.credentialFromResult(result);
        const token = credential?.accessToken;
        const user = result.user;

        console.log('● token');
        console.log(token);
        console.log('● user');
        console.log(user);
      }).catch((error) => {
        console.log('● error');
        console.log(error);
      });
  }

  return (
    <div>
      <h1>Googleログイン</h1>
      <button onClick={doLoginWithGoogle}>googleでログインする</button>
      {/* <button onClick={doLoginWithGoogle}>googleでログインする</button> */}
    </div>
  )

}

export default App;
No.2133
01/08 21:57

edit

useReducer を理解しやすくする

● (useReducerの理解 1). useReducer を理解する前に 配列の reduce メソッドを復習しておきましょう

https://ginpen.com/2018/12/23/array-reduce/

functional programming で map と共に必ず紹介されるメソッドです。
functional programmingの特徴をかなり簡単に紹介すると、
・「元データを更新せず元データを元に新しいデータを作成する(イミュータブル)」
・「関数に関数を渡す」
といったところです。
https://codewords.recurse.com/issues/one/an-introduction-to-functional-programming

・Array.reduce メソッド

const result = array.reduce((前回の値, 現在の値, 現在の値のインデックス, reduceによって操作している配列全て) => {
    return 次の値;
}, 初期値);
Array.reduce は 「単一の値を返します」

初期値について

・初期値を渡さないと、 インデックス=1 (2番目の値)から処理を始めます。
・初期値を渡すと、最初の値から、インデックス=0  から処理を始めます。

例1. (初期値なし)

const myArray = ['hoge', 'fuga', 'piyo'];

myArray.reduce((acc, cur, index, ar) => {
  console.log( `● acc : ${acc} / ● cur : ${cur}  / ● index : ${index}` );
  return 'xxx';
});

結果

● acc : hoge / ● cur : fuga  / ● index : 1 
● acc : xxx / ● cur : piyo  / ● index : 2 

例2. (初期値あり)初期値が acc にセットされるので配列の最初の値から処理されます

const all_result = myArray.reduce((acc, cur, index, ar) => {
  console.log( `● acc : ${acc} / ● cur : ${cur}  / ● index : ${index}` );

  const result = acc + '__' + cur;
  return result;
  
} , 'saisho');

結果

● acc : saisho / ● cur : hoge  / ● index : 0 
● acc : saisho__hoge / ● cur : fuga  / ● index : 1 
● acc : saisho__hoge__fuga / ● cur : piyo  / ● index : 2 

また最終結果 all_result

saisho__hoge__fuga__piyo 

になります。

● (useReducerの理解 2). useReducer を使ってみる

const [state, dispatch] = useReducer(reducer, initialState);

useReducerは以下の引数を受け取ります。

・引数

reducer : stateを更新するための関数。stateとactionを受け取って、新しいstateを返す。
(state, action 共に、数値や配列やオブジェクト、どのような値も受け取ることが可能です。)

initialState : stateの初期値

・戻り値

state : state(コンポーネントの状態)
dispatch : reducerを実行するための呼び出し関数

https://bit.ly/3n3ws5T

・useReducerは、ステートに依存するロジックをステートに非依存な関数オブジェクト(dispatch)で表現することができる点が本質である。
 このことはReact.memoによるパフォーマンス改善につながる。
・useReducerを活かすには、ステートを一つにまとめることで、ロジックをなるべくreducerに詰め込む。

● useReducer を使ってオブジェクトの値を更新したのに再描画が起きない場合

React は state の変更の有無の判定に Object.is() を使うとのことなので、現在の state を表す array を直接変更するのではなく別の array を新たに生成して setBookmarks() に渡せば OK です。
No.2131
09/07 16:17

edit

Next.js で 静的サイトとしてエクスポートする(next export)

● 1. まずビルドを実行してエラーがあるかどうかを確認する

npm run build

next バージョンの確認

npx next --version

● 2. サーバーサイドのページがある場合は取り除く

こちらのように /api/ 以下のファイルはサーバーサイドとなりますので取り除きます

Page                                       Size     First Load JS
├ λ /api/hello                             0 B            86.6 kB

● 3. next.config.js 設定の変更

module.exports = {
  reactStrictMode: true,
}

   ↓

module.exports = {
  reactStrictMode: true,
  trailingSlash: true,
}

● 4. エクスポートの実行

npm run build ; npx next export

エクスポートが成功すると out ディレクトリに htmlファイルが生成されるので表示して確認します。 php でサーバを起動する場合は

php -S 0.0.0.0:8000 -t out

http://localhost:8000/ へアクセスして確認します。

● Error: Image Optimization using Next.js' default loader is not compatible with next export. エラーの修正

・1. <Image>タグを戻す

<Image> タグを使用していると 静的サイトエクスポートできないのでエラーとなります。
代わりに <img>タグに戻しましょう

・2. next/image を 削除する

// import Image from 'next/image'

・3. eslint の設定を変更する

あわせて .eslintrc.json の設定も変更します

{
  "extends": "next/core-web-vitals"
}

  ↓

{
  "extends": "next/core-web-vitals",
  "rules": {
    "@next/next/no-img-element": "off"
  }
}
No.2127
12/31 23:58

edit

React で ページの先頭へスムーススクロール

● React で ページの先頭へスムーススクロール

npm install react-scroll
  const scrollToTop = () => {
    scroll.scrollToTop();
  };
<a onClick={scrollToTop}><div>戻る</div></a>
No.2126
12/30 23:20

edit

react swiper を使用する

● swiper の インストール

npm install swiper

pages/_app.js

import "swiper/swiper.scss";

components/SwiperComponent.jsx

import * as React from 'react';
import SwiperCore, { Pagination, Autoplay } from "swiper";
import { Swiper, SwiperSlide } from "swiper/react";

SwiperCore.use([Pagination, Autoplay]);

// interface Props {
//   imageData: Array<string>;
//   isAuto: boolean;
//   clickable: boolean
// }

const SwiperComponent = (props) => {
  return (
    <div style={{ zIndex: -9999 }}>
      <Swiper pagination={{ clickable: props.clickable }} autoplay={props.isAuto}>
        {props.imageData.map((imageSrc, i) => {
          return (
            <SwiperSlide key={`${imageSrc}${i}`}>
              <div className="top_banner_background_image" style={{ backgroundImage: `url(${imageSrc})` }}></div>
            </SwiperSlide>
          );
        })}
      </Swiper>
    </div>
  );
};
export default SwiperComponent;

表示させる

          <SwiperComponent
            imageData={[
              "img/top_banner_background_02.png",
              "img/top_banner_background_01.png",
            ]}
            isAuto={true} clickable={false}
          />
No.2125
12/29 15:27

edit

Next.js で 独自の 404エラーページを作成する

● Next.js で 独自の 404エラーページを作成する

pages/404.js を以下のような内容で作成すると独自の404エラーページが表示されます

pages/404.js

import Link from 'next/link'

export default function Custom404() {
  return (
    <div className="container">
      <h1>404 - お探しのページは見つかりませんでした</h1>
      <p>
        こちらからサイトのトップに戻ってみてください<br />
        <Link href="/"><a>サイトトップに戻る</a></Link>
      </p>
    </div>
  )
}
No.2122
12/27 11:26

edit

Next.js サイトに読み込みローディングバー(プログレスバー)を表示させる

npm install nextjs-progressbar

または

yarn add nextjs-progressbar

/pages/_app.js

import NextNprogress from 'nextjs-progressbar'
      <Component {...pageProps} />

       ↓ NextNprogress を追加

      <NextNprogress
        color="#3b7d6b"
        startPosition={0.3}
        stopDelayMs={200}
        height={1}
        showOnShallow={true}
      />

      <Component {...pageProps} />
No.2121
12/26 14:01

edit

Next.js で scss (sass) を使用する(サイト全体css / ページ単位css)

● Next.js で サイト全体に適用するグローバルな scss (sass) を使用する

・ 1. sassのインストール

npm install sass --save-dev

・ 2. scssファイル群の用意

cd styles
mkdir scss
touch scss/style.scss

styles/scss/style.scss をトップのscssとします

・ 3. pages/_app.js から読み込み

style.scss を _app.js から読み込みます

pages/_app.js

_app.js からの読み込みを style.scss に変更します

import '../styles/globals.css'

  ↓

import '../styles/scss/style.scss'

なお、scss内で画像を使用するときは絶対パスを記述して、画像ファイルは /public/ 以下に置きます

● グローバルなCSSを各ページに記述する

次のようにstyleタグに jsx global を付けることで直接 CSS を記述するのと同じように記述できます

<style jsx global>{`
  .my-class {
    background-color: green;
  }
`}</style>

● Next.js で ページ固有の scss (sass) を使用する

拡張子 「 .module.scss 」なファイルを作成する。 バスはどこでも自由ですが拡張子は必ず module.scss または module.css とします
拡張子を module.scss とした場合は自動的にscss 記法が使用できます

例 : styles/components/404.module.scss

.container_404 {
  border: 1px solid red;
  margin: 50px 0;
  h2 {
    color: blue;
  }
}

表示するコンポーネントから読み込む

import css from "../styles/components/404.module.scss"

以下のように記述して読み込みます

<div className={css.container_404}>
No.2120
06/01 11:57

edit

Next. js で共通のレイアウトページを作成する

● Next. js で共通のレイアウトページを作成する

1 . 共通レイアウト Layout.js の作成

メインの children のところが各ページ内容に置き換わります

mkdir components
vi components/Layout.js

components/Layout.jsx レイアウトを複数作る場合は components/layouts/default.jsx

import Image from 'next/image'
import Script from 'next/script'
import Link from 'next/link'
import Head from 'next/head'

export default function Layout({ children }) {

  return (
    <>
      <Head>
      <meta charSet="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <link rel="stylesheet" href="./css/style.css" />
      <title>サイトのタイトル</title>
      <meta name="description" content="サイトのデスクリプション" />
      </Head>

      {/* ======================== ヘッダ ======================== */}
      <header className="header">
      </header>
      {/* ======================== / ヘッダ ======================== */}


      {/* ======================== メイン ======================== */}
      <main>{children}</main>
      {/* ======================== /メイン ======================== */}


      {/* ======================== フッター ======================== */}
      <footer className="footer"></footer>
      {/* ======================== / フッター ======================== */}
    </>
  )
}

2 . レイアウトを全ページへ適用

pages/_app.js

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

       ↓  このように修正

import Layout from '../components/Layout'
function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}
No.2119
07/19 15:31

edit

react-datepicker を使用する

● react-datepicker を使用する

https://github.com/Hacker0x01/react-datepicker

yarn add react-datepicker
yarn add @types/react-datepicker
import React, { useState } from 'react';

// datepicker
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
  // datetime-picker
  const [startDate, setStartDate] = useState(new Date());
<DatePicker selected={startDate} onChange={(date:any) => setStartDate(date)} />

オプション

https://qiita.com/k_hoso/items/1bacfb38e449552f2c71

No.2115
01/20 18:03

edit

JSONからTypeScriptの type を作成する

● JSONからTypeScriptの type を作成する

TypeScript で JSON オブジェクトに型情報を付加する|まくろぐ

● JSONからTypeScriptの interface を作成する

https://app.quicktype.io/?l=ts

https://jvilk.com/MakeTypes/

● JSONからTypeScriptの type を作成する(VS Code)

https://marketplace.visualstudio.com/items?itemName=quicktype.quicktype

No.2114
12/16 18:39

edit

React のグリッド、データテーブル

● React のグリッド、データテーブル

● jbetancur / react-data-table-component

デモ : https://codesandbox.io/s/fz295
ドキュメント : https://react-data-table-component.netlify.app/?path=/story/getting-started-intro--page

インストール

yarn add react-data-table-component

使い方


import DataTable from 'react-data-table-component';

const columns = [
    {
        name: 'Title',
        selector: row => row.title,
    },
    {
        name: 'Year',
        selector: row => row.year,
    },
];

const data = [
    {
        id: 1,
        title: 'Beetlejuice',
        year: '1988',
    },
    {
        id: 2,
        title: 'Ghostbusters',
        year: '1984',
    },
]

function MyComponent() {
    return (
        <DataTable
            columns={columns}
            data={data}
        />
    );
};

画像を表示させる

const columns = [
    {
        name: 'Avator',
        selector: (row: any) => <img src={row.avartar} width="36" alt="avator" /> ,
    },
];

● mbrn / material-table

デモ : https://material-table.com/#/

インストール

yarn add material-table @material-ui/core

使い方

import MaterialTable from "material-table";
  return (
    <Layout>

      <main>
        <MaterialTable
          columns={[
            { title: "名前", field: "name" },
            { title: "かな", field: "surname" },
            { title: "birthYear", field: "birthYear", type: "numeric" },
            {
              title: "birthCity",
              field: "birthCity",
              lookup: { 34: "Tokyo", 63: "Osaka" },
            },
          ]}

          options={{
            search: true
          }}

          data={[
            {
              name: "山田",
              surname: "やまだ",
              birthYear: 1987,
              birthCity: 63,
            },
            {
              name: "大橋",
              surname: "おおはし",
              birthYear: 1990,
              birthCity: 34,
            },
            {
              name: "中村",
              surname: "なかむら",
              birthYear: 1990,
              birthCity: 34,
            },
          ]}
          title="Demo Title"
        />
      </main>

    </Layout>
  );
No.2111
12/15 09:23

edit

React Bootstrap を TypeScript で使用する

● React Bootstrap を TypeScript で使用する

yarn add bootstrap 
yarn add @types/bootstrap

yarn add react-bootstrap 
yarn add @types/react-bootstrap

index.tsx

import 'bootstrap/dist/css/bootstrap.min.css';
No.2110
12/09 21:48

edit

No.2108
12/07 09:24

edit

react-router のミニマルな形 (TypeScript)

● react-router のミニマルな形 (TypeScript)

● react アプリの作成

npx create-react-app my-router-ts-app --template typescript
cd my-router-ts-app

● react-router の追加

yarn add react-router-dom
yarn add @types/react-router-dom

index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter, Routes, Route } from "react-router-dom";
import About from "./routes/About";
import Contact from "./routes/Contact";
import Post from "./routes/Post";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<App />} />
        <Route path="about" element={<About />} />
        <Route path="contact" element={<Contact />} />
        <Route path="/post/:id" element={<Post />} />
      </Routes>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

App.tsx

import { Link } from "react-router-dom";
import './App.css';

function App() {

  return (
    <div>
      <h1>My-App Hello!</h1>
      <nav>
        <Link to="/about">About</Link>{" "}|{" "}
        {/* <Link to="/contact">Contact</Link>{" "}|{" "}
        <Link to="/post/1">Post1</Link>{" "}|{" "}
        <Link to="/post/2">Post2</Link>{" "}|{" "} */}
      </nav>
    </div>
  );

}

export default App;

routes/About.tsx

import { Link } from "react-router-dom";

export default function About() {
  return (
    <main style={{ padding: "1rem 0" }}>
      <h2>About</h2>
      <p>テストテキストテストテキストテストテキスト</p>
      <Link to="/">戻る</Link>
    </main>
  );
}

routes/Contact.tsx

import { Link } from "react-router-dom";

export default function Contact() {
  return (
    <main style={{ padding: "1rem 0" }}>
      <h2>Contact</h2>
      <p>コンタクトテキストコンタクトテキストコンタクトテキストコンタクトテキストコンタクトテキストコンタクトテキスト</p>
      <Link to="/">戻る</Link>
    </main>
  );
}

routes/Post.tsx

import { Link, useParams } from "react-router-dom";

export default function Post() {
  let params = useParams();

  return (
    <main style={{ padding: "1rem 0" }}>
      <h2>Post (ID : {params.id})</h2>
      <p>テストテキストテストテキストテストテキスト</p>
      <Link to="/">戻る</Link>
    </main>
  );
}

これで、次のURLへアクセスできます。

http://localhost:3000/about
http://localhost:3000/contact
http://localhost:3000/post/1
http://localhost:3000/post/2

● 全ページ共通のレイアウトを使用する(Layout コンポーネント)

https://maku.blog/p/dxamw8i/

● 戻るボタン( history.back(); )

import { useNavigate } from "react-router-dom";
const navigate = useNavigate();

<button type='button' onClick={() =>{ navigate(-1) }}>戻る</button>

● 遅延ローディング

https://zukucode.com/2021/06/react-router-razy.html

● Switch-Router

https://dev-yakuza.posstree.com/react/create-react-app/react-router/

● react-router v6 では Switch の代わりに Routes を使いましょう

https://stackoverflow.com/questions/69843615/switch-is-not-exported-from-react-router-dom

● react-router でメソッドを使って画面遷移する

import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
navigate('/home');

● 認証を伴ったルーティング

function PrivateRoute ({component: Component, authed, ...rest}) {
  return (
    <Route
      {...rest}
      render={(props) => authed === true
        ? <Component {...props} />
        : <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
    />
  )
}
<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />
No.2107
01/21 13:40

edit

TypeScript

Reactの関数コンポーネントで親コンポーネントから子コンポーネントの関数を実行する

● Reactの関数コンポーネントで親コンポーネントから子コンポーネントの関数を実行する

forwardRef , useImperativeHandle を使います

import React, { useImperativeHandle, forwardRef, useRef } from "react";

https://ja.reactjs.org/docs/hooks-reference.html#useimperativehandle

useImperativeHandle は forwardRef と組み合わせて使います。
useImperativeHandle は コンポーネントにメソッドを生やす Hooks です

● 子コンポーネントで関数 showAlert() を登録

1. メソッドの追加

import { useImperativeHandle, forwardRef } from "react";
    useImperativeHandle(
        ref,
        () => ({
            showAlert() {
                alert("Child Function Called")
            }
        }),
    )

2. refを受け取れるようにする

const MyChildComponent = (props) => {

      ↓

const MyChildComponent = (props, ref) => {
export default MyParentComponent;

      ↓

export default forwardRef(MyParentComponent);

● 親コンポーネントで ref を渡す

import { useRef } from "react";
const childRef = useRef();
<MyChildComponent
    ref={childRef}
/>

後は親コンポーネントの好きなところから関数を呼ぶことができます

<button type="button"
    onClick={ () => { childRef.current.showAlert() }}
>

関数コンポーネントはクラスとどう違うのか? — Overreacted

● useImperativeHandle を async で使用したい

子コンポーネントのuseImperativeHandle を次のようにします。

    useImperativeHandle(
        ref,
        () => (
            {
                showAlert:  async function(){ await showAlert(); } ,
            }
        )
    )

また showAlert() 関数にも async をつけておきます

● TypeScript で useImperativeHandle を使用する例

ForwardRefRenderFunction を使います

import React, { useImperativeHandle, forwardRef } from "react";

interface Props {
  text: string;
}

interface Handler {
  showAlert(): void;
}

const ChildBase: React.ForwardRefRenderFunction<Handler, Props> = (
  props,
  ref
) => {
  // 公開する関数
  useImperativeHandle(ref, () => ({
    showAlert() {
      alert(props.text);
    },
  }));

  return <span>{props.text}</span>;
};

const VFCChild = forwardRef(ChildBase);
export default VFCChild;
No.2093
03/10 12:11

edit

Next.js の ビルド先ディレクトリ「.next」を変更する / ビルド先ディレクトリをシェルから変更する

● Next.js の ビルド先ディレクトリ「.next」を変更する

next.config.js

module.exports = {
  distDir: '.next',
}

● Next.js の ビルド先ディレクトリをシェルから変更する

シェルから環境設定をセットすると process.env で読み取ることができます

シェルコマンド

export NEXTJS_BUILD_DIST=.tmp_next

next.config.js

( NEXTJS_BUILD_DISTが設定してある場合はそのディレクトリをセット。 設定されてない場合はデフォルトの .next をセット )

module.exports = {
  distDir: process.env.NEXTJS_BUILD_DIST ? process.env.NEXTJS_BUILD_DIST : '.next',
}

シェルから環境変数を削除するには次のようにします

export -n NEXTJS_BUILD_DIST
No.2090
10/28 09:15

edit

Next.js の 環境設定ファイル(.env)と サーバーサイド・クライアントサイドの判別

● Next.js の 環境設定ファイル(.env)

使うのは以下の2ファイルに限定すると良いでしょう。

.env.development : 開発用ファイル

NODE_ENV が development ( = npm run dev )の時に読み込まれる。

.env.production : 本番用ファイル

NODE_ENV が production ( = npm run build && npm run start )の時に読み込まれる。

● .envファイルの書き方と呼び出し方

書き方(サーバーサイド)
( .env.development または .env.production)

HOGE=mySettingValue

呼び出し方(サーバーサイド)
( xxx.js や xxx.ts ファイル )

console.log( process.env.HOGE );

書き方(フロントエンド)
( .env.development または .env.production)

NEXT_PUBLIC_HOGE=mySettingValue

呼び出し方(フロントエンド)
( xxx.js や xxx.ts ファイル )

console.log( process.env.HOGE );
alert( process.env.HOGE );

● 現在「開発用」か「本番用」かどちらが読み込まれているのかをチェックする

NODE_ENV で判断すると良いでしょう

console.log( process.env.NODE_ENV );

● 現在「サーバーサイド」か「フロントエンド」かを判別する

typeof window === "undefined"

windowオブジェクトがないのがサーバーサイド
windowオブジェクトがあるのがフロントエンド
です。

デバッグ用にサーバーがクライアントを返したい場合はメソッドにしておいても良いかもです

export default function ServerOfClient() {
  return (typeof window === "undefined") ? 'Server' : 'Client';
}

Booleanの場合はそのまま利用しても良いですし以下のようにしても良いです

const isProduction = process.env.NODE_ENV === "production";

● envの値を確認するサンプル

  console.error(`● process.env.APOLLO_FETCH_POLICY は (${process.env.APOLLO_FETCH_POLICY}) / NODE_ENVは(${process.env.NODE_ENV}) / Server or Client は(${ServerOfClient()})`);

結果例

● process.env.APOLLO_FETCH_POLICY は (network-only-S) / NODE_ENVは(development) / Server or Client は(Server)

● .envファイルを書き換えたのに値が更新されない時は?

control + c でいちどプロセスを終了してから再度起動します
npm run dev
No.2088
10/28 09:01

edit

Next.js を nginx にデプロイする

● Next.js を nginx にデプロイする

Next.js とphpを使用できるように下記の仕様とします

「/php/<ファイル名>でアクセスした場合」→  /home/YOUR/SERVER/PATH/DocumentRoot/php/<ファイル名>を返す
「/でアクセスした場合」→  next.jsを返す  
	location /php/ {
	    alias  /home/YOUR/SERVER/PATH/DocumentRoot/php/;
	    index  index.html index.htm;
	}

	location / {
      	# Next.js Server
      	proxy_pass http://localhost:3000;
	}
No.2086
10/25 13:50

edit

Next.js で 任意のdivの高さを 100% にする ( height:100% )

● Next.js で 任意のdivの高さを 100% にする ( height:100% )

const FullHeightPage = () => (
  <div>
    Hello World!
    <style global jsx>{`
      html,
      body,
      body > div:first-child,
      div#__next,
      div#__next > div {
        height: 100%;
      }
    `}</style>
  </div>
)
No.2085
10/22 10:55

edit

Next.js で GraphQL クライアント Apolloクライアントを使用する

● 1. サンプルの「GraphQLクエリ」と「結果」を確認しておく

URL
http://snowtooth.moonhighway.com/

エンドポイントURL

https://countries.trevorblades.com

GraphQLクエリ

query {
  countries{
    code
    name
    emoji
  }
}

こちらの「エンドポイントURL」「GraphQLクエリ」を入力して ▶︎のボタンをクリックするとGraphQLクエリーが実行されます


● 2. apolloクライアントのインストール

開発しているアプリケーションの一番上の階層からnpmでインストールします

npm install @apollo/client graphql

● 3. apolloクライアントクラスの作成

apollo-client.js

import { ApolloClient, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
  uri: 'https://countries.trevorblades.com',            // 今は直接設定していますが .env から参照しましょう。
  cache: new InMemoryCache(),
});

export default client;

● 4. GraphQLから取り込んだデータを表示したいコンポーネントで次のように追加する

/pages/index.js

import { gql } from "@apollo/client";
import client from "../apollo-client";

SSG (Static Site Generation)用の getStaticProps() 関数を作成する
/pages/index.js

// getStaticProps ( for SSG (Static Site Generation) )
//   getStaticPathsは(本番環境)ビルド時に実行されます / (開発環境)リクエスト毎に実行されます
export async function getStaticProps() {
  const { data } = await client.query({
    query: gql`
query {
  countries{
    code
    name
    emoji
  }
}
    `,
  });
  return {
    props: {
      countries: data.countries.slice(0, 4),
    },
 };
}

/pages/index.js の JSXで表示する

export default function Home({ countries }) {
      {countries.map((v) => (
        <div key={v.code} style={{ display:'flex' }}>
          <h1 style={{ lineHeight:'100px' }}>{v.name}</h1>
          <div style={{ fontSize:'100px' }}>
            {v.emoji}
          </div>
        </div>
      ))}

これで該当ページを表示すると次のような表示となります

以上です。


● getStaticProps , getStaticPaths , getServerSideProps について

https://bit.ly/3oCOn4L

getStaticProps(静的生成): ビルド時にデータ取得する
getStaticPaths(静的生成): データに基づきプリレンダリングする動的ルートを特定する
getServerSideProps(サーバーサイドレンダリング): リクエストごとにデータを取得する


● VS Code の拡張機能「Apollo GraphQL」のインストールして開発速度をアップさせる

https://marketplace.visualstudio.com/items?itemName=apollographql.vscode-apollo

[GraphQL] TypeScript+VSCode+Apolloで最高のDXを手に入れよう | DevelopersIO


● GraphQL Playground for Chrome

https://chrome.google.com/webstore/detail/graphql-playground-for-ch/kjhjcgclphafojaeeickcokfbhlegecd/related?hl=ja


● Apollo Client Devtools for Chrome

https://chrome.google.com/webstore/detail/apollo-client-devtools/jdkknkkbebbapilgoeccciglkfbmbnfm/related

Google Chromeの拡張機能 Apollo Client Devtools でできること(引用 : https://bit.ly/3pmP4je

– GraphiQLをその場で実行する(本来であればAPIサーバと別のポートでGraphiQLサーバを立ち上げて、ブラウザでそこにアクセスして利用します)
– JavaScriptから発行されたクエリのログの確認、クエリの編集・再発行
– apollo-link-stateでキャッシュに書き込んだ値の確認(apolloはクエリのレスポンスを勝手にキャッシュしてくれるのですが、その内容も確認できます)

初めて Apollo Client を使うことになったらキャッシュについて知るべきこと - WASD TECH BLOG

・取得するフィールドに id は必ず含める
・更新処理のときは Mutation のレスポンスでオブジェクトのキャッシュを更新する
・作成、削除処理のときは refetchQueries などを使い配列のキャッシュを更新する
・画面表示のたびに最新のデータを表示したければ fetchPolicy: "cache-and-network" を使う

fetchPolicy の種類

https://www.apollographql.com/docs/react/data/queries/

Name Description
cache-first

Apollo Client first executes the query against the cache. If all requested data is present in the cache, that data is returned. Otherwise, Apollo Client executes the query against your GraphQL server and returns that data after caching it.

Prioritizes minimizing the number of network requests sent by your application.

This is the default fetch policy.

cache-only

Apollo Client executes the query only against the cache. It never queries your server in this case.

A cache-only query throws an error if the cache does not contain data for all requested fields.

cache-and-network

Apollo Client executes the full query against both the cache and your GraphQL server. The query automatically updates if the result of the server-side query modifies cached fields.

Provides a fast response while also helping to keep cached data consistent with server data.

network-only

Apollo Client executes the full query against your GraphQL server, without first checking the cache. The query's result is stored in the cache.

Prioritizes consistency with server data, but can't provide a near-instantaneous response when cached data is available.

no-cache

Similar to network-only, except the query's result is not stored in the cache.

standby

Uses the same logic as cache-first, except this query does not automatically update when underlying field values change. You can still manually update this query with refetch and updateQueries.

参考 : ApolloのFetchPoliciesを理解する - Qiita

添付ファイル1
No.2084
12/09 16:10

edit

添付ファイル

ReactのJSX内で string の style を扱う

● ReactのJSX内で string の style を扱う

helpers/stringHelper.js

export const getStyleObjectFromString = str => {
  if (!str) { return {}; }
  const style = {};
  str.split(";").forEach(el => {
    const [property, value] = el.split(":");
    if (!property) return;

    const formattedProperty = formatStringToCamelCase(property.trim());
    style[formattedProperty] = value.trim();
  });
  return style;
};

コンポーネントで使用する

import { getStyleObjectFromString } from "helpers/stringHelper";
      return (
          <div
            style={getStyleObjectFromString("color:red; margin:20px; font-weight:bold;")}
          />
      )
No.2083
10/26 13:14

edit

Next.js で YAMLファイルを使用する

● Next.js で YAMLファイルを使用する

js-yaml-loaderのインストール

npm install js-yaml-loader

next.config.js に以下を設定

module.exports = {
 webpack: function (config) {
   config.module.rules.push(
     {
       test: /\.ya?ml$/,
       use: 'js-yaml-loader',
     },
   )
   return config
 }
}

使用する

import useryml from 'user.yml';
  console.log( '● useryml' );
  console.log( useryml );
No.2082
10/19 22:23

edit

VS Code の「F5」キーで Next.js のデバッグを行う

● VS Code の「F5」キーで Next.js のデバッグを行う

Next.JSのVS Code を使ったデバッグは次の2ステップのみでとても簡単に行うことができます

・1. ローカルサーバーを起動する

VS Codeの
「エクスプローラー」→「NPMスクリプト」 →「dev」の横の ▶︎ をクリックしてローカルサーバーを起動する。

・2. 「F5」をクリックしてデバッガーを起動する

最初に launch.json があるかどうかのチェックが行われ、まだない場合は作成を促されるので作成します。 launch.json を以下の内容で保存します

{
    "version": "0.2.0",
    "configurations": [
      {
        "name": "Next.js: debug server-side",
        "type": "node-terminal",
        "request": "launch",
        "command": "npm run dev"
      },
      {
        "name": "Next.js: debug client-side",
        "type": "pwa-chrome",
        "request": "launch",
        "url": "http://localhost:3000"
      },
      {
        "name": "Next.js: debug full stack",
        "type": "node-terminal",
        "request": "launch",
        "command": "npm run dev",
        "console": "integratedTerminal",
        "serverReadyAction": {
          "pattern": "started server on .+, url: (https?://.+)",
          "uriFormat": "%s",
          "action": "debugWithChrome"
        }
      }
    ]
  }

この設定ファイルでは

Next.js: debug client-side
Next.js: debug server-side
Next.js: debug full stack

と言う3つのデバッグを定義していますので「Next.js: debug client-side」を選択肢で起動します。(クライアントアプリをデバッグしたい場合)

これだけでokです。
後は「F5」でデバッグを起動「shift+F5」デバッグを終了 のお決まりのキーボード操作をしてください

No.2081
11/22 09:53

edit

Next.js で import の相対パスのルートディレクトリを変更(固定)してルートからの絶対パスで記述する

● Next.js で import の相対パスのルートディレクトリを変更(固定)してルートからの絶対パスで記述する

jsconfig.json (プロジェクトトップディレクトリにこのファイルがない場合は新規で作成します)

{
  "compilerOptions": {
    // import時のパスのルートを設定
    "baseUrl": "."
  },
  "include": ["**/*.js", "**/*.jsx"]
}

VS Codeの「F12」てのファイル移動も有効です

No.2080
11/02 09:04

edit

React Hook Formでフォームを作成する ( + Yup )

● 1. React Hook Formのインストール

npm install react-hook-form
または
yarn add  react-hook-form

● 2. React Hook Formを使用する

import { useForm } from 'react-hook-form';
    // React Hook Form
    const {
        register,
        handleSubmit,
        formState: { errors },
    } = useForm();

  const onSubmit = (data:any) => {alert('form送信されました'); console.log(data);}

useForm() には様々なオプションを渡すことができます

https://react-hook-form.com/api/useform/

useForm({
  mode: 'onSubmit',  // onChange | onBlur | onSubmit | onTouched | all = 'onSubmit'
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstError",
  shouldFocusError: true,
  shouldUnregister: false,
  shouldUseNativeValidation: false,
  delayError: undefined
})

useForm() して返ってくる handleSubmitに (バリデーションOKの時の関数,バリデーションNGの時の関数) を渡します

handleSubmit(SubmitHandler, SubmitErrorHandler)

例1: 会社名を入力するフォームに「入力必須」「入力文字数4文字以上」のバリデーションを設定します

return (
    <form onSubmit={handleSubmit(onSubmit)}>
        <input {...register('company', { required: true, minLength: 4 })} placeholder="株式会社○○" />
        {errors.company?.type === "required" && <div className="err_message" id="company_err">会社名は必須です</div>}
        {errors.company?.type === "minLength" && <div className="err_message" id="company_err">会社名は4文字以上を入力してください</div>}

        <input type="submit" />
    </form>
);

例2: 次のように「チェック内容」と「エラーメッセージ」をまとめて記述することもできます

    <input type="email" placeholder="user@server.xxx"
        {...register("email", {
            required: "入力必須です",
            pattern: {
            value: /\S+@\S+\.\S+/,
            message: "メールアドレスが不完全です"
            }
        })}
    />
    {errors.email && <div className="err_message">{errors.email.message}</div>}

● 3. フォーム入力値のバリデーションの記述と種類

https://react-hook-form.com/jp/api#register

● 4. React Hook Form + Yup を使用する

バリデーションの記述をYupでまとめるには次のように記述します

npm install @hookform/resolvers yup
または
yarn add @hookform/resolvers yup
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';
  const validationSchema = Yup.object().shape({
    firstName: Yup.string()
      .required('First Name は必須です'),
    lastName: Yup.string()
      .required('Last Name は必須です'),
  });

  const formOptions = { resolver: yupResolver(validationSchema) };

  const { register, handleSubmit, reset, formState } = useForm(formOptions);
  const { errors } = formState;
<form onSubmit={handleSubmit(onSubmit)}>
  <input type="text" {...register('firstName')} className={`form-control ${errors.firstName ? 'is-invalid' : ''}`} />
  <div className="invalid-feedback">{errors.firstName?.message}</div>
</form>

name="firstName" は記述しなくてokです。

● SWR(Axios)で非同期で取得したデータをフォームのデフォルト値として流し込む

外部からフォームの値を変更するには以下の2つの方法を使用します。
( なお、useState は使用できません )

・A. Resetメソッドを使う方法

次の二箇所にデータを流し込む命令をセットします

// ● SWR
const fetcher = (url: string) => axios(url)
  .then((res) => {
    reset(res.data);  // 1. axiosでデータ取得時にデータをフォームに反映(Re-render)
    return res.data
  });
const { data, error, mutate } = useSWR(`http://localhost:8000/api/news/1`, fetcher);

// ● useForm
const formOptions = {
  defaultValues: data,  // 2. SWRのキャッシュがデータをすでに取得している場合はキャッシュからフォームに反映
};
const { control, register, handleSubmit, reset, formState: { errors } } = useForm(formOptions);

・B. setValueメソッドを使う方法

  // ● React Hook Form
  const { register, handleSubmit, setValue, formState } = useForm(formOptions);

setValueを使用します

 // ● SWR
  const fetcher = (url: string) => axios(url)
    .then((res) => {
      // 最初に1度だけフォームに値をセット
      const data = res.data;
      Object.keys(data).forEach(function (k) {
          setValue(k, data[k]);
      });

      return res.data
    });

  const { data, error, mutate } = useSWR(`http://localhost:8000/api/news/${params.id}`, fetcher);
  // swrによるfetchエラー時
  if (error) return <div>failed to load</div>

● setValue() がうまく動作しない場合は

{...register('hoge')} の記述が抜けている可能性がありますチェックしましょう。

<input type="text" id="hoge" {...register('hoge')} />

● Yupでカスタムバリデーション

let customValidation = yup.object().shape({
  beverage: yup
    .string()
    .test("is-tea", "${path} is not tea", value => value === "tea")
});

● useFormState

https://react-hook-form.com/api/useformstate
https://qiita.com/bluebill1049/items/f838bae7f3ed29e81fff

● @hookform/devtools

yarn add @hookform/devtools
import { DevTool } from '@hookform/devtools';

jsx

<DevTool control={control} placement="top-left" />

<form onSubmit={handleSubmit(onSubmit, onError)}>
.............
</form>

● その他参考になるサイト

公式サンプル
https://github.com/react-hook-form/react-hook-form/tree/master/examples

https://bit.ly/3oSjgm9

React Hook Form 7 - Form Validation Example | Jason Watmore's Blog

jsonによるスキーマ定義をyupに変換する

https://github.com/kristianmandrup/schema-to-yup

yarn add schema-to-yup

● フォームをメソッドで submit する

onClick = { () => {
    handleSubmit(onSubmit)()
}}
````

No.2068
09/02 16:39

edit

React で登場する ... (3点リーダー)(ドット3つ)の使い方

● React で登場する ... (3点リーダー)(ドット3つ)(JSX Spread Attributes)の使い方

これは Spread Attributes(スプレッド構文) といって ES6 Javascript での記法です。Spread syntax (...)
意味としては以下のコードと同じ意味合いとなります。

● Spread syntax (...) の例

const obj = {
  id:1,
  name: 'John',
  email: 'john@test.local',
}
console.log( obj );
console.log( {...obj} );

結果

{ id: 1, name: 'John', email: 'john@test.local' }
{ id: 1, name: 'John', email: 'john@test.local' }
const props = { foo: "foo", bar: "bar" };
render() {
  return <Child foo={props.foo} bar={props.bar} />
}

       ↓

const props = { foo: "foo", bar: "bar" };
render() {
  return <Child {...props} />
}

● なぜ JSX Spread Attributes を利用するのか?

子コンポーネントに渡したい値に変更があった場合に変更箇所が少なくて済みます。

props に hogee を追加する場合でも提示する箇所のみの変更でokです。

const props = { foo: "foo", bar: "bar", hoge:"hoge" };
render() {
  return <Child {...props} />
}
No.2067
07/01 16:02

edit

Next.js のコンポーネントが 「サーバー」/「クライアント」どちらで動いているかを調べる

● Next.js のコンポーネントが 「サーバー」/「クライアント」どちらで動いているかを調べる

引用 : https://maku.blog/p/m7is4dn/

windowオブジェクトはクライアント(WEBブラウザ)で動作している時のみ存在するのでこれを調べると言う方法です

const isClient = () => typeof window !== 'undefined'
// const isServer = () => typeof window === 'undefined'
const HelloPage: FC = () => {
  if (isClient()) {
    console.log('これはクライアントサイド JS として実行されているよ!')
    console.log(window.location)
    alert('だから alert も使えるよ!')
  }
  return <h1>Hello</h1>
}
No.2066
07/12 09:01

edit

Next.jsで改行コードを改行タグに変換(nl2br)する

● Next.jsで改行コードを改行タグに変換(nl2br)する

npm install react-nl2br -S
const nl2br = require('react-nl2br');

jsxで以下のように記述します
my_textに入っている文字列の改行コードをbrタグに変換します

return(
    <div>
        { nl2br(my_text) }
    </div>
);
No.2065
10/08 09:33

edit

Next.js で日付をフォーマットする

● moment.js の次世代のパッケージ date-fns をインストールする

次世代のパッケージ date-fns をインストールします

npm install date-fns

● 使用する

import { parseISO, format } from 'date-fns'
import ja from 'date-fns/locale/ja'
console.log( format(new Date(), 'yyyy-MM-dd (EEEE) HH:mm:ss', {locale:ja}) );

結果

2021-10-08 (金曜日) 10:09:21

● jsxで使用する

// date-fns
function Date({ dateString }) {
 return <time dateTime={dateString}>{format(parseISO(dateString), 'yyyy.MM.dd (EEEE)', {locale:ja} )}</time>
};
<Date dateString={my_date} />
No.2064
11/01 09:59

edit

Next.js で font awesomeなどのアイコンフォントを使用する

● React Icons インストール

npm install react-icons --save

● コンポーネント内で呼び出して使用する

Mycompoenent.js

import { FaGithub } from "react-icons/fa"

function MyComponent() {
  return (
    <div>
        <FaGithub />
    </div>
  )
}

● 使用したいアイコンを検索する

https://react-icons.github.io/react-icons/search?q=cart

No.2063
10/07 15:04

edit

Next.js で Axios , fetcher の非同期通信を便利にする SWR を使用する

● SWR を使用する

・1. swrとaxiosのインストール

yarn add swr
yarn add axios @types/axios

または

npm i swr -S

・2. fetcherの定義

import useSWR from 'swr'

fetchを使用する場合 の fetcher の定義方法

const fetcher = (url) => fetch( url )
  .then(res => res.json());

axiosを使用する場合 の fetcher の定義方法**

const fetcher = (url: string) => axios(url)
  .then((res) => {
    return res.data
  });

・3. NewsIndexコンポーネントの作成

function NewsIndex() {
  const { data, error } = useSWR('https://YOUR/SERVER/PATH/api/news/', fetcher)
    // swrによるfetchエラー時
  if (error) return <div>failed to load</div>
  // swrによるfetchロード中
  if (!data) return <div>now loading...</div>
  return (
    <ul>
    {data.map((v) => 
      <li key={v.id}>{v.id} : {v.name}</li>
    )}
  </ul>
  );
}

・4. NewsIndexコンポーネントの呼び出し

<div>
  <NewsIndex/>
</div>

これで URL「https://YOUR/SERVER/PATH/api/news/」が返す json (中身はコレクション)の id と name を一覧で表示します。

● SWRの自動再検証(自動再読込)について

デフォルトでは自動的に revalidate(再読込)処理が入っています。

「ページがフォーカスした時」 「タブを切り替えた時」 「設定したポーリング間隔」で再読み込みされます。

100msecとフォーカス時に自動再読込

  const options = {
    revalidateOnFocus,
    refreshInterval: 100,
  };
  const { data, error, mutate } = useSWR(url, fetcher, options);
revalidateIfStale = true: automatic revalidation on mount even if there is stale data (details)
revalidateOnMount: enable or disable automatic revalidation when component is mounted
revalidateOnFocus = true: automatically revalidate when window gets focused (details)
revalidateOnReconnect = true: automatically revalidate when the browser regains a network

公式 : https://swr.vercel.app/docs/options
参考 : https://bit.ly/3Aicc4w

● fetch と axios どちらを使用するか?

axios

const options = {
  url: 'http://localhost/test.htm',
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json;charset=UTF-8'
  },
  data: {
    a: 10,
    b: 20
  }
};

axios(options)
  .then(response => {
    console.log(response.status);
  });

fetch

const url = 'http://localhost/test.htm';
const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json;charset=UTF-8'
  },
  body: JSON.stringify({
    a: 10,
    b: 20
  })
};

fetch(url, options)
  .then(response => {
    console.log(response.status);
  });

● データのPOST(送信)


● 参考

zeit製のswrがすごい - Qiita

引用 : https://bit.ly/3DkNoL0

【JS】Fetch API のResponse には気をつけて。axios とは違うぞ。。。 - 7839

No.2062
02/22 22:58

edit

React に Tailwind CSS を導入する

● React + Tailwind CSS のアプリを新規作成する

npx create-next-app next-tailwind -e with-tailwindcss

● Tailwind CSS のインストール

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

● Tailwind CSS の設定ファイルの作成

npx tailwindcss init -p

自動で tailwind.config.js , postcss.config.js の2ファイルが生成されます

tailwind.config.js

module.exports = {
  mode: 'jit',
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}
No.2056
10/01 15:34

edit

React hooksの概要 / useState / useEffect / useContext / useReducer / useMemo / useCallback / useRef

● 1. React hooks / useState()

useState() とは

「状態を持つ変数」と「更新する関数」を管理するReactフックです。
「状態を持つ変数」の値が変わると useState を宣言したコンポーネントで再レンダリングが起こります。

useStateの使い方

import { useState } from "react";
const [email, setEmail] = useState("");
// const [変数, 変数を更新するための関数(setter アクセサ)] = useState(状態の初期値);
// (例)変数 email / 更新する関数 setEmail() を宣言する
        <input
          type="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
          placeholder="Email..."
        />

オブジェクトや配列に対して、useStateをどう使うか

const [member, setMember] = useState({ name: "", part: "" });

引用 : https://bit.ly/3nG3WHa

useStateで型指定

const [isLogined, setIsLogined] = useState<boolean>(true);

TypeScriptで中身がオブジェクトであるuseStateの初期値にnullが使えない

TypeScriptで

const [startDate, setStartDate] = useState(new Date());

の初期値を null にしたい場合は、以下のように記述します

  const [startDate, setStartDate] = useState <Date|null>(null);

useStateで更新したstateは即座に更新されるわけではない https://tyotto-good.com/blog/usestate-pitfalls

前の値を保持する時にuseRefを使います。 https://qiita.com/cheez921/items/9a5659f4b94662b4fd1e


● 2. React hooks / useEffect()

useEffect() とは

React HooksのuseEffectで関数コンポーネントにライフサイクルを持たせることができます
useEffectを使うと、useEffectに渡された関数はレンダーの結果が画面に反映された後に動作します。( = componentDidMount() )
また便利なことに、useStateフックはマウント解除にも使用できます。( =componentWillUnmount()  )

useEffect の宣言方法

// 第1引数に「実行させたい副作用関数」を記述
// 第2引数に「副作用関数の実行タイミングを制御する依存データ」を記述
useEffect( 副作用関数, [依存する変数の配列]) 

初回レンダリング時とアンマウント時 のみ実行する

useEffect(() => {
    console.log('useEffectが実行されました');
},[]);
// 第2引数の配列を空にして記述すると初回レンダリング時(とアンマウント時)のみ実行されます

第2引数を省略するとコンポーネントがレンダリングされるたび毎回実行されます。 (非常に危険です)

初回レンダリング時と任意の変数が変化したとき実行する

  const [count, setCount] = useState(0);
  useEffect(() => {
    alert('変数 count が変更されましたよ');
  }, [count]);  // 第二引数の配列に任意の変数を指定

マウント解除時

const FunctionalComponent = () => {
 React.useEffect(() => {
   return () => {
     console.log("Bye");
   };
 }, []);
 return <h1>Bye, World</h1>;
};

初回は実行されないuseEffectのカスタムフックを作る(React)

https://zenn.dev/catnose99/scraps/30c623ba72d6b5


● 3. React hooks / useContext()

useContext() とは

Contextは、propsのバケツリレーを回避するために使用します。
グローバルなステートを管理するのに使用する Redux, Recoil とどちらを使うかについては設計段階で検討しましょう。
ReduxはMiddlewareを間に挟むことができるので、Middlewareを使いたい場合はReduxを使用します

以下の4つの要素から作成されます

・React.createContext関数でステートを作成する
・<Context.Provider> を用いて値を渡す
・<Context.Consumer> を用いて値を受け取る
・React.useContext を用いると、Contect.Consumer と同じ役割をさせることができます

1. ・React.createContext関数でステートを作成する

// ● context
export const MyContext = createContext({ myvalue: 'defaultHOGE' }) // デフォルト値を "" とする。

2. ・<Context.Provider> を用いて値を渡す

<MyContext.Provider value={contextValue}>
   ...(ここに値を受け取りたいコンポーネントを記述する)
</MyContext.Provider>

3.・<Context.Consumer> を用いて値を受け取る

const SampleSub1 = () => {
  return (
    <>
      <MyContext.Consumer>
        {value => {
          return (
            <>
              <h1>Hello SampleSub1!</h1>
              <h3>{value.myvalue}</h3>
            </>
          )
        }}
      </MyContext.Consumer>
    </>
  )
}

3.・React.useContext を用いて値を受け取る

こちらの方が記述がシンプルになるのでおすすめです。

const SampleSub2 = () => {
  const { myvalue } = useContext(MyContext)

  return (
    <>
      <h1>Hello SampleSub2!</h1>
      <h3>{myvalue}</h3>
    </>
  )
}


● 4. React hooks / useReducer()

useReducer() とは

引用 : https://bit.ly/3uzQaJb

useReducer で setState 関連のロジックを閉じ込める
deleteItem メソッドは、配列のうち該当する index の item を削除するメソッドであるが、こういったロジックをどこに書くかをかなり悩んできた。結論としては useReducer 内にロジックを保持するパターンが、一番疎結合である。

引用: https://bit.ly/3BeyRQw

useReducerというAPIも登場しています。 useReducerはReduxにおけるReducerのような振る舞いをします。 

引用: https://bit.ly/2Yb49ZK

useReducer が生きるのは、複数の関連したステート、つまりオブジェクトをステートとして扱うときです。

useReducerの使い方

import { useReducer } from "react";
const [state, dispatch] = useReducer(reducer, initialState);
[ステート, 発火関数 ] = useReducer(関数[, 初期値])

なお reducer は自作する必要があります。

const reducer = (state, action) => {
    
    if(action === 'INCREMENT'){
        return {count: state.count + 1}
    }else{
        return {count: state.count - 1}
    }
}

引用 : https://bit.ly/357icDb

Reduxと全く同じ使い方ですので、Reduxをさらっておくことをおすすめします。

useReducer は useState と似ている。
useState では「数値」、「文字列」、「論理値」を扱うことができるがそれを拡張して
useReducerでは「配列」、「オブジェクト」を扱えるようにする


● 5 , 6. React hooks / useCallback / useMemo

React.memo / React.useMemo / React.useCallback は コンポーネントのレンダリング最適化や無限ループ防止を考えるときに登場します

useCallbackは関数そのものをメモ化します。
useMemoは関数の戻り値をメモ化します。

おもにファンクションの場合にuseCallbackを、
オブジェクトの場合にuseMemoを使用します。

Reactで再レンダリングが起きる条件

・stateが更新されたコンポーネントは再レンダリング
・propsが更新されたコンポーネントは再レンダリング
・再レンダリングされたコンポーネント配下の子コンポーネントは再レンダリングされる

引用 : https://bit.ly/34YCuyH

 親コンポーネントのレンダリングによる再レンダリングを制御する方法には以下の2つがあります。

コンポーネントをメモ化する
コンポジション(props.children)を使って子コンポーネントを渡す

memo() とは

使い方

MyChild.jsx

const MyChild = () => {
  console.log('🤙 MyChildのレンダリングが開始されました');
  return (
    <div>MyChild</div>
  );
}
export default MyChild;

 ↓ このように変更することでメモ化されます

MyChild.jsx

import { memo } from "react";
const MyChild = memo( () => {
  console.log('🤙 MyChildのレンダリングが開始されました');
  return (
    <div>MyChild</div>
  );
})
export default MyChild;

全体を memo() で囲っておきます。このようにすることで親コンポーネントに変更があった場合に MyChild は再レンダリングはされなくなります。

useMemo() とは

useMemoとは変数に対して memo化を行うものです。
useMemoは、以前行われた計算の結果をキャッシュし、useMemoが属するコンポーネントが再レンダリングしても、useMemoの第2引数(deps)が更新されていない場合は計算をスキップして以前の結果を使うという機能を提供します。

引用 : https://bit.ly/3tpOgw4

useCallback() とは

useEffect の高速化のための手法
useCallbackはパフォーマンス向上のためのフックで、メモ化したコールバック関数を返します。
useEffectと同じように、依存配列(=[deps] コールバック関数が依存している要素が格納された配列)の要素のいずれかが変化した場合のみ、メモ化した値を再計算します。


● 7. React hooks / useRef

useRef() とは

関数コンポーネントでは、Classコンポーネント時のref属性の代わりに、useRefを使って要素への参照を行います。

useStateを利用している場合はstateの変更される度にコンポーネントの再レンダリングが発生しますが、
useRefは値が変更になっても、コンポーネントの再レンダリングは発生しません。

コンポーネントの再レンダリングはしたくないけど、内部に保持している値だけを更新したい場合は、保持したい値をuseStateではなく、useRefを利用するのが良さそうです。

引用: https://bit.ly/3zXdqC2

refを「ref」propとして渡さないでください。これはReactで予約されている属性名であり、エラーが発生します。
代わりに、forwardRefを使用します
No.2055
10/04 22:03

edit

Next.js + Firebase で Googleログインのテスト

● Next.js + Firebase で Googleログインのテスト


● Next.js アプリの初期化と実行

1-1. アプリの初期化

npx create-next-app

(アプリ名を聞かれるので 「my-first-next-app」 のように入力します。)

1-2. アプリの実行

cd my-first-next-app
npm install
npm run dev

http://localhost:3000/ へ アクセスできることを確認します


● Firebaseの設定

Firebaseのインストール

yarn add firebase @types/firebase

2-1. Firebaseの新規登録

https://console.firebase.google.com/u/0/
へアクセスして「+プロジェクトの追加」をクリックします

(プロジェクト名を聞かれるので「my-first-next-app-firebase」 のように入力します。)

「続行」をクリックしてプロジェクトを作成します。

2-2. 新規登録したFirebaseのプロジェクトの設定

「ウェブ」アイコンをクリックして「ウェブアプリへの Firebase の追加」画面へ移動します。

(ニックネームを聞かれるので「my-first-next-app-apelido」 のように入力します。)

登録が完了すると firebaseConfig が表示されるのでコピーしておきます。

const firebaseConfig = {
  apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  authDomain: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  projectId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  storageBucket: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  appId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
};

コピーした後コンソールに進みます

2-3. FirebaseでGoogle認証を有効にする

一番左のメニューの「Authentication」をクリックし「始める」をクリックします。

ログインプロバイダーにGoogleを追加して有効化します。

2-4. ログインテスト用アカウントを作成しておく

「Authentication」 →「Users」からログインテスト用アカウントを作成しておきます


● Next.js アプリへFirebaseの追加

3-1. Firebaseの新規登録

npm install firebase

3-2. Firebaseアプリ初期化コンポーネントの作成

FirebaseApp.js

import { initializeApp } from "firebase/app";

const firebaseConfig = {
  apiKey              : "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
  authDomain          : "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
  projectId           : "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
  storageBucket       : "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
  messagingSenderId   : "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
  appId               : "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
};

const FirebaseApp = initializeApp(firebaseConfig);

export default FirebaseApp

3-3. Firebaseログインサンプル画面の作成

pages/login.js

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import FirebaseApp from '../FirebaseApp';
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";

export default function Home() {

  const doLogin = () => {
    const auth = getAuth();

    // Firebaseに登録したID,PASSWORD
    const email = 'test@user.com';
    const password = 'XXXXXXXXXX';

    signInWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        const user = userCredential.user;
        alert( 'ログインok!' );
        console.log( '● user' );
        console.log( user );
      })
      .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
      });
  }

  return (
    <div className={styles.container}>
      <h1>Googleログイン</h1>
      <button onClick={doLogin}>googleでログインする</button>
    </div>
  )
}

3-4. Firebaseログインの実行

http://localhost:3000/login
にアクセスしてログインボタンをクリックすると「ログインok!」のアラートが表示されます。

● ログイン状態の永続性

import {
  getAuth,
  setPersistence,
  browserLocalPersistence,
  browserSessionPersistence,
  inMemoryPersistence
} from "firebase/auth";

const auth = getAuth()

await setPersistence(auth, browserLocalPersistence);

以下の4パターンが存在します

browserLocalPersistence (LOCAL)
browserSessionPersistence (SESSION)
indexedDBLocalPersistence (LOCAL)
inMemoryPersistence (NONE)

Firebase Authentication これだけは覚えておけ!テストに出るぞ! - Qiita

● 現在ログイン中かどうかを調べる

firebase.auth().currentUser

に現在ログイン中のユーザ情報が帰ってきますのでそこを調べます。

No.2052
01/19 17:56

edit

プロセスマネージャー pm2で next.js を動作させる

● 1. pm2 のインストール

-g オプションをつけてグローバルにインストールします

 npm install pm2@latest -g

バージョンを確認します

pm2 --version
5.1.2


● 2-A. ワンライナーでpm2からnext.jsを起動する

cd <nextjsアプリのディレクトリ >
pm2 start npm --name "my-next-app" -- start 


● pm2のコマンド

プロセスの状態を見る

pm2 ls

「nextjs」という名前のアプリを停止する

pm2 stop nextjs

「nextjs」という名前のアプリをプロセスリストからする

pm2 delete nextjs

「nextjs」という名前のアプリをリスタートする

pm2 restart app_name

● pm2 を サーバーマシン起動時に自動実行するように設定する

pm2 を自動起動させる( centos )

pm2 startup

● pm2自動起動時の起動プロセスを保存

pm2 を自動起動させる

pm2 save
No.1907
09/13 15:47

edit

next.js