かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

WordPress REST API を無効化する

WordPress v6.1.1

rest_authentication_errors フィルターを使う

apply_filters( 'rest_authentication_errors', WP_Error|null|true $errors )
This is used to pass a WP_Error from an authentication method back to the API.

Authentication methods should check first if they’re being used, as multiple authentication methods can be enabled on a site (cookies, HTTP basic auth, OAuth). If the authentication method hooked in is not actually being attempted, null should be returned to indicate another authentication method should check instead.
$errors
WP_Error if authentication error, null if authentication method wasn't used, true if authentication succeeded.
cf. rest_authentication_errors | Hook | WordPress Developer Resources

  • null … 認証のチェックがまだされていない
  • boolean … 認証のチェック済み
    • true … 認証に成功
    • false … 認証に失敗
  • WP_Error … エラーが発生

WordPressREST API で認証を確認するために通過する箇所へのフィルター
この箇所で WP_Error を返すことで全ての API をエラーにすることができるという事っぽい

<?php
function disable_rest_api() {
  return new WP_Error(
    'disabled',
    'REST API is disabled.',
    array( 'status' => rest_authorization_required_code() )
  );
}
add_filter( 'rest_authentication_errors', 'disable_rest_api' );

API にアクセスするとエラーメッセージが返ってくる

/wp-json/wp/v2/posts

{"code":"disabled","message":"REST API is disabled.","data":{"status":403}}

/wp-json/wp/v2/users

{"code":"disabled!","message":"REST API is disabled.","data":{"status":403}}

できた ( ᐢ˙꒳​˙ᐢ )

📝 Tips: WordPress v4.7.0 から rest_enabled を使う方法は非推奨になっている

<?php
add_filter( 'rest_enabled', '__return_false' );

上記のコードは下記の notice が表示される

Deprecated: フック rest_enabled は、バージョン 4.7.0 から非推奨になりました ! 代わりに rest_authentication_errors を使用してください。 REST API を完全に無効化することはできなくなりました。代わりに rest_authentication_errors フィルターを使って API へのアクセスを制限できます。

📝 Tips: rest_endpoints フィルターで API をカスタマイズする方法

rest_endpoints フィルターで REST API のエンドポイントをカスタマイズできる。その中で API を無効化にする方法

<?php
function filter_rest_endpoints() {
  if ( isset( $endpoints['/wp/v2/users'] ) ) {
    unset( $endpoints['/wp/v2/users'] );
  }
  if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) ) {
    unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
  }
  return $endpoints;
}
add_filter( 'rest_endpoints', 'filter_rest_endpoints', 10, 1 );

とりあえずまるっと無効化してしまうなら rest_authentication_errors フィルターで良さそう

おわり ₍ ᐢ. ̫ .ᐢ ₎


[参考]

React 👻 jotai 👻 Provider 完全に理解した!

前回までのあらすじ

React の軽量状態管理ライブラリ jotai に入門しました
今回は jotai の Provider と store の使い方について試したみたのメモです

環境
  • jotai v 2.0.3
  • React v 18.2.0
  • TypeScript v 4.9.3

結論

  1. jotai の atom はデフォルトでは global として扱われる
  2. Provider の中で使用された atom は Provider の中で閉じられる
  3. Provider で囲われたコンポーネントから Provider の外の global な atom にはアクセスできない
  4. Provider の外の atom にアクセスしたい場合は、アクセス可能な atom を React の context に store として登録し context 経由でならアクセス可能

Provider で囲われてない atom は global

import { atom, useAtom, useAtomValue } from "jotai";

const countAtom = atom<number>(0);

const Counter: FC = () => {
  const [count, setCount] = useAtom(countAtom);
  const handleIncrement = () => {
    setCount((val) => val + 1);
  };

  return (
    <div>
      <label>Local Count: {count}</label>
      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
    </div>
  );
};

export default function App() {
  const count = useAtomValue(countAtom);
  return {
    <div>
      <code>Count Atom: {count}</code>
      Counter 1: <Counter />
      Counter 2: <Counter />
    </div>
  };
}

Provider で囲われてないので countAtom は全て共通になっている (Counter 1 で更新しても Counter 2 で更新しても全ての count が同期される)

