◆ Switch の中の Route の有無を切り替えたい
◆ ひとつの条件分岐で複数の Route を出力するために <></> を使うと fragment になる
◆ fragment は children の中で展開されず Route でも Redirect でもない要素が混ざる
◆ Route ひとつずつ条件を書くか事前にフラットな配列で Route を用意して Switch の children とする

問題

こういう感じで React Router を使ったコンポーネントがあります

import { Route, Switch, Link, MemoryRouter as Router } from "react-router-dom"

export default function App() {
return (
<Router>
<div style={{ display: "flex", gap: "20px" }}>
<Link to="/">Top</Link>
<Link to="/a/x">/a/x</Link>
<Link to="/a/y">/a/y</Link>
<Link to="/b/x">/b/x</Link>
<Link to="/b/y">/b/y</Link>
</div>
<Switch>
<Route exact path="/">
Route:/
</Route>
<Route exact path="/a/x">
Route:/a/x
</Route>
<Route exact path="/a/y">
Route:/a/y
</Route>
<Route exact path="/b/x">
Route:/b/x
</Route>
<Route exact path="/b/y">
Route:/b/y
</Route>
</Switch>
</Router>
)
}

上の方にあるリンクでページが切り替わって 今のルートが表示されます

「/a」関係と「/b」関係のページは 使う使わないを設定できて使わないなら非表示にしたいです
そこでコンポーネントをこういう風に修正しました

export default function App() {
return (
<Router>
<div style={{ display: "flex", gap: "20px" }}>
<Link to="/">Top</Link>
{config.use_a && (
<>
<Link to="/a/x">/a/x</Link>
<Link to="/a/y">/a/y</Link>
</>
)}
{config.use_b && (
<>
<Link to="/b/x">/b/x</Link>
<Link to="/b/y">/b/y</Link>
</>
)}
</div>
<Switch>
<Route exact path="/">
Route:/
</Route>
{config.use_a && (
<>
<Route exact path="/a/x">
Route:/a/x
</Route>
<Route exact path="/a/y">
Route:/a/y
</Route>
</>
)}
{config.use_b && (
<>
<Route exact path="/b/x">
Route:/b/x
</Route>
<Route exact path="/b/y">
Route:/b/y
</Route>
</>
)}
</Switch>
</Router>
)
}

とりあえず config は

const config = { use_a: true, use_b: true }

とします
両方とも true なので 条件分岐を作る前と同じように動く と思っていたのですが /b/x と /b/y が表示されなくなりました

原因

React Router では Switch コンポーネントの中には Route か Redirect コンポーネントのみ配置するというルールがあるのでもしかしたらという気はしていたのですが あたりでした
ソースコードを見ると Switch コンポーネントは React.Children.forEach を使って props.children を見ていく処理をしています

https://github.com/remix-run/react-router/blob/v5.2.0/packages/react-router/modules/Switch.js
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;

const path = child.props.path || child.props.from;

match = path
? matchPath(location.pathname, { ...child.props, path })
: context.match;
}
});

Switch コンポーネントの props.children を表示するとこういう感じです

0: {$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …}
1: {$$typeof: Symbol(react.element), type: Symbol(react.fragment), key: null, ref: null, props: {…}, …}
2: {$$typeof: Symbol(react.element), type: Symbol(react.fragment), key: null, ref: null, props: {…}, …}
length: 3

要素が 3 つの配列です
「<></>」でまとめた部分は配列に展開されず fragment となっています
この状態で forEach をするせいで /a/x の Route ではなく fragment が現在の location にマッチするかのチェックをしています
当然 props に必要なプロパティがないので 正しく動作しません

/a 系の fragment を fragment1 とし /b 系の fragment を fragment2 とすると
fragment1 のチェック時に path が未指定として扱われるので ここで常にマッチしたことになります
その結果 /a/x と /a/y は表示できます
しかし fragment1 に常に一致するので fragment2 のチェックは行われません
/b/x のルートを開いたときには fragment1 にマッチしているのに /a/x でも /a/y でもないので Route コンポーネントが何もレンダリングしないことで空になります

fragment1 のところに Route 以外の常に表示される div を入れると /b/x や /b/y のルートを開いたときに常に表示されます

{config.use_a && (
<>
<Route exact path="/a/x">
Route:/a/x
</Route>
<Route exact path="/a/y">
Route:/a/y
</Route>
<div>fragment1 is active</div>
</>
)}

対処

「<></>」を使わなければいいので まとめずにルートをひとつごとに分岐させればちゃんと動きます

<Switch>
<Route exact path="/">
Route:/
</Route>
{config.use_a && (
<Route exact path="/a/x">
Route:/a/x
</Route>
)}
{config.use_a && (
<Route exact path="/a/y">
Route:/a/y
</Route>
)}
{config.use_b && (
<Route exact path="/b/x">
Route:/b/x
</Route>
)}
{config.use_b && (
<Route exact path="/b/y">
Route:/b/y
</Route>
)}
</Switch>

Switch 直下は Route にして内側で分岐もできます

<Switch>
<Route exact path="/">
Route:/
</Route>
<Route exact path="/a/x">
{config.use_a && (
Route:/a/x
)}
</Route>
<Route exact path="/a/y">
{config.use_a && (
Route:/a/y
)}
</Route>
<Route exact path="/b/x">
{config.use_b && (
Route:/b/x
)}
</Route>
<Route exact path="/b/y">
{config.use_b && (
Route:/b/y
)}
</Route>
</Switch>

ただ これだと条件を満たさなくても Route には一致してしまいます
今回の例だと問題ないのですが 一番最後に常にマッチするルートを配置して どのルートにもマッチしない場合に 404 エラーみたいな画面を作る場合には向いていません

他には 事前に Route の配列を作っておいて 配列を children に埋め込む方法にすれば同じ条件の分岐をルートごとに書かなくて済みます

const routes = []
const add = (fragment) => routes.push(...[].concat(fragment.props.children))

add(
<>
<Route exact path="/">
Route:/
</Route>
</>
)
config.use_a && add(
<>
<Route exact path="/a/x">
Route:/a/x
</Route>
<Route exact path="/a/y">
Route:/a/y
</Route>
</>
)
config.use_b && add(
<>
<Route exact path="/b/x">
Route:/b/x
</Route>
<Route exact path="/b/y">
Route:/b/y
</Route>
</>
)

return (
<Switch>{routes}</Switch>
)

children を直接操作するものがあると こういう特殊な扱いをしないといけないのが扱いづらいところですね
children を使うのではなく Route が Context を使って直近の祖先の Switch の情報を共有して表示するしないを切り替えるとかしてくれたらいいのですけど