web-dev-qa-db-ja.com

React Router v4ルートレベルでアクセスできないネストされたマッチパラメータ

テストケース

https://codesandbox.io/s/rr00y9w2wm

再現する手順

OR

予想される行動

  • match.params.topicIdは、両方の親から同一である必要がありますTopicsコンポーネントは、Topicコンポーネント内でアクセスする場合、match.params.topicIdと同じである必要があります

実際の行動

  • match.params.topicIdTopicコンポーネント内でアクセスした場合未定義
  • match.params.topicIdTopicsコンポーネント内でアクセスされた場合レンダリング

this closed issue から、これは必ずしもバグではないことを理解しています。

この要件は、親レベルのコンポーネントTopicsmatch.params.paramIdにアクセスする必要があるミルWebアプリケーションで実行を作成するユーザーの間で非常に一般的です。ここで、paramIdは一致するURLパラメーターですネストされた(子)コンポーネントTopic

const Topic = ({ match }) => (
  <div>
    <h2>Topic ID param from Topic Components</h2>
    <h3>{match.params.topicId}</h3>
  </div>
);

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <h3>{match.params.topicId || "undefined"}</h3>
    <Route path={`${match.url}/:topicId`} component={Topic} />
    ...
  </div>
);

一般的な意味では、TopicsはDrawerまたはNavigation Menuコンポーネントであり、Topicは、私が開発しているアプリケーションのように、任意の子コンポーネントです。子コンポーネントには独自の:topicId paramがあり、これには独自の(たとえば)<Route path="sections/:sectionId" component={Section} /> Route/Componentがあります。

さらに苦しいのは、ナビゲーションメニューがコンポーネントツリーと1対1の関係を持つ必要がないことです。メニューのルートレベルの項目(TopicsSectionsなど)は、nested構造に対応する場合があります(Sectionsは、トピック/topics/:topicId/sections/:sectionIdにのみ表示されますが、独自のナビゲーションバーのタイトルSectionsでユーザーが使用できる正規化されたリスト)。したがって、Sectionsをクリックすると、itが強調表示され、Sectionsトピック

sectionIdまたはsectionsパスがアプリケーションのルートレベルにあるナビゲーションバーコンポーネントに使用できない場合、このような一般的なユースケースでは このようなハッキング を記述する必要があります。

私はReact Routerの専門家ではありません。だから誰かがこのユースケースに適切でエレガントなソリューションをベンチャーできるなら、これは実り多い努力だと思います。

  • history.location.pathnameではなく、matchを使用します
  • window.location.xxxを手動で解析するようなハッキング手法は含まれません
  • this.props.location.pathnameを使用しません
  • path-to-regexpなどのサードパーティライブラリを使用しません
  • クエリパラメータを使用しません

その他のハッキング/部分的な解決策/関連する質問:

  1. React Router v4-現在のルートを取得する方法?

  2. React Router v4 globalネストされたルートの子と一致しない

TIA!

10
nikjohn

クエリパラメータ?を利用して、親と子が現在選択されているtopicにアクセスできるようにしてください。残念ながら、react-router-domはクエリを自動的に解析しないため、モジュール qs を使用する必要があります(react-router v3は行います)。

作業例: https://codesandbox.io/s/my1ljx40r9

URLは連結された文字列のように構造化されます。

topic?topic=props-v-state

次に、&を使用してクエリに追加します。

/topics/topic?topic=optimization&category=pure-components&subcategory=shouldComponentUpdate

✔ルートURL処理に一致を使用

this.props.location.pathnameを使用しません(this.props.location.searchを使用します)

qsを使用してlocation.searchを解析します

✔ハッキングアプローチを含まない

Topics.js

import React from "react";
import { Link, Route } from "react-router-dom";
import qs from "qs";
import Topic from "./Topic";

export default ({ match, location }) => {
  const { topic } = qs.parse(location.search, {
    ignoreQueryPrefix: true
  });

  return (
    <div>
      <h2>Topics</h2>
      <ul>
        <li>
          <Link to={`${match.url}/topic?topic=rendering`}>
            Rendering with React
          </Link>
        </li>
        <li>
          <Link to={`${match.url}/topic?topic=components`}>Components</Link>
        </li>
        <li>
          <Link to={`${match.url}/topic?topic=props-v-state`}>
            Props v. State
          </Link>
        </li>
      </ul>
      <h2>
        Topic ID param from Topic<strong>s</strong> Components
      </h2>
      <h3>{topic && topic}</h3>
      <Route
        path={`${match.url}/:topicId`}
        render={props => <Topic {...props} topic={topic} />}
      />
      <Route
        exact
        path={match.url}
        render={() => <h3>Please select a topic.</h3>}
      />
    </div>
  );
};

別のアプローチは、HOCにパラメーターを保存するstateを作成し、パラメーターが変更されたときに子が親のstateを更新することです。

URLはフォルダーツリーのように構造化されています:/topics/rendering/optimization/pure-components/shouldComponentUpdate

作業例: https://codesandbox.io/s/9joknpm9jy

✔ルートURL処理に一致を使用

this.props.location.pathnameを使用しません

✔オブジェクト間の比較にlodashを使用

✔ハッキングアプローチを含まない

Topics.js

import map from "lodash/map";
import React, { Fragment, Component } from "react";
import NestedRoutes from "./NestedRoutes";
import Links from "./Links";
import createPath from "./createPath";

export default class Topics extends Component {
  state = {
    params: "",
    paths: []
  };

  componentDidMount = () => {
    const urlPaths = [
      this.props.match.url,
      ":topicId",
      ":subcategory",
      ":item",
      ":lifecycles"
    ];
    this.setState({ paths: createPath(urlPaths) });
  };

  handleUrlChange = params => this.setState({ params });

  showParams = params =>
    !params
      ? null
      : map(params, name => <Fragment key={name}>{name} </Fragment>);

  render = () => (
    <div>
      <h2>Topics</h2>
      <Links match={this.props.match} />
      <h2>
        Topic ID param from Topic<strong>s</strong> Components
      </h2>
      <h3>{this.state.params && this.showParams(this.state.params)}</h3>
      <NestedRoutes
        handleUrlChange={this.handleUrlChange}
        match={this.props.match}
        paths={this.state.paths}
        showParams={this.showParams}
      />
    </div>
  );
}

NestedRoutes.js

import map from "lodash/map";
import React, { Fragment } from "react";
import { Route } from "react-router-dom";
import Topic from "./Topic";

export default ({ handleUrlChange, match, paths, showParams }) => (
  <Fragment>
    {map(paths, path => (
      <Route
        exact
        key={path}
        path={path}
        render={props => (
          <Topic
            {...props}
            handleUrlChange={handleUrlChange}
            showParams={showParams}
          />
        )}
      />
    ))}
    <Route
      exact
      path={match.url}
      render={() => <h3>Please select a topic.</h3>}
    />
  </Fragment>
);
7
Matt Carlotta

React-routerは、一致した子Routeの一致パラメーターを提供しませんが、現在の一致に基づいてパラメーターを提供します。したがって、次のようなルート設定がある場合

<Route path='/topic' component={Topics} />

Topicsコンポーネントには、次のようなルートがあります

<Route path=`${match.url}/:topicId` component={Topic} />

URLが/topic/topic1は内部ルートに一致しましたが、トピックコンポーネントの場合、一致したルートはまだです、/topicしたがって、その中にパラメータはありません。これは理にかなっています。

トピックコンポーネントで一致する子ルートのパラメーターを取得する場合は、React-routerが提供するmatchPathユーティリティを使用し、パラメーターを取得する子ルートに対してテストする必要があります。

import { matchPath } from 'react-router'

render(){
    const {users, flags, location } = this.props;
    const match = matchPath(location.pathname, {
       path: '/topic/:topicId',
       exact: true,
       strict: false
    })
    if(match) {
        console.log(match.params.topicId);
    }
    return (
        <div>
            <Route exact path="/topic/:topicId" component={Topic} />
        </div>
    )
}

編集:

任意のレベルですべてのパラメーターを取得する1つの方法は、コンテキストを利用し、コンテキストプロバイダーで一致したときにパラメーターを更新することです。

Routeを正しく動作させるには、Routeのラッパーを作成する必要があります。典型的な例は次のようになります

RouteWrapper.jsx

import React from "react";
import _ from "lodash";
import { matchPath } from "react-router-dom";
import { ParamContext } from "./ParamsContext";
import { withRouter, Route } from "react-router-dom";

class CustomRoute extends React.Component {
  getMatchParams = props => {
    const { location, path, exact, strict } = props || this.props;
    const match = matchPath(location.pathname, {
      path,
      exact,
      strict
    });
    if (match) {
      console.log(match.params);
      return match.params;
    }
    return {};
  };
  componentDidMount() {
    const { updateParams } = this.props;
    updateParams(this.getMatchParams());
  }
  componentDidUpdate(prevProps) {
    const { updateParams, match } = this.props;
    const currentParams = this.getMatchParams();
    const prevParams = this.getMatchParams(prevProps);
    if (!_.isEqual(currentParams, prevParams)) {
      updateParams(match.params);
    }
  }

  componentWillUnmount() {
    const { updateParams } = this.props;
    const matchParams = this.getMatchParams();
    Object.keys(matchParams).forEach(k => (matchParams[k] = undefined));
    updateParams(matchParams);
  }
  render() {
    return <Route {...this.props} />;
  }
}

const RouteWithRouter = withRouter(CustomRoute);

export default props => (
  <ParamContext.Consumer>
    {({ updateParams }) => {
      return <RouteWithRouter updateParams={updateParams} {...props} />;
    }}
  </ParamContext.Consumer>
);

ParamsProvider.jsx

import React from "react";
import { ParamContext } from "./ParamsContext";
export default class ParamsProvider extends React.Component {
  state = {
    allParams: {}
  };
  updateParams = params => {
    console.log({ params: JSON.stringify(params) });
    this.setState(prevProps => ({
      allParams: {
        ...prevProps.allParams,
        ...params
      }
    }));
  };
  render() {
    return (
      <ParamContext.Provider
        value={{
          allParams: this.state.allParams,
          updateParams: this.updateParams
        }}
      >
        {this.props.children}
      </ParamContext.Provider>
    );
  }
}

Index.js

ReactDOM.render(
  <BrowserRouter>
    <ParamsProvider>
      <App />
    </ParamsProvider>
  </BrowserRouter>,
  document.getElementById("root")
);

ワーキングデモ

5
Shubham Khatri

次のようなことを試してください。

_<Switch>
  <Route path="/auth/login/:token" render={props => <Login  {...this.props} {...props}/>}/>
  <Route path="/auth/login" component={Login}/>
_

最初に、パラメータ付きのルートと、パラメータなしのリンクの後。 Loginコンポーネントの中に、このコード行console.log(props.match.params.token);を入れてテストし、私のために働きました。

0
Fred Brasil