Provider は Context を作成する

Provider で囲った箇所は Context が作成され、その中で useAtom しているものは元の atom 設定が同じ変数でも別の状態として扱われる

import { atom, useAtom, useAtomValue, Provider } from "jotai";

const countAtom = atom<number>(0);

// 略

export default function App() {
  const count = useAtomValue(countAtom);
  return {
    <div>
      <code>Count Atom: {count}</code>
      Counter 1: <Counter />
      Counter 2: <Counter />
      <Provider>
         In Provider Counter
         <Counter />
         {/* countAtom が別ものとして扱われる */}
      </Provider>
    </div>
  };
}

jotai Provider
Provider で囲った箇所に Context が作成されている

👇 サンプル

Provider 外の atom にアクセスする

Provider の内部から外の atom にアクセスするには、

  1. 全体を Context で囲いそこに store としてアクセス可能な atom を登録する
  2. コンポーネントでは useContext を使い store を取得しする
  3. store を指定した方法 (useAtom(atom, { store });) で使いたい atom を取得する
import { createContext, useContext, FC} from "react";
import { atom, createStore, useAtom, useAtomValue, Provider } from "jotai";

// atom の作成
const countAtom = atom<number>(0);
// root に持たせる store と context の作成
const store = createStore();
store.set(countAtom, 1); // atom と初期値を設定
const RootContext = createContext<typeof store>(store);

export default function App(): JSX.Element {
  return (
    <div>
      {/* 全体を Context.Provider で囲う */}
      <RootContext.Provider value={store}>
        Parent Counter: <ParentCounter />
        {/* jotai の Provider でスコープを作成 */}
        <Provider>
          Child Counter1: <ChildCounter />
        </Provider>
        <Provider>
          Child Counter2: <ChildCounter />
        </Provider>
      </RootContext.Provider>
    </div>
  );
}

// Child Counter
function ChildCounter(): JSX.Element {
  // RootContext に登録されている countAtom の取得
  const store = useContext(RootContext);
  const parentCount = useAtomValue(countAtom, { store });

  // useAtom したものは Provider 内の local な atom になる
  const [count, setCount] = useAtom(countAtom);
  const handleIncrement = () => {
    setCount((val) => val + 1);
  };
  
  return (
    <div>
      <code>Parent Count: {parentCount}</code>
      <label>Local Count: {count}</label>
      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
    </div>
  );
}

// Parent Counter
function ParentCounter(): JSX.Element {
  // useAtom(countAtom) は store とは別の local atom になる
  // 明示的に useContext で store を取得して useAtom に store を渡す必要がある
  const [count, setCount] = useAtom(countAtom);
  // => これは store に登録された atom とは別物扱いになる
  
  const store = useContext(RootContext);
  const [rootCount, setRootCount] = useAtom(countAtom, { store });

  const handleIncrement = () => {
    setRootCount((val) => val + 1);
  };

  return (
    <div>
      <label>Root Count: {rootCount}</label>
      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
    </div>
  );
}

Provider 外の Atom にアクセスするサンプル

📝 jotai v1系 では Provider に scope プロパティを持たせることで実現していたが、v2系では廃止されている

Provider's scope prop
Previous API

const myScope = Symbol()
  // Parent component
  <Provider scope={myScope}>
    ...
  </Provider>

  // Child component
  useAtom(..., myScope)

New API

const MyContext = createContext()
const store = createStore()
  // Parent component
  <MyContext.Provider value={store}>
    ...
  </MyContext.Provider>

  // Child Component
  const store = useContext(MyContext)
  useAtom(..., { store })

cf. v2 API migration — Jotai

所管

Provider があるのに Provider の外の atom にアクセスするには React の Context を使うのが少し気持ち悪い気もしましたが、元の React の実装を利用することでライブラリのサイズを抑えてるんだろうな〜と感じました!
やり方が分かってしまえば Provider で atom のスコープを区切れるので便利そうです。一方で自由に簡単にアクセスができるとカオスが生まれる原因にもなるので、Recoil と同様に atom へのアクセスはカスタム hooks の中に閉じ込めてインターフェイスを絞るのが良さそうだな〜と思いました。

jotai の v1 系と v2 系とで API が結構ガラッと変わっていて検索して出てくる方法 (v1 系の書き方) だとエラーになってしまって、なんでダメなん?と v2 API migration のドキュメントに気づくまで結構ハマってしまいました… (急いでる時ほどドキュメントはちゃんと読もうな…

おわり

👉 jotai の基本 React 軽量状態管理ライブラリ👻 jotai 👻 さわってみた! - かもメモ
👉 つづき React 👻 jotai を使うと localStorage を使った永続化が簡単だった件について - かもメモ


俺もおしまい!

React 軽量状態管理ライブラリ👻 jotai 👻 さわってみた!

普段 React の状態管理には Recoil を愛用しています
jotai という Recoil ライクな軽量ライブラリがあると聞いたので試してみました

👻 jotai

Recoil vs jotai vs Redux @reduxjs/toolkit vs jotai vs react-redux vs recoil vs redux | npm trends

この記事を書いた時点では Recoil 23.5KB で jotai が 2.3KB 確かに小さいです!! (Recoil が思ったより大きかった)

環境
  • jotai 2.0.3
  • React 18.2.0
  • TypeScript: 4.9.3

状態 (state) = atom

jotai では Recoil と同様に state は atom と呼ぶ

import { atom } from 'jotai';
const countAtom = atom<number>(0);

Recoil と違って key の設定が必要ない

atom の使い方 useAtom

useAtom フックで atom と更新関数が取得できる。
React の useState, Recoil の useRecoilState と同じように扱える

import { atom, useAtom } from 'jotai';
const countAtom = atom<number>(0);

const Component: React.FC = () => {
  const [count, setCount] = useAtom(countAtom);
  const handleInclremt = () => setCount((value) => value + 1);
  const handleReset = () => setCount(0);

  return (
    <div>
      <div>Count: {count}</div>
      <button type="button" onClick={handleInclremt}>Increment</button>
      <button type="button" onClick={handleReset}>Reset</button>
    </div>
  );
};

値だけ使いたい時は useAtomValue(), 更新関数だけ使いたい時は useSetAtom() を使える。(Recoil で言う所の useRecoilValue()useSetRecoilState())
Recoil と同様にコンポーネントで直接 useAtom は呼ばないようにして、カスタムフックの中に閉じ込めてインターフェイスを制限するのが良さそう

依存のある atom

Recoil では selector で表現できるものを jotai では atom((get) => {}) で表現できる

import { atom } from "jotai";
const countAtom = atom<number>(0);
const doubleCountAtom = atom<number>((get) => get(countAtom) * 2);

以降の扱い方は通常の atom と同じ

import { atom, useAtom } from 'jotai';
const countAtom = atom<number>(0);
const doubleCountAtom = atom<number>((get) => get(countAtom) * 2);

const Component: FC = () => {
  const [count, setCount] = useAtom(countAtom);
  const [doubleCount, setDoubleCount] = useAtom(doubleCountAtom);
  // setDoubleCount(value) は never なのでエラーになる
  const handleInclremt = () => setCount((value) => value + 1);
  const handleReset = () => setCount(0);

  return (
    <div>
      <div>Count: {count} / Double: {doubleCount}</div>
      <button type="button" onClick={handleInclremt}>Increment</button>
      <button type="button" onClick={handleReset}>Reset</button>
    </div>
  );
};

第一引数が get 関数だけの atom は readonly となり set 関数は never で使えなくなっているが、個人的には Recoil の selector と別物になっている方が間違いなく見通しが良いように感じた

依存元を更新できる atom readWriteAtom

const readOnlyAtom = atom((get) => get(priceAtom) * 2)
const writeOnlyAtom = atom(
  null, // it's a convention to pass `null` for the first argument
  (get, set, update) => {
    // `update` is any single value we receive for updating this atom
    set(priceAtom, get(priceAtom) - update.discount)
  }
)
const readWriteAtom = atom(
  (get) => get(priceAtom) * 2,
  (get, set, newPrice) => {
    set(priceAtom, newPrice / 2)
    // you can set as many atoms as you want at the same time
  }
)

cf. atom — Jotai, primitive and flexible state management for React

jotai では第一引数が get 関数の atom も第二引数を指定することで、set 関数で依存元を更新できる atom が作成できる。逆に第一引数を null にすると別の atom を更新できるだけの atom を作成できる。

readWriteAtom のサンプル

import { atom, useAtom } from "jotai";

const kgAtom = atom<number>(0);
const gramAtom = atom(
  (get) => get(kgAtom) * 1000,
  (_get, set, newValue: number) => {
    return set(kgAtom, newValue / 1000);
  }
);

export const Converter: FC = () => {
  const [kg, setKg] = useAtom(kgAtom);
  const [gram, setGram] = useAtom(gramAtom);
  const handleChangeKg = (evt: React.ChangeEvent<HTMLInputElement>) => {
    setKg(Number(evt.currentTarget.value));
  };
  const handleChangeGram = (evt: React.ChangeEvent<HTMLInputElement>) => {
    setGram(Number(evt.currentTarget.value));
  };

  return (
    <div>
      <label>
        <input type="number" value={kg} onChange={handleChangeKg} />
          kg
       </label><label>
        <input type="number" value={gram} onChange={handleChangeGram}/>
          g
      </label>
   </div>
  );
};

このサンプルでは依存先である gramAtom から依存元の kgAtom の値を更新することができます
カスタム hook で対象の atom を更新するだけのインターフェイスを作れるから個人的に 依存先の atom から更新ができる Read-Write な atom の使い所はちょっと思いつきませんでした…

非同期で更新される atom

jotai では atom の第一関数を非同期関数にすることで非同期で取得できる値の状態を扱える
loadable を使うことで loading 中やエラーの扱いを簡単にすることができます

loadable
If you don't want async atoms to suspend or throw to an error boundary (for example, for finer-grained control of loading and error logic), you can use the loadable util.
It would work the same way for any atom. Simply wrap your atoms with the loadable util. It returns a value with one of three states: loading, hasData and hasError.
cf. Async — Jotai, primitive and flexible state management for React

Pokemon API からデータを取得する例

atoms.ts

import { atom } from "jotai";
import { loadable } from "jotai/utils";

const API = "https://pokeapi.co/api/v2/pokemon/" as const;

export const queryAtom = atom<number | undefined>(undefined);

const asyncAtom = atom(async (get) => {
  const query = get(queryAtom);
  if (!query) {
    return;
  }
  const data = await fetch(`${API}${query}`).then((res) => res.json());

  return {
    name: data.name || "",
    img: data.sprites?.front_default || ""
  };
});
// 非同期な atom を loadable でラップする
export const loadableAtom = loadable(asyncAtom);

コンポーネント

import { useRef, FC } from "react";
import { useAtomValue, useSetAtom } from "jotai";
import { loadableAtom, queryAtom } from "./atoms";

const LoadContent: FC = () => {
  const { state, data, error } = useAtomValue(loadableAtom);
  // data, error が Property does not exist on type になってしまう…
  if (state === "hasError") {
    if (error instanceof Error) {
      return (<div>Error: {error.message}</div>);
    }
    return <div>Error</div>;
  }
  if (state === "loading") {
    return <div>Loading...</div>;
  }
  if (!data) {
    return null;
  }
  return (
    <div>
      <img src={data.img} alt={data.name} />
      <label>{data.name}</label>
    </div>
  );
};

const SearchForm: FC = () => {
  const setQuery = useSetAtom(queryAtom);
  const inputRef = useRef<HTMLInputElement>(null);
  const handleChangeQuery = (evt: React.MouseEvent) => {
    if (!inputRef.current || !inputRef.current.value) {
      return;
    }
    const query = inputRef.current.value;
    setQuery(Number(query));
  };

  return (
    <div>
      <label>Pokemon No.</label>
      <input ref={inputRef} type="number" min="1" />
      <button type="button" onClick={handleChangeQuery}>
        Search
      </button>
    </div>
  );
};

export const PokemonSearch: FC = () => {
  return (
    <div>
      <LoadContent />
      <SearchForm />
    </div>
  );
}

フォームに ポケモンのナンバーを入れて検索ボタンを押すと queryAtom が変更され、queryAtom に依存してしている asyncAtom に変更が伝播して再 fetch が実行され検索結果が更新される

jotai だけで useEffect を使わずに非同期処理が扱えるので atom がネストさえ秘匿してしまえば見通しは良さそう。
ただ react-querySWR といった非同期に特化したようなライブラリがあるので使い時は限定されそう…

Sample


📝 TypeScript での Tips

Derived atoms are also type inferred and explicitly typed

const asyncStrAtom = atom(async () => 'foo')
const writeOnlyAtom = atom(null, (_get, set, str: string) => set(fooAtom, str))
const readWriteAtom = atom<string, number>(
  (get) => get(strAtom),
  (_get, set, num) => set(strAtom, String(num))
)

cf. TypeScript — Jotai, primitive and flexible state management for React

TypeScript (TypeScript v4.9.3) で read-write な atom を定義する時に公式にある方法でも型エラーが発生したのでメモ

1. atom<type, type> は set関数が never になり使えない

const gramAtom = atom<number, number>(
  (get) => get(kgAtom) * 1000,
  (_get, set, newValue) => set(kgAtom, newValue / 1000)
);

const Component: FC = () => {
  const [gram, setGram] = useAtom(gramAtom);
  const handleChangeGram = (evt: React.ChangeEvent<HTMLInputElement>) => {
    setGram(Number(evt.currentTarget.value));
    // => This expression is not callable. Type 'never' has no call signatures.
  };
  // … 
};

ドキュメントに書かれている指定方法だが、set 関数の setGram が never 型 (const setGram: never) と推論されてしまい、set関数を使おうとした箇所で This expression is not callable. Type 'never' has no call signatures. のType エラーが発生してしまった

2. atom<type> だと第二引数で Type error になる

const gramAtom = atom<number>(
  (get) => get(kgAtom) * 1000,
  (_get, set, newValue) => set(kgAtom, newValue / 1000)
  // => Expected 1 arguments, but got 2
);

write を指定できる第二引数の指定箇所が Expected 1 arguments, but got 2 になってしまう
また更新時に渡される値が入る newValueunknown 型になる

解決方法 Generics で型指定しない

型指定しないといい感じに推論される… どうして…

const gramAtom = atom(
  (get) => get(kgAtom) * 1000,
  (_get, set, newValue) => set(kgAtom, newValue / 1000)
);

const Component: FC = () => {
  const [gram, setGram] = useAtom(gramAtom);
  // const setGram: SetAtom<[newValue: unknown], void>
  // …
};

これは Type error が回避される。但し newValue が unknown 型として推論される
👇 関数定義の部分に型を指定すればOK

const gramAtom = atom(
  (get) => get(kgAtom) * 1000,
- (_get, set, newValue) => set(kgAtom, newValue / 1000)
+ (_get, set, newValue: number) => set(kgAtom, newValue / 1000)
);

const Component: FC = () => {
  const [gram, setGram] = useAtom(gramAtom);
  // const setGram: SetAtom<[newValue: number], void>
  // …
};

newValue に直接型を指定すれば set 関数もいい感じに形推論してくれるようになりました!
atom だけで色々できるようにした結果、型定義が難しくなってるのかもしれません

所管

ざっと触っただけですが jotai は Recoil と同じように使うことができそうです!
jotai は React の Context を利用したライブラリで Recoil に比べて軽量なのは魅力的だなと思いました。一方で依存のある状態も全て atom として表現するので TypeScript の型がうまく推論されない部分があったり、 atomselector とで別になっている Recoil の方が見通しは良さそうという印象も持ちました。 また、v1 と v2 で API が大きく変更されてたりとするので、現状では個人的な小さなプロジェクトで実験的に使うとかなら良さそうという肌感でした

他にも Provider や localStorage, sessionStorage を利用した state を作成できる utility 機能もあるので使いこなせると出来ることは多そうです! (Provider については調べてたのですが記事が長くなりすぎそうだったので、別記事にしようと思います)

Recoil の記事書こうと思ったまま下書きに放置してたことを思い出した…
おわり つづく!

👇 つづき


基礎から学ぶ React/React Hooks

基礎から学ぶ React/React Hooks

Amazon

[asin:B0BN5BF2SF:detail]