web-dev-qa-db-fra.com

React Router v4 Paramètres de correspondance imbriqués non accessibles au niveau racine

Cas de test

https://codesandbox.io/s/rr00y9w2wm

Étapes pour reproduire

OR

Comportement prévisible

  • match.params.topicId doit être identique à partir du composant parent Topics doit être identique à match.params.topicId lorsque vous y accédez via le composant Topic

Comportement actuel

  • match.params.topicId lorsque vous y accédez par le composant Topic est undefined
  • match.params.topicId lorsque vous y accédez par le composant Topics est rendering

Je comprends depuis ce numéro fermé que ce n’est pas nécessairement un bug.

Cette exigence est très courante chez les utilisateurs qui souhaitent créer une exécution dans l'application Web de la fabrique dans laquelle un composant Topics au niveau parent doit accéder à match.params.paramIdparamId est un paramètre d'URL qui correspond à un imbriqué ( enfant) composant 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>
);

De manière générique, Topics pourrait être un composant Tiroir ou Menu de navigation et Topic pourrait être n'importe quel composant enfant, comme dans l'application que je développe. Le composant enfant a son propre paramètre :topicId qui a son propre (disons) <Route path="sections/:sectionId" component={Section} /> Route/Component.

Encore plus pénible, il n’est pas nécessaire que le menu de navigation ait une relation un-à-un avec l’arbre des composants. Parfois, les éléments situés au niveau racine du menu (par exemple, Topics, Sections etc.) peuvent correspondre à une structure nested (Sections est uniquement restitué sous un sujet, /topics/:topicId/sections/:sectionId, bien qu’elle ait sa propre liste normalisée disponible pour le serveur. sous le titre Sections dans la barre de navigation) . Par conséquent, lorsque vous cliquez sur Sections, it doit être mis en surbrillance et non les deux Sections _ ​​etSujets.

Le chemin sectionId ou sections n'étant pas disponible pour le composant Barre de navigation qui se trouve au niveau racine de l'application, il devient nécessaire d'écrire des hacks comme celui-ci pour un cas d'utilisation aussi banal. 

Je ne suis pas du tout un expert chez React Router. Par conséquent, si quelqu'un peut proposer une solution élégante et appropriée à ce cas d'utilisation, j'estimerais qu'il s'agit d'une tentative fructueuse. Et par élégant, je veux dire

  • Utilise match et non history.location.pathname
  • N'implique pas les approches de hacky comme l'analyse manuelle du window.location.xxx 
  • N'utilise pas this.props.location.pathname
  • N'utilise pas de bibliothèques tierces comme path-to-regexp
  • N'utilise pas de paramètres de requête

Autres hacks/solutions partielles/questions connexes:

  1. React Router v4 - Comment obtenir l'itinéraire actuel?

  2. React Router v4 global ne correspond pas à la route imbriquée childs

TIA!

10
nikjohn

Essayez d’utiliser les paramètres de requête ? pour permettre au parent et à l’enfant d’accéder à la topic actuellement sélectionnée. Malheureusement, vous devrez utiliser le module qs parce que react-router-dom n'analyse pas automatiquement les requêtes (rea-router v3 le fait).

Exemple de travail: https://codesandbox.io/s/my1ljx40r9

L'URL est structurée comme une chaîne concaténée: 

topic?topic=props-v-state 

Ensuite, vous ajouteriez à la requête avec &

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

✔ Correspondance des utilisations pour la gestion des URL de route

✔ N'utilise pas this.props.location.pathname (utilise this.props.location.search)

✔ Utilise qs pour analyser location.search

✔ N'implique pas les approches de hacky

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

Une autre approche consiste à créer une variable HOC qui stocke les paramètres dans state et les enfants mettent à jour la variable state du parent lorsque ses paramètres ont été modifiés.

L'URL est structurée comme une arborescence de dossiers: /topics/rendering/optimization/pure-components/shouldComponentUpdate

Exemple de travail: https://codesandbox.io/s/9joknpm9jy

✔ Correspondance des utilisations pour la gestion des URL de route

✔ N'utilise pas this.props.location.pathname

✔ Utilise lodash pour la comparaison d'objet à objet

✔ N'implique pas les approches de hacky

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 ne vous donne pas les paramètres de correspondance de l'un des itinéraires enfants correspondants, il vous donne plutôt les paramètres en fonction de la correspondance actuelle. Donc, si vous avez configuré votre itinéraire comme

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

et dans le composant Topics vous avez un itinéraire comme

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

Désormais, si votre URL est /topic/topic1 qui correspond à la Route intérieure, mais pour le composant Topics, la Route correspondante est toujours, /topic et ne contient donc aucun paramètre, ce qui est logique.

Si vous voulez récupérer les paramètres de la route enfant correspondante dans le composant topic, vous devez utiliser l'utilitaire matchPath fourni par React-router et tester la route enfant dont vous souhaitez obtenir les paramètres.

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

MODIFIER:

Une méthode pour obtenir tous les paramètres à n'importe quel niveau consiste à utiliser le contexte et à mettre à jour les paramètres au fur et à mesure qu'ils correspondent dans le fournisseur de contexte.

Vous devez créer une enveloppe autour de Route pour que celle-ci fonctionne correctement. Un exemple typique ressemblerait à ceci:

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

Travailler DEMO

3
Shubham Khatri

Essayez de faire quelque chose comme ça:

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

Premièrement, la route avec le paramètre et après le lien sans paramètre . À l’intérieur de mon composant Login, j’ai mis cette ligne de code console.log(props.match.params.token); à tester et a travaillé pour moi. 

0
Fred Brasil