web-dev-qa-db-de.com

Reagieren Sie auf Router v4. Auf verschachtelte Übereinstimmungsparameter kann auf Stammebene nicht zugegriffen werden

Testfall

https://codesandbox.io/s/rr00y9w2wm

Schritte zum Reproduzieren

OR

Erwartetes Verhalten

  • match.params.topicId sollte mit dem übergeordneten Topics -Komponenten identisch sein, wenn innerhalb der Topic -Komponente auf match.params.topicId zugegriffen wird

Tatsächliches Verhalten

  • match.params.topicId beim Zugriff im Thema Komponente ist undefiniert
  • match.params.topicId beim Zugriff in der Topics -Komponente ist Rendering

Ich verstehe aus dieser geschlossenen Ausgabe , dass dies nicht unbedingt ein Fehler ist.

Diese Anforderung ist besonders häufig bei Benutzern, die einen Lauf in der Mill-Webanwendung erstellen möchten, bei dem eine Komponente Topics auf übergeordneter Ebene auf die URL match.params.paramId zugreifen muss, wobei paramId ein URL-Parameter ist, der mit a übereinstimmt verschachtelte (untergeordnete) Komponente 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>
);

Im allgemeinen Sinne kann Topics eine Schubladen- oder Navigationsmenükomponente und Topic eine beliebige untergeordnete Komponente sein, wie es in der von mir entwickelten Anwendung der Fall ist. Die untergeordnete Komponente hat einen eigenen :topicId -Parameter, der einen eigenen (sagen wir mal) <Route path="sections/:sectionId" component={Section} /> -Route Komponente hat.

Noch schmerzhafter ist, dass das Navigationsmenü keine Eins-zu-Eins-Beziehung zum Komponentenbaum haben muss. Manchmal entsprechen die Elemente auf der Stammebene des Menüs (z. B. Topics, Sections usw.) einer verschachtelten -Struktur (Sections wird nur unter einem Zweig gerendert, /topics/:topicId/sections/:sectionId, obwohl eine eigene normalisierte Liste verfügbar ist an den Benutzer unter dem Titel Abschnitte in der Navigationsleiste). Daher sollte beim Klicken auf Abschnitte es hervorgehoben werden und nicht beide Abschnitte und Themen .

Da der Pfad sectionId oder sections nicht für die Navigationsleisten-Komponente verfügbar ist, die sich im Stammverzeichnis der Anwendung befindet, muss für einen solchen alltäglichen Anwendungsfall/- hacks wie dieser geschrieben werden.

Ich bin überhaupt kein Experte für React Router. Wenn also jemand eine elegante Lösung für diesen Anwendungsfall wagen kann, würde ich dies für ein fruchtbares Unterfangen halten. Und mit elegant meine ich

  • Verwendet match und nicht history.location.pathname
  • Enthält keine hacky Ansätze wie manuelles Parsen des window.location.xxx
  • Verwendet nicht this.props.location.pathname
  • Verwendet keine Bibliotheken von Drittanbietern wie path-to-regexp
  • Verwendet keine Abfrageparameter

Andere Hacks/Teillösungen/verwandte Fragen:

  1. React Router v4 - Wie erhalte ich die aktuelle Route?

  2. React Router v4 global nicht mit verschachtelten Routen-Childs übereinstimmen

TIA!

10
nikjohn

Verwenden Sie die Abfrageparameter ?, damit das übergeordnete und untergeordnete Element auf die aktuell ausgewählte topic zugreifen kann. Leider müssen Sie das Modul qs verwenden, da react-router-dom Abfragen nicht automatisch analysiert (Reakt-Router v3).

Arbeitsbeispiel: https://codesandbox.io/s/my1ljx40r9

Die URL ist wie eine verkettete Zeichenfolge aufgebaut: 

topic?topic=props-v-state 

Dann würden Sie die Abfrage mit & ergänzen: 

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

✔ Verwendet Übereinstimmungen für die Routen-URL-Verarbeitung

✔ verwendet nicht this.props.location.pathname (verwendet this.props.location.search)

✔ verwendet qs zur Analyse von location.search

✔ beinhaltet keine gehackten Ansätze

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>
  );
};

Ein anderer Ansatz wäre die Erstellung einer HOC, die Parameter in state speichert, und Kinder aktualisieren die state des Elternteils, wenn sich die Parameter geändert haben.

Die URL ist wie ein Ordnerbaum aufgebaut: /topics/rendering/optimization/pure-components/shouldComponentUpdate

Arbeitsbeispiel: https://codesandbox.io/s/9joknpm9jy

✔ Verwendet Übereinstimmungen für die Routen-URL-Verarbeitung

✔ verwendet this.props.location.pathname nicht

✔ Verwendet lodash für den Vergleich von Objekten

✔ beinhaltet keine gehackten Ansätze

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>
);
6
Matt Carlotta

React-router gibt Ihnen nicht die Übereinstimmungsparameter einer übereinstimmenden untergeordneten Route, sondern die auf dem aktuellen Treffer basierenden Parameter. Also wenn du deine Routen eingerichtet hast

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

und in Topics Komponente haben Sie eine Route wie

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

Wenn Ihre URL /topic/topic1 ist, die der inneren Route entspricht, aber für die Topics-Komponente, ist die übereinstimmende Route immer noch /topic und enthält daher keine Parameter, was Sinn macht.

Wenn Sie Parameter der untergeordneten Route abrufen möchten, die in der Themenkomponente übereinstimmen, müssen Sie das von React-router bereitgestellte Dienstprogramm matchPath verwenden und mit der untergeordneten Route testen, deren Parameter Sie abrufen möchten

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>
    )
}

BEARBEITEN:

Eine Methode, um alle Parameter auf einer beliebigen Ebene zu erhalten, besteht darin, den Kontext zu verwenden und die Parameter zu aktualisieren, sobald sie im Kontextanbieter übereinstimmen.

Sie müssten einen Wrapper für Route erstellen, damit er ordnungsgemäß funktioniert. Ein typisches Beispiel würde aussehen

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")
);

Working DEMO

3
Shubham Khatri

Versuche so etwas zu tun:

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

Zuerst die Route mit dem Parameter und nach dem Link ohne Parameter . Innerhalb meiner Login-Komponente stelle ich diese Codezeile console.log(props.match.params.token); zum Testen und arbeitete für mich. 

0
Fred Brasil