20200810のReactに関する記事は13件です。

シンプルなReact用JSONエディター

この記事で作っていたJSON Editorを(だいぶ整えて)npmに公開しました。
Reactでデバッグに使う用のテキストエディタを作った

npm: https://www.npmjs.com/package/react-plain-json-editor
GitHub: https://github.com/nariakiiwatani/react-plain-json-editor

※npmへの公開にあたってはこちらの記事を大いに参考にさせていただきました。
初めてのnpm パッケージ公開

インストール

npm i react-plain-json-editor

もしくは

yarn add react-plain-json-editor

できること

Component(<PlainJsonEditor />)とhooks(useJsonEditor)のどちらでも使用可能。
基本的にはコンポーネント版の方が高機能(コンポーネント版は中でhook版を使用している)

大雑把に言えば、teaxtarea要素に打ち込んだテキストがJSONにパースした結果を何かに利用するためのライブラリということになる。

使い方はGitHubのreadmeもしくはデモページを参照。
このデモページでは入力されたJSONデータをそのままそのページ自体のCSS Propertiesとして使用している。
デモページ

他にも、独自のデータ構造を持ったprops受けるコンポーネントへのテストデータの生成や
単にJSON文字列のバリデータとしてなど、使い道はいくつかありそう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReduxとReduxToolkitを使用してReact内でデータを管理する

はじめに

前回のReactを使用してWeb画面を作成するでは、Reactの簡単なサンプルと共に説明をまとめました。次はデータを持ちまわすためにReduxとReduxToolkitの簡単なサンプルと説明をまとめようと思います。
最低限の簡単なサンプルなのでアクションは別ファイルにするべきであったり、といったベストプラクティス的なものは他のサイトで調べてください。

環境

  • node.js: v12.18.2
  • webpack: 4.44.1
  • React: 16.13.1
  • Redux: 7.2.1

環境作成

前回のReactを使用してWeb画面を作成するの続きとなります。
環境構築などはそちらを見てください。

Redux

Reactのコンポーネント間でデータを共有するためにReduxというライブラリを使用します。

Reduxのインストール

ReduxのライブラリとReduxをシンプルに記載するためのtoolkitをインストールします。

npm install react-redux
npm install @reduxjs/toolkit

シンプルなRedux

Reduxのソースの作成

フォルダの作成

Reduxのソースをstore、slice、コンポーネントに分けて作成するので、コンポーネント以外のフォルダを作成してください。コンポーネントは前回作成したsrcの下に入れます。

project_root
├─src    // reactのJavaScriptファイルやCSSファイルを格納
├─store  // redux toolkitのstore(reduxのstoreをまとめたもの)ファイルを格納
  ├─slice    // redux toolkitのslice(reduxのactionとreducerをまとめたもの)

sliceファイルの作成

内部的に保持する情報と処理をまとめたものをsliceファイルとして作成しています。
今回は単純にWeb画面と文字のやり取りをするため保持するデータは"mess"、処理は"hello"をデータに置き換える処理としています。

messageSlice.js
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios';

export const messageSlice = createSlice({
  // slice名
  name: 'message',
  // 内部で保持するデータ(キー:mess, 初期値:メッセージ)
  initialState: {
      "mess": "メッセージ"
  },
  // 内部のデータにアクセスするための処理(処理名:sayhello)
  reducers: {
    sayhello: state => {
      state.mess = "hello";
    }
  },
});

// 外からインポートするためにactionとreducerをエクスポートする
export const { sayhello } = messageSlice.actions;
export default messageSlice.reducer;

storeファイルの作成

先ほど作成したsliceのreducerをstoreに登録することで各コンポーネントで情報を共有できるようにします。

store.js
import { configureStore } from '@reduxjs/toolkit';
import messageReducer from './slice/messageSlice';

export default configureStore({
  reducer: {
    message: messageReducer,
  },
});

storeをコンポーネントに適用させる

コンポーネント間で情報をやり取りするために先ほど作成したstoreをreactのrenderに登録します。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

// redux用のインポート
import { Provider } from 'react-redux'
import store from '../store/store'

ReactDOM.render(
  // インポートしたstoreを登録する
  <Provider store={store}>
    <App />,
  </Provider>,
  document.getElementById('app')
);

slice経由でstoreを使用する

コンポーネントで情報を処理するためにsliceのaction経由でstoreを操作します。

App.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { sayhello } from '../store/slice/messageSlice';

export function Message() {
  // store内の値を取得
  const message = useSelector(state => state.message.mess);
  // actionを操作するための関数取得
  const dispatch = useDispatch();

  return (
    <div>
      <div>
        {/* Sliceで定義したactionをdispatch経由で呼び出す */}
        <button aria-label="hello" onClick={() => dispatch(sayhello())}>
          こんにちは
        </button>
        {/* 上で呼び出したmessageを表示する */}
        <span>{message}</span>
      </div>
    </div>
  );
}

最終的なフォルダ構成

project_root
├─dict
├─public
|  Ⅼ-index.html
├─src    
|  ├─App.js
|  Ⅼ─index.js
├─store  
|  ├─slice  
|  | Ⅼ─messageSlice.js
|  Ⅼ-store.js
├─.babelrc
├─package.json
Ⅼ─webpack.config.js

Reactの実行

前回同様にReactを開発用のサーバで起動してブラウザからアクセスしてください。

"./node_modules/.bin/webpack-dev-server"

表示されたWeb画面にこんにちはというボタンがあると思うため、それをクリックすると隣の文字がhelloに変わります。
内部の処理としては、ザックリ言うと以下のようなイメージになります。
画面描画時にApp.js内でuseSelectorを使用することにより、messageSlice.jsで定義したmess変数を呼び出しています。
その呼び出した変数を<span>{message}</span>と紐づけて変数が変わったら自動的に変わるように使用しています。
ボタンを押したらmessageSlice.jsで定義したsayhelloを呼び出してmess変数を更新して、再描画しています。

画面から受け取るRedux

先ほどはactionの中で定義した値に更新していましたが、今度はWeb画面に入力した内容を使用してstoreを更新します。

Reduxのソースの作成

先ほどのファイルに処理を追加して機能を実装します。

sliceファイルの作成

messageSlice.js
import { createSlice } from '@reduxjs/toolkit';

export const messageSlice = createSlice({
  name: 'message',
  ~~~ 上と同じ ~~~

  reducers: {
  ~~~ 上と同じ ~~~
  // この処理を追加します。
    sayAmount: (state, action) => {
      state.mess = action.payload;
    },
  },
});

// 追加したsayAmountをエクスポートできるようにする
export const { sayhello, sayAmount} = messageSlice.actions;

export default messageSlice.reducer;

storeファイルの作成

storeファイルは、上のreducerをまとめて登録しているので変更なしです。

storeをコンポーネントに適用させる

storeファイルは、上のreducerをまとめて登録しているので変更なしです。

slice経由でstoreを使用する

内容としては。ほとんど先ほどのものと変わらないです。
8行目のところでuseStateを使用して2つの関数を生成していますが、ザックリ言うとクラス内のstateの宣言などを不要にする物になります。

Message.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { sayhello, sayAmount } from '../store/slice/messageSlice';

export function Message() {
  const message = useSelector(state => state.message.mess);
  const dispatch = useDispatch();
  const [messsageAmount, setMesssageAmount] = useState('2');
  ~~~ 上と同じ ~~~
        {/* テキストボックスとボタンにインポートしたものを適用する  */}
        <input aria-label="set amount" value={messsageAmount} onChange={e => setMesssageAmount(e.target.value)} />
        <button onClick={() => dispatch(sayAmount(messsageAmount))}>
          テキスト変更
        </button>
      </div>
    </div>
  );
}

Reactの実行

上と同様にReactを開発用のサーバで起動してブラウザからアクセスしてください。
今回追加したテキストボックスとボタンが追加されています。テキストボックスに値を入れてボタンをクリックすると表示されているテキストが変更されます。
基本的に内容としてはほぼ表示のみと変わりません。

終わりに

axiosとの連携を書こうと考えていましたが、Reduxの説明が予想以上に長くなったので今回はここまでにします。次回以降にaxiosのサンプルと簡単な説明を書いていこうと思います。axiosのサンプルと説明はReactのRedux内でaxiosを使用した通信をするにまとめました。
今回は簡単な例なのでメリットがわからないと思いますが、Web画面や保持する情報が増えたら明確にメリットがわかるようになると思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + apollo client + Hexagonal architectureでロジックとビューを分離する

導入

Reactを書く時、外部との通信や状態の更新など、ロジックどこに書くか問題というのは常につきまとうかと思います。1ページ分だけの小さなReact applciationであれば、とりあえず外側のコンポーネントに押し込んでおけばいいかと割り切ることもできると思うのですが、機能が増えたり複雑なロジックを持つようになるとつらみが増し、末端のコンポーネントにロジックが漏れて行ったりします。
以前からどうすればいいかといつも悩んでいたのですが、Hexagonal architectureを導入してみたところ、まま快適になったので、まとめます。

サンプルのコードはこちら
https://github.com/eiji03aero/react-hexagonal-sample

概要

Hexagonal architectureとは

本家ブログはこちら。
https://alistair.cockburn.us/hexagonal-architecture/

Hexagonal architectureでは、applicationをコアとなるロジックの部分(application service以下)とそれ以外の外界とのやりとり(adapters: DBのread/writeやhttp endpointなど)を明確にわけることで、以下のようなメリットを得ることを目的とします。今回は主にview層へのビジネスロジック流出の阻止が目的となります。

高いテスタビリティ

基本的にplain oldなコードのみで記述されるApplication service以下にビジネスロジックが集約されるため、テストを容易に行うことができます。またApplication serviceが依存するportsを実体のないinterfaceとすることでモックの差し込みも容易です。

view層へのビジネスロジック流出の阻止

application service以下にビジネスロジックを配置し、view層はあくまでapplication serviceが提供するapiをadapterとして利用するだけにする、という形に制限することで、ビジネスロジックがview層に流れることを防ぎます。ここは人力です。

同じ機能を複数の入力に対して提供する

application serviceがinportsを提供することにより、複数のadaptersが同じ機能を利用することができます。たとえば、csvレポートを出力するという機能をapplciation serviceが持っていた場合に、その機能を利用するhttp endpointのadapterとcliのadapterを作成することができます。

sampleについて

sampleはtodoを管理するアプリです。todoの作成や完了更新、tagの作成やtodoへの紐づけ、またtodo一覧でfilter表示などができます。
Apollo clientを状態管理に利用することで、ServiceとReact双方から同じデータを参照できるようにしつつ、Serviceが好きなようにデータの更新を行うことができます。
申し訳程度の六角形ですがお許しください。

react-hexagonal-sample-architecture.png

  • Service
    • Application serviceとしての責務を持ちます。adapterとしてのreactが利用する、主にデータ更新系のapiを提供します。
  • React
    • view層を担います。Serviceのinstanceをcontextに保持し、データの作成や更新の際に必要なapiを呼び出します。
    • データ(Todoなど)の読み込みをapollo client cacheから行うことにより、Service(Repositories経由で)が状態を変更した時に自動的にviewに反映します。
  • Repositories
    • Apollo clientに依存し、データのRead/Writeを担います。
  • Apollo client cache
    • ローカルの状態管理を担います。
    • データの変更があった際に、Reactに変更を反映します。
  • ①: ReactはServiceが提供するapiを呼び出してデータの更新などを行います。
  • ②: ServiceはRepositoriesが提供するapiを呼び出してデータのRead/Writeを行います。
  • ③: Repositoriesはapollo clientに直接依存し、データのRead/Writeを行い、実装の詳細をServiceから隠します。
  • ④: ReactはApollo clientの提供するapiを利用し、データの読み込みと、変更が会った際のデータの反映を受け取ります。

sampleのコード

コードを追いながら、実際の処理のフローを見てみます。

ユースケース1: Todoを作成する

Todoのタイトルをユーザーが入力して作成の処理を進め、作成されたデータがviewに反映されるまでです。

1. Serviceのapiを呼び出し

まずcomponentのeventListenerをtriggerに、Service#createTodoを呼び出します。

src/adapters/ui/routes/Todos.tsx
export const Todos: React.FC = () => {
  ...  
  const handleCreate = React.useCallback((value: string) => {
    ctx.service.createTodo({
      title: value,
    });
  }, [ctx]);
  ...
};

2. Service#createTodo

受け取った引数で、todosServiceのメソッドを呼び出して、Todoの作成の処理を進めます。

src/service/Service.ts
export class Service implements types.IService {
  ...
  async createTodo (params: {
    title: string,
  }): types.PromisedEither<types.ITodo> {
    const r1 = await this._todosService.create(params);
    ...
  }
  ...
}

3. TodosService#create

受け取った引数を使って、Todo classのインスタンスを作成します。
さらにインスタンスのvalidationを実行し、問題がなければtodosRepository#saveにインスタンスを渡して永続化をします。

src/domain/services/TodosService.ts
export class TodosService implements types.ITodosService {
  ...
  async create (params: {
    title: string;
  }): types.PromisedEither<types.ITodo> {
    const todo = new Todo({
      title: params.title,
      done: false,
    });
    const r1 = todo.validate();
    if (E.isLeft(r1)) {
      return r1;
    }

    const r2 = await this._todosRepository.save(todo);
    if (E.isLeft(r2)) {
      return r2;
    }

    return E.right(todo);
  }
  ...
}

4. TodosService#save

Todo classのインスタンスを受け取り、apollo client cacheに書き込みます。

src/adapters/repositories/TodosRepository.ts
export class TodosRepository implements types. ITodosRepository {
  ...
  async save (todo: types.ITodo): types.PromisedEither<null> {
    const r1 = await this._getSerialized({});
    if (E.isLeft(r1)) {
      return r1;
    }
    const stodos = r1.right;

    const stodo = todo.serialize();
    this._apolloClient.writeQuery({
      query: local.GetTodosDocument,
      data: {
        todos: [stodo, ...stodos]
      }
    });

    return E.right(null);
  }
  ...
}

5. 追加されたデータのviewへの反映

前項目で追加されたデータはuseQueryを通して反映されます。

src/adapters/ui/routes/Todos.tsx
export const Todos: React.FC = () => {
  ...
  const todosResult = useQuery(local.GetTodosDocument, {
    variables: {
      keyword: state.keyword,
      tagIds: state.tagIds,
      sort: state.sort,
    }
  });
  ...
};

ユースケース2: 無効なTagを作成しようとしたエラーの通知をviewで購読する

無効な入力でTagを作成しようとして、Tag classのvalidationに引っ掛かり、エラ〜メッセージがEventEmitter経由でviewに通知されるまでです。

1. 無効な値でServiceのapiを呼び出し

空文字を名前としてTagを作成するためのapiを呼び出します。

src/adapters/ui/routes/Tags.tsx
export const Tags:React.FC = () => {
  ...
  const handleCreate = React.useCallback((value: string) => {
    ctx.service.createTag({
      name: value,
    });
  }, [ctx]);
  ...
};

2. Service#createTag

受け取った引数でTagsService#createを呼び出します。

src/service/Service.ts
export class Service implements types.IService {
  ...
  async createTag (params: {
    name: string,
    color?: string,
  }): types.PromisedEither<types.ITag> {
    const r1 = await this._tagsService.create(params);
    ...
  }
  ...
}

3. TagsService#create

受け取った引数でTag classのインスタンスを作成し、validateメソッドを呼び出します。

src/domain/services/TagsService.ts
export class TagsService implements types.ITagsService {
  ...
  async create (params: {
    name: string,
    color?: string,
  }): types.PromisedEither<types.ITag> {
    const tag = new Tag({
      name: params.name,
      color: params.color || colors.random(),
    });
    const r1 = tag.validate();
    ...
  }
  ...
}

4. Tag#validate

このインスタンスはname propertyに空文字を持っているため、Errorを返却します。

src/domain/entities/Tag.ts
export class Tag extends BaseEntity implements types.ITag {
  ...
  validate () {
    ...
    if (!this.name) {
      return E.left(new Error("name cannot be empty"));
    }
    ...
  }
  ...
}

5. Service#createTag-2

前項目で返却されたエラーはService#createTagまでもどってきます。
Service#notificateを呼び出し、Eventをpublishします。

src/service/Service.ts
export class Service implements types.IService {
  ...
  async createTag (params: {
    name: string,
    color?: string,
  }): types.PromisedEither<types.ITag> {
    const r1 = await this._tagsService.create(params);
    if (E.isLeft(r1)) {
      await this.notificate({
        type: "error",
        message: `Failed to create tag: ${r1.left.message}`,
      });
      return r1;
    }
    ...
  }
  ...
}

6. 通知の購読

前項目でpublishされた通知は、通知のためのコンポーネントによって購読され、ユーザーへfeedbackされます。

src/adapters/ui/containers/NotificationsContainer.tsx
export const NotificationsContainer: React.FC = () => {
  ...
  React.useEffect(() => {
    const handler = ((sn: types.SNotification) => {
      setState({
        open: true,
        currentNotification: sn,
      });
    });

    ctx.service.onNotification(handler);

    return () => {
      ctx.service.offNotification(handler);
    };
  }, [setState]);
  ...
  return (
      <Snackbar
        open={state.open}
        autoHideDuration={6000}
        anchorOrigin={{
          vertical: "bottom",
          horizontal: "right",
        }}
        onClose={handleClose}
      >
        <Alert
          elevation={6}
          variant="filled"
          severity={severity}
          onClose={handleClose}
          children={message}
        />
      </Snackbar>
  );
};

所感

結局ビジネスロジックがview層に漏れでていないかどうかは開発者が人力でレビューするしかないのですが、明確にcomponentを定義してこの中に収まるように書きましょう、と規約を設定できるのはわかりやすくていいと思います。以下pros cons。

良いところ

  • Service以下にロジックを集約できる。Reactからはあくまで必要な引数を作ってServiceのapiに渡すだけにとどめる。きれいになります。
  • Apollo clientのおかげで、Service - React間の繋ぎ込みが容易です。
  • テストが書きやすい。
    • 今回のsampleではテストは書いていないのですが、やはりビジネスロジックが混入しないよう努力されているため、書きやすいはずであります。

悪いところ

  • ボイラープレートが多い
    • Service class内に直接IO処理(apollo client cacheへのRead/Write)を書かないためにRepositoryというadapterを作ることにしているが、ServiceとRepositoryのapiで似たようなsignatureになりやすいので、冗長な記述が増えてしまった。一つ一つtypeのaliasを定義するのもいかがなものか。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firestoreのsetメソッド基本の使い方

1. addメソッドとは

  • データベースに追加するときに使う
  • firesroreのaddメソッドは自動でIDを採番してくれるので単純にデータを渡せば良い
const itemRef = db.collection('items')
const data = {
  name: name,
  price: parseInt(price, 10)
}

return itemRef.add(data)
  .then(() => {
    dispatch(push('/'))
})

2. setメソッドとは

  • idを指定して登録できる
  • 何も指定がないと自動で設定される(addと同じ)
return itemRef.doc().set(data)
  • 事前に割り振られるIDを取得できる
const ref = itemRef.doc();
const id = ref.id;
data.id = id
return itemRef.doc(id).set(data)
  • 変更部分のみmargeできる
return item.Ref.doc(id).set(data, {marge: true})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js & material UIで[Prop className` did not match`]が発生する

問題

Next.jsアプリでmaterial-UIを使用した際に、[Prop className' did not match' Server 'xx' Client 'xx']が発生する

詳細

  • makeStylesで作成したスタイルの読み込み時に発生
  • 初回レンダリング時には発生しない
  • 変更を加えたり、ページ更新を行うと発生する
  • Server 'xx' Client 'xx'の内容は、Server "makeStyles-mainContainer-1" Client makeStyles-mainContainer-2のように、クラス名に違いが生じている

解決方法

参照:
Material UI公式:サーバーサイドレンダリングについて
上記ページからのリンク:Next.jsのサンプルリポジトリ

サンプルリポジトリの通り、_app.js_document.jsを変更することで解決

pages/_app.js
import React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import theme from '../src/theme';

export default function MyApp(props) {
  const { Component, pageProps } = props;

  React.useEffect(() => {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
      jssStyles.parentElement.removeChild(jssStyles);
    }
  }, []);

  return (
    <React.Fragment>
      <Head>
        <title>My page</title>
        <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
      </Head>
      <ThemeProvider theme={theme}>
        {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </React.Fragment>
  );
}

MyApp.propTypes = {
  Component: PropTypes.elementType.isRequired,
  pageProps: PropTypes.object.isRequired,
};

_document.js
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '@material-ui/core/styles';
import theme from '../src/theme';

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          {/* PWA primary color */}
          <meta name="theme-color" content={theme.palette.primary.main} />
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {

  // Render app and page and get the context of the page with collected side effects.
  const sheets = new ServerStyleSheets();
  const originalRenderPage = ctx.renderPage;

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
    });

  const initialProps = await Document.getInitialProps(ctx);

  return {
    ...initialProps,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
  };
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebaseでユーザー認証を行うときのメソッド

Firebese Authのメソッド

1. createUserWithEmailAndPassword()

文字通りメールアドレスとパスワード認証でユーザーを作成するメソッド
引数にemail,passwordを受け取る
戻り値はPromis

 .then(result => {
        const user = result.user

とするとuser.uidなどで取得しやすい

firestoreに登録する場合

if (user) {
          const uid = user.uid
          const timestamp = FirebaseTimestamp.now()
          const userInitialDate = {
            created_at: timestamp,
            email: email
            uid: uid,
            updated_at: timestamp,
            username: username
          }

          db.collection('users').doc(uid).set(userInitialDate)
            .then(() => {
              dispatch(push('/'))
            })
        }

とすることでusersコレクションに登録される。ちなみにdbの中身はfirebase.firestore()になっていて、処理が完了するとルートパスに戻る。

2.signInWithEmailAndPassword()

上記で作成したアカウントにログインするメソッド
引数にemail, passwordを受け取る
上記同様に実行結果を定数に入れることで扱いやすくなる
firebase Authでサインインした場合アプリ側にもstateを更新する必要があるので、以下のようにする

if (user) {
          const uid = user.uid

          db.collection("users").doc(uid).get()
            .then(snapshot => {
              const data =snapshot.data()

              dispatch(signInAction({
                isSignedIn: true,
                uid: uid,
                username: data.username
              }))
              dispatch(push("/"))
            })
        }

データベースからuidが一致する情報を引っ張り、signInActionを実行しreduxのstateを変更している(reduxではactionsは変更を伝える役割で実際にはreducersがどう変更するか決めている)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[ApexCharts] React / Angular などでテスト(jest)を実行すると 'TypeError: r.node.getBBox is not a function' 発生

概要(症状)

私の場合 React ですが、以下のようなエラーが出ました。

エラー状況
(node:3677) UnhandledPromiseRejectionWarning: TypeError: Caught error after test environment was torn down

Cannot read property 'body' of null
(node:3677) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 23)
 FAIL  src/App.test.js (28.739s)
  ● renders hoge huga

    TypeError: r.node.getBBox is not a function

...以下略

テスト対象(※Reactのソースです)

エラーが発生するテスト対象はこんな感じ
なるべく削ぎ落としてます。

App.js
import React, { Component } from 'react';
import ReactApexChart from 'react-apexcharts'

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      options: {
        chart: {
          type: 'candlestick',
          height: 350
        },
        title: {
          text: 'CandleStick Chart',
          align: 'left'
        },
        xaxis: {
          type: 'datetime'
        },
        yaxis: {
          tooltip: {
            enabled: true
          }
        }
      },
      series: [{
        name: 'series-1',
        data: [{
            x: new Date(1538778600000),
            y: [6629.81, 6650.5, 6623.04, 6633.33]
          },
          {
            x: new Date(1538780400000),
            y: [6632.01, 6643.59, 6620, 6630.11]
          },
          {
            x: new Date(1538782200000),
            y: [6630.71, 6648.95, 6623.34, 6635.65]
          },
          {
            x: new Date(1538784000000),
            y: [6635.65, 6651, 6629.67, 6638.24]
          },
          {
            x: new Date(1538785800000),
            y: [6638.24, 6640, 6620, 6624.47]
          },
          {
            x: new Date(1538787600000),
            y: [6624.53, 6636.03, 6621.68, 6624.31]
          },
          {
            x: new Date(1538789400000),
            y: [6624.61, 6632.2, 6617, 6626.02]
          },
          {
            x: new Date(1538791200000),
            y: [6627, 6627.62, 6584.22, 6603.02]
          },
          {
            x: new Date(1538793000000),
            y: [6605, 6608.03, 6598.95, 6604.01]
          },
          {
            x: new Date(1538794800000),
            y: [6604.5, 6614.4, 6602.26, 6608.02]
          },
          {
            x: new Date(1538796600000),
            y: [6608.02, 6610.68, 6601.99, 6608.91]
          },
        ]
      }]
    }
  }

  render() {
    return (
      <>
        <div id="chart">
          <ReactApexChart options={this.state.options} series={this.state.series} type="candlestick" width={800} height={350} />
        </div>
        <span>test hoge huga !</span>
      </>
    );
  }
}

export default App;

対処

github の issue が既にいくつか上がっており、mockを2つ作成することでこのエラーが回避できました。

英語だと読解に時間がかかったので(´;ω;`)ブワッ、日本語化しておこうと思った次第です

ソースコード

こんな感じでOKでした。

App.test.js
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';

// この2つの jest.mock を追加しただけ ↓
jest.mock("react-apexcharts", () => jest.fn(() => { return null; }) );
jest.mock("apexcharts", () => (
  {
    exec: jest.fn(() => {
      return new Promise(
        (resolve, reject) => { resolve("uri"); }
      );
    }) 
  }
));


test('renders hoge huga', () => {
  const { getByText } = render(<App />);
  const linkElement = getByText(/hoge huga/i);
  expect(linkElement).toBeInTheDocument();
});

// result
=>  PASS  src/App.test.js (15.903s)

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactを使用してWeb画面を作成する

はじめに

reactとreact-reduxを調べたときに概念や考え方など難しい話から入っているサイトやちょっと難しい(カッコいい)Web画面をサンプルに使用していて理解が難しいなと感じました。
そのため、単純なサンプルを使用して最低限の説明のみをしようと思います。

環境

  • node.js: v12.18.2
  • webpack: 4.44.1
  • React: 16.13.1

環境作成

node.jsのインストール

公式のサイトに従ってインストールしてください
公式サイト: https://nodejs.org/ja/

プロジェクト用のファイルを作成

node.jsのプロジェクトではプロジェクトの設定やインストールしたパッケージなどをpackage.jsonに記載します。
次のコマンドでpackage.jsonを作成するといくつかの入力項目がありますが基本的にすべてデフォルトで問題ないです。

npm init

Babel

環境やブラウザのバージョンによって使用できるJavaScriptの仕様が異なります。その仕様の差分を埋めるために、Babelを使用して作成したJavaScriptを対応可能なものに変換します。

Babelのインストール

npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/preset-react @babel/register

Babelの設定作成

Babelの設定はプロジェクト直下の.babelrc に記載します。そのため、このファイルを作成して以下の内容を記載します。

{
    "presets": ["@babel/env", "@babel/preset-react"]
}

webpack

Web画面からJavaScriptを読みだす際にJavaScriptのファイルが多いと無駄な時間や処理が発生します。webpackを使用すると複数のファイルを一つにまとめていい感じにしてくれます。

webpackのインストール

webpackに必要なライブラリの他にもローカルでサーバを起動するために webpack-dev-server をインストールします。

npm install --save-dev webpack webpack-cli webpack-dev-server style-loader css-loader babel-loader

webpackの設定作成

webpackの設定はプロジェクト直下のwebpack.config.js に記載します。このファイルを作成して以下の内容を記載します。

webpack.config.js
const path = require("path");
const webpack = require("webpack");

module.exports = {
  entry: "./src/index.js",
  mode: "development",
  module: {
    // ファイルをどのように変換すればよいのかのルールを設定。
    // testで入力するファイルの条件、excludeで除外する条件、
    //loaderで外部ライブラリのルールを参照する 
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  // ビルドの順番を設定
  resolve: { extensions: ["*", ".js", ".jsx"] },
  // ビルド後の設定
  // pathは、ビルド後のファイルを吐き出すフォルダ、
  // filenameはビルド後のファイル名を設定
  output: {
    path: path.resolve(__dirname, "dist/"),
    filename: "bundle.js"
  },
  // ローカルで起動するサーバの設定
  // contentBaseでブラウザからアクセスしたときのルート、
  // portはブラウザからアクセスするときのポート番号、
  // hotOnlyはファイルを更新したときに自動読み込みをする設定
  devServer: {
    contentBase: path.join(__dirname, "public/"),
    port: 8080,
    hotOnly: true
  },
  plugins: [new webpack.HotModuleReplacementPlugin()]
};

react

reactのインストール

npm install react react-dom

Web画面のソースの作成

フォルダの作成

プロジェクトルート直下にsrcとpublicとdistのフォルダを作成してください。
※上のwebpackの設定を変えたときはここも変えてください。

project_root
├─dist   // ビルド後のファイルを格納 
├─public // htmlを格納
├─src    // reactのJavaScriptファイルやCSSファイルを格納
├─.babelrc
├─package.json
├─webpack.config.js

htmlファイルの作成

ブラウザからアクセスした際に一番最初にアクセスされるhtmlファイルを作成します。
※webpackのビルド後のファイルをインポートするのを忘れないでください。

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>React Sample</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./bundle.js"></script>
  </body>
</html>

reactのrendarファイルの作成

reactの機能をつかってレンダリングするJavaScriptファイルを作成します。
ReactDOM.render()にコンポーネントファイルとdocument.getElementById(置き換えるhtmlのid)を指定してあげます。
上のindex.htmlの<div id="app"></div>とAppコンポーネントを置き換えたいのでReactDOM.render()<App />document.getElementById('app')を指定します。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('app')
);

reactのコンポーネントファイルの作成

実際にWeb画面に表示するための情報を書いたコンポーネントのJavaScriptファイルを作成します。
基本的にはコンポーネントファイルを増やして、Web画面を増やしたりWeb画面の要素を増やしたりします。
※上のindex.jsのimport App from './App';でこのファイルをインポートしています。最後の行のexportを忘れないでください。

App.js
import React, { Component} from "react";

class App extends Component{
  render(){
    return(
      <div className="App">
        <h1> Hello, World! </h1>
      </div>
    );
  }
}

export default App;

Web画面の起動

ビルド

開発用のサーバを起動するときに同時にビルドが走るので特に必須ではないですが、webpackの設定や作成したファイルが間違っていないかをチェックするために一旦ビルドします。
プロジェクトのルートで次のコマンドを実行するとビルドが走ります。Windowsの場合はダブルクォーテーションで囲まないとうまくいかないです。

"./node_modules/.bin/webpack" 

ビルドが成功するとdictフォルダ内にJavaScriptファイルが一つできるはずです。

開発用サーバの起動

プロジェクトのルートで次のコマンドを実行するとビルドとサーバの起動が走ります。Windowsの場合はダブルクォーテーションで囲まないとうまくいかないです。

"./node_modules/.bin/webpack-dev-server"

サーバが起動したらブラウザからlocalhost:8080にアクセスするとHello, World!が表示されます。

終わりに

Reduxやaxiosとの連携を書こうと考えていましたが、予想以上に長くなったので今回はここまでにします。
次回以降にReduxとaxiosのサンプルと簡単な説明を書いていこうと思います。
それぞれまとめました。
Redux:ReduxとReduxToolkitを使用してReact内でデータを管理する
axios:ReactのRedux内でaxiosを使用した通信をする

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactサーバをプロキシにしてCORSに引っかからないようにする

create-react-appでreact環境を作りました。
次にAPIを叩きたいのですが、今のreactサーバからやってもいいけど負荷が集中するからあまり現実的じゃない。
(→役割分担させる。webサーバは基本的にページを返す。APIサーバは基本的に裏でAPI叩いてデータのやり取りを行う。)

そこでreactサーバとネットの間にもう一つサーバ(Flask)をかますことにしました。
ただ、このまま使ってもCORSに引っかかってうまくAPIを叩くことができません。

CORS

セキュリティ的な問題でデフォルトで設定されている。
ブラウザから入力したものが意図しないリソースにアクセスされないように。
参考
https://dev.classmethod.jp/articles/cors-cross-origin-resource-sharing-cross-domain/

create-react-appのプロキシ設定

それ用のmiddlewareがあるからそれを使えば簡単に設定できる。
窓口は一つのポート番号にして、そこから裏で動いている別サーバにアクセすることが可能
参考
https://applingo.tokyo/article/1568

注意点

直接URLにFlaskのエンドポイントを入力しても画面は真っ白のまま、、、
一方で、コンポーネント内でaxiosを使ってgetするとうまくいく、、、
これは、create-react-appの仕様上の問題で、access headerがtext/htmlじゃないもののみプロキシに渡すようになっているらしい。
だから、直接URLに入力しても何も帰ってこないのか、納得。
詳しくは公式ドキュメントを参考にしてください。
https://create-react-app.dev/docs/proxying-api-requests-in-development/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reduxのデバックに必須!Redux DevToolsの使い方

react reduxを使用して開発しているときに必須のデバックツールだと思います。
Google Choromeのエクステンションでstateの偏移や、アクションの実行の履歴などを確認できます。

Redux DevToolsをインストールする

chrome ウェブストアでインストールしましょう。

https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ja
そしたらRedux DevToolsのアイコンが右上にでると思います。
0_lNKJWkLVKZOMfFh9_.jpg
そしたらreactのプロジェクトのindex.jsを書き換えましょう

index.js
import React from 'react';
import { ReactDOM } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, compose } from 'redux';

import App from './App';
import reducer from './reducers';

  const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  );

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

redux-thunkなどのミドルウェアを使用するならこうなります。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import reduxThunk from 'redux-thunk';

import App from './App';
import reducers from './reducers';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, composeEnhancers(applyMiddleware(reduxThunk)));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector('#root')
);

これだけで準備は完了です。

Redux DevTools を使用する

あとはローカルサーバーを起動したときにアイコンをクリックすればウィンドウがでます。
右側にあるActionを押せばなんのActionが実行されたか、Stateを押したらStoreの状況を見ることができます。他にも機能があるので色々試してみてください。
arbnb_redux_devtools.png

以上です。便利なデバックツールを活用して開発を行っていきましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React初心者が、React.Componentのライフサイクルを理解する

React.Componentには、
- Mounting
- Updating
- Unmounting

があります。それぞれで呼び出される関数があり、それらを実装すると、コンポーネント自体のライフサイクルを操作できる。

Mounting

  1. constructor(props)
  2. static getDerivedStateFromProps(nextProps, prevState)
  3. componentWillMount() / UNSAFE_componentWillMount()
  4. render()
  5. componentDidMount()

それぞれの関数の説明

constructor()

コンポーネっとのクラス自体のコンストラクタです。
super()を実行することが必須で、受け取ったpropsを処理する
stateの初期化もこの段階で行う。

static getDerivedStateFromProps(nextProps, prevState)

propsとstateを確認して、propsによってstateを書き換えるかどうかを決定する。
書き換えが必要であれば、書き換え後のstateを作成し、その値を返す。
書き換えが不要であれば、nullを返す。
この関数を通すため、コンストラクタがstateは初期化しておくべき!

componentWillMount() / UNSAFE_componentWillMount()

React0.17ではいしされる。
安全でないので使わない

render()

描画を扱う関数。

componentDidMount()

コンポーネントのMountionの処理が全て完了した際に呼び出される関数で、コンポーネントで一度だけ呼び出される。
一般には、コンストラクタでstateの箱を作り、componentDidMount()から必要なデータを取得しにいくような処理を記述。

Updating

  1. componentWillReceiveProps(props) / UNSAFE_componentWillReceiveProps()
  2. static getDerivedStateFromProps(nextProps, preState)
  3. shouldComponentUpdate(nextProps, nextState)
  4. componentWIllUpdate() / UNSAFE_componentWillUpdate()
  5. render()
  6. getSnapshotBeforeUpdate(prevProps, prevState)
  7. componentDidUpdate(prevProps, prevState,snapshot)

それぞれの関数の説明

shouldComponentUpdate(nextProps, nextState)

コンポーネントのアップデータが必要かどうかを判断するための関数。

getSnapshotBeforeUpdate(prevProps, prevState)

UPdatingの処理でrender()のあとに呼び出され、この関数でreturnされる値がcomponentDIdUpdateのsnapshotとして呼び出される。

Uumounting

  1. componentWIllUnmount()

それぞれの関数の説明

componentWIllUnmount()

メモリリークを抑えるために参照の削除などを実装

参照

React Native+Expoではじめるスマホアプリ開発

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VS CodeでESlint、Prettierを使用したReact環境を構築する

Reactの環境構築はcreate-react-appでとても簡単になりました。ここにコードチェック、整形ツールであるESlint、Prettier を導入してみます。

ESlint、Prettierを使用した環境構築方法やルールはプロジェクトによって違うと思うので、基本的な設定だけしています。プロジェクトに合わせて編集してください。

完成品はGitHubにアップしています。create-react-appで生成されるReactのアイコンなどは削除しています。

https://github.com/nineharker/react-vscode-eslint-prettier

環境構築

それでは環境構築していきましょう!パッケージ管理にyarnを使っていきますが、npmを使用している人は便宜読み替えてください。

VS CodeにESlintとPrettierの拡張機能を追加する

VS Codeの拡張機能としてESlintをインストールしましょう。
eslint-800x358.png

Prettierもインストールします。
prettier-800x358.png
これで必要な拡張機能はインストールできました。

create-react-appでプロジェクトを作成する

Reactプロジェクトを作成しましょう。

create-react-app sample

必要なパッケージをインストールする

create-react-appで作成された雛形では、すでにESLintに関するパッケージが導入されています。

create-react-appで作成したプロジェクトの場合、eslintとbabel-eslint、eslint-loaderをインストールしたらエラーが発生するのでインストールしないください。

yarn add --dev prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-react

eslint、prettierの設定ファイルを生成する

プロジェクトルートに.eslintrc.jsと.prettierrc配置してルールを書いていきます。 基本的な設定だけを書いています。

.eslintrc.js
module.exports = {
  "env": {
    "es6": true,
    "node": true
  },
  "parser": "babel-eslint",
  "plugins": [
    "react",
    "prettier"
  ],
  "parserOptions": {
    "version": 2018,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:prettier/recommended",
    "prettier/react"
  ],
  "rules": {
    "prettier/prettier": "error"
  }
}
.prettierrc
{
  "printWidth": 120,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5",
  "bracketSpacing": true,
  "jsxBracketSameLine": false
}

VS Codeの設定でセーブ時に整形するようにする

セーブ時に整形するようにVS Codeの設定を変更しましょう。VS Codeのデフォルトのフォーマット機能をオフにしています。

 {
   "javascript.format.enable": false,
   "eslint.autoFixOnSave": true
 }

おわり

お疲れ様でした!これで設定が完了です。App.jsなどのコンポーネントの拡張子はjsxに変更しましょう。

ESlint、Prettierの設定は大変ですが、その後の開発が圧倒的に楽になるのでぜひお試しください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gatsbyjs + Netlify + Contentful で Blogを作ってみた。

Gatsbyjs + Netlify + Contentful で Blogを作成

  • 今回作成するサイトのDemoです。
  • こちらが今回実際に作成したBlogです。

以前にgatsby-starter-blogでブログを作成したのですがCMSを利用したいと思い、
新たにgatsby-starter-gcnでブログを作成したので備忘録を残します。

利用したサービス

  • Gatsbyjs

GatsbyjsはReactを利用したモダンなサイトを高速に作成できるオープンソースフレームワークです。
Gatsbyjsでなにか作りたい方はテンプレートが豊富に用意されているので、そちらを利用するといいと思います。

  • Netlify

Netlifyは静的なサイトを高速で提供できるホスティングWebサービスです。
フロントエンドのビルド、デプロイ、ホスティングの全てを高速に行ってくれます。
また、NetlifyはGitHubリポジトリとリンクして、GitHubリポジトリにプッシュがある度にビルド・デプロイをしてくれます。

  • Contentful

ContentfulはAPIファーストなHeadless CMS(コンテンツ管理システム)です。コンテンツとはブログ記事などのことです。
WordPressなどのCMSとは異なり、開発者はREST API経由で記事(コンテンツ)を取得し、アプリケーションやデバイスのUIに反映させるというものです。
特徴はなんといってもAPIベースでコンテンツを管理・取得できるので、表示(フロントエンド)側での制約がなくなり、管理しているコンテンツをWebからでも、モバイルからでも取得し、表示することができる、つまり、フロントエンドとバックエンド切り離すことができるということです。
今回はその管理の分離を考えて、HeadlessCMSを利用することにしました。

ブログ作成手順

こちらからブログ作成の手順を残しておきます。

Gatsbyプロジェクトの作成

まずはこちらのリンクからGatsbyjsのテーマを選びます。
今回はGatsbyjs + contentful + Netlifyという構成にしたかったので、
gatsby-starter-gcnを選びました。

上記リンク先のSourceからGithubにとべるので、そちらのREADMEにしたがって作成していきます。

まずはプロジェクトを作成します。

$ gatsby new gatsby-starter-gcn(任意のプロジェクト名) https://github.com/ryanwiemer/gatsby-starter-gcn.git

上記コマンドでGatsbyのプロジェクトを作成します。(gatsby-cliが必要です。)

自分の場合は以下のエラーが出ましたが、現在の最新のnodejsのバージョン(14.7.0)に上げたらエラーがなくなりプロジェクトを作成できました。(エラーが確認できたnodejsのバージョンは13.2.0でした。)

/usr/local/lib/node_modules/gatsby-cli/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:53
        throw ex;
        ^

Error: No valid exports main found for '/usr/local/lib/node_modules/gatsby-cli/node_modules/@urql/core'
    at resolveExportsTarget (internal/modules/cjs/loader.js:611:9)
    at applyExports (internal/modules/cjs/loader.js:492:14)
    at resolveExports (internal/modules/cjs/loader.js:541:12)
    at Function.Module._findPath (internal/modules/cjs/loader.js:643:22)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:941:27)
    at Function.Module._load (internal/modules/cjs/loader.js:847:27)
    at Module.require (internal/modules/cjs/loader.js:1016:19)
    at require (internal/modules/cjs/helpers.js:69:18)
    at Object.<anonymous> (/usr/local/lib/node_modules/gatsby-cli/node_modules/urql/dist/urql.js:206:12)
    at Module._compile (internal/modules/cjs/loader.js:1121:30) {
  code: 'MODULE_NOT_FOUND'
}

無事プロジェクトが作成できたので次にいきます。

ContentfulのSetup

次にこちらにしたがってContetfulの設定をしていきます。

まずcontentfulのアカウントを作成し、空ページを作ります。

最初はチュートリアルページがあるかと思うので、そちらを削除し、画面左上の+ Create spaceを押して空ページを作成します。

ヘッダーのSpace Homeを押した際に以下のページが表示されていると思います。

Contentful空ページ作成後Home

次にContentfulのAPIKEYの設定をします。

$ npm run setup

上記のコマンドを叩くと

  • SPACE ID
  • Content Delivery API access token
  • Content Preview API access token
  • Content Management API access token

を聞かれるのでそれぞれContentfulのSpace Settings → API keysから取得して入力します。
Content delivery / preview tokensとContent management tokensのタブの情報が両方必要なので注意してください(tokenはなければ作成してください)。

これでContentfulの内容が反映されるようになったので、

$ gatsby develop

でローカルプロジェクトを立ち上げて http://localhost:8000/ でローカルでコンテンツの確認をすることができるようになりました。

あとはNetlifyを使ってBuild・Deployの設定をします。

NetlifyのSetup

こちらもREADMEに沿って設定していきます。
gatsby-starter-gcnのNetlifyの設定手順

Netlifyのアカウントを作成したらこちらからNetlifyとgitプロジェクトの紐付けをしていきます。

リンク先の画面でGithubを選択し、該当のGithubリポジトリを選択します。
次の画面でDeploy Siteを押し、サイトのDeployを開始します。

Deployが開始されましたがそのままではビルドがこけてしまうので環境変数を設定します。
NetlifyトップページのSettings → Build & Deploy → Build Environment Variables.から
READMEにならってSPACE_IDとACCESS_TOKENを設定します。

その後Deploysから手動でDeployを実行します。
すると今度はDeployが成功していると思います。

これでプロジェクトのmasterブランチにpushすれば本番環境にbuild・deployされるようになりました。

最後にContentfulでWebhookの設定をしておきます。

ContentfulでWebhookの設定

このままだとContentfulで記事を投稿しても手動でdeployしないといけないので、
Contentfulで記事が投稿、削除されたらNetlifyに通知し、Deployされるようにしたいと思います。

ContentfulのWebhooksの設定

例によってこちらもREADMEどおりに進めます。

Netlifyの Settings → Build & Deploy → Build hooksからbuildhookを新たに作成します。

こちらで作成したBuildhookのURLをContentfulのWebhooksに設定します。
ContentfulでSettings→Webhooksを選択し、画面右側のNetlifyのテンプレートを選択します。
以下のNetlifyのAddを選択。
ContentfulでNetlifyのWebhooksテンプレート

TriggersにはPublish Unpublish Deleteを選択しておきます。

これでWebhooksの設定も完了したのでContentful上で記事の投稿・削除をすればNetlifyに通知され
自動でDeployされるようになりました!

[FYI]Contact Form

BlogのContactで送られたものはNetlifyのFormsで確認できますが、こちらの通知も設定できますので通知してほしいという方はREADMEにならって設定してください。
NetlifyのFormsの通知設定

ブログの作成手順は以上になります。

なににおいても継続することが成功への近道だと思うので、
しっかりアウトプットしていきたいと思います!

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む