7
votes

Comment gérer correctement le rendu des composants conditionnels en fonction de l'itinéraire?

J'ai beaucoup cherché sur le Web et le SO, demandé dans le chat reactiflux, mais je n'ai pas trouvé de moyen propre et non hack de rendre un composant en fonction de l'itinéraire / chemin.

p> Disons que j'ai le

qui devrait être affiché sur certaines pages et devrait être caché sur d'autres.

Je peux certainement utiliser cette chaîne dans le composant Header

if (props.location.pathname.indexOf('/pageWithoutAHeader1') > -1) return null
if (props.location.pathname.indexOf('/pageWithoutAHeader2') > -1) return null
if (props.location.pathname.indexOf('/pageWithoutAHeader3') > -1) return null

C'est tout à fait correct, si / pageWithoutAHeader est l'unique. Si j'ai besoin de la même fonctionnalité pour 5 pages, cela devient ceci:

if (props.location.pathname.indexOf('/pageWithoutAHeader') > -1) return null

Oui, je peux stocker des routes dans le tableau et écrire une boucle, qui sera plus réutilisable en code. Mais est-ce la meilleure et la plus élégante façon de gérer ce cas d'utilisation?

Je pense que cela peut même être bogué, par exemple si je ne rend pas l'en-tête d'une page avec une route / xyz et que j'ai des routes avec des UUID, comme / projects /: id et id = xyzfoo , donc / projects / xyzfoo n'affichera pas d'en-tête mais il devrait.


0 commentaires

6 Réponses :


2
votes

Incluez ces données sur l'en-tête en tant que paramètre d'itinéraire ou requête.

/ projects /: headerBool /: id

Ou:

/ projects /: id? header = false

Ensuite, vous pouvez y accéder via props.match ou props.location.


2 commentaires

Je ne peux pas dire si c'est la meilleure solution, car je ne veux pas stocker l'en-tête "état" dans la route + n'importe qui pourrait simplement le remplacer par ce dont il a besoin.


Vrai! Eh bien, vous pouvez réduire le risque de bug en divisant le chemin par '/', puis en recherchant une correspondance exacte pour chaque partie. Et si vous le faites de cette façon, il est plus logique de créer un objet avec des clés les chaînes que vous recherchez et les valeurs «true» afin que vous puissiez simplement rechercher chaque partie lorsque vous les parcourez.



4
votes

Je pense que vous voyez cela de la mauvaise façon. La composabilité est donc le trait numéro un lorsque l'on pense dans React. Un en-tête est un composant réutilisable qui peut être déposé où vous voulez!

Penser de cette manière vous fournira plusieurs options.

Disons que vous avez plusieurs routes de page que vous avez conçues pour votre application. Un en-tête est un composant enfant de l'une de ces pages qui l'utilisent!

function withHeader(Page){
    return class extends React.Component {
        render() {
          // Wraps the input component in a container, without mutating it.
          return (
              <React.Fragment>
                 <Header/>
                 <Page {...this.props} />);
              </React.Fragment>
        }
    }
}

function AppRouter() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about/">About</Link>
            </li>
            <li>
              <Link to="/users/">Users</Link>
            </li>
          </ul>
        </nav>

        <Route path="/" exact component={withHeader(Index)} />
        <Route path="/about/" component={About} />
        <Route path="/users/" component={Users} />
      </div>
    </Router>
   );
}

Maintenant, dans chaque page, vous voulez l'en-tête, vous pouvez aller et simplement introduire le composant d'en-tête chaque fois que nécessaire.

export default function Index(){
    return (
        <React.Fragment>
             <Header/>
             <div> ... Index Content </div>
        </React.Fragment>
    );
}

export default function About(){
    return (
        <React.Fragment>
             //I don't need a header here.
             <div> ... Index Content </div>
        </React.Fragment>
    );
}

Une approche encore plus élégante mais un peu plus complexe consisterait à introduire un composant d'ordre supérieur. Cela rendrait vos intentions plus claires concernant l'ajout d'en-têtes au niveau de l'itinéraire!

function AppRouter() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about/">About</Link>
            </li>
            <li>
              <Link to="/users/">Users</Link>
            </li>
          </ul>
        </nav>

        <Route path="/" exact component={Index} />
        <Route path="/about/" component={About} />
        <Route path="/users/" component={Users} />
      </div>
    </Router>
  );
}


1 commentaires

importer Header et l'utiliser partout me semble être une grosse répétition de code, ce qui ne résout pas le problème de rendre le code moins répétable. HOC a l'air un peu mieux, mais il se produit encore des parties répétables. Une chance de faire de même, mais envelopper plusieurs éléments avec quelque chose afin que chaque route à l'intérieur de ce wrapper rendra les composants avec Header ?



4
votes

Vous pouvez d'abord répertorier toutes les routes sans en-tête et regrouper les autres dans un commutateur supplémentaire:

...
<Switch>
  <Route path="/noheader1" ... />
  <Route path="/noheader2" ... />
  <Route path="/noheader3" ... />
  <Route component={HeaderRoutes} />
</Switch>
...

HeaderRoutes = props => (
  <React.Fragment>
    <Header/>
    <Switch>
      <Route path="/withheader1" ... />
      <Route path="/withheader2" ... />
      <Route path="/withheader3" ... />
    </Switch>
  </React.Fragment>
)

Depuis le documentation :

Les itinéraires sans chemin correspondent toujours.

Malheureusement, cette solution peut avoir un problème avec la page "introuvable". Il doit être placé à la fin des HeaderRoutes et sera rendu avec un Header.

La solution de Dhara n'a pas un tel problème. Mais cela pourrait ne pas fonctionner correctement avec Switch si les éléments internes de React Router changent:

Tous les enfants d'un doivent être des éléments ou . Seul le premier enfant correspondant à l'emplacement actuel sera affiché.

HOC sur Route n'est pas une Route en soi. Mais cela devrait fonctionner correctement car la base de code actuelle attend en fait que tout React.Element avec la même sémantique d'accessoires que et ait .


4 commentaires

Wow, je dois vérifier ça, et si ça marche comme ça, c'est exactement ce dont j'ai besoin! :)


J'ai vérifié et ça marche, merci! C'est la manière la plus élégante sans aucune répétition de code. Et je suis surpris de son fonctionnement, j'apprécierai une explication sur la façon dont nous avons utilisé Route sans chemin prop


Merci pour l'explication! J'aime aussi la solution de Dhara (et celle de jdc91, il est arrivé en premier avec HOC) mais j'ai trouvé la vôtre la moins répétitive du code.


Vous pouvez également utiliser le prop path = "*" bien que cela semble un peu redondant.



6
votes

Afin d'atteindre la règle DRY (éviter la répétition du code) et implémenter le rendu conditionnel en fonction des routes, vous devez travailler sur la structure suivante:

étape 1) Créer la mise en page (HOC) qui renvoie le composant donné avec

et exportez-le

import React, { Component } from 'react'
import { BrowserRouter, Route, Switch } from "react-router-dom"
import Test1 from './Test1';
import Test2 from './Test2';
import { HeaderLayout } from './HeaderLayout';

export default class Main extends Component {
    render() {
        return (
            <BrowserRouter>
                <Switch>
                    <HeaderLayout path="/test1" component={Test1} />
                    <Route path="/test2" component={Test2}/>
                </Switch>
            </BrowserRouter>

        )
    }
}

Étape 2) importez la mise en page et utilisez-la

import React from "react"
import { Route } from "react-router-dom"
import Header from "./Header"

export const HeaderLayout = ({component: Component, ...rest}) => (
    <Route {...rest} render={(props) => (
        <>
            <Header/>
            <Component {...props} />
        </>
    )} />
)
Sortie:

 path-test1

 path-test2

Conclusion:

Donc, chaque fois que vous voulez inclure un composant d'en-tête avec votre composant défini d'itinéraire, utilisez et si vous ne le faites pas ' Si vous souhaitez utiliser l'en-tête, utilisez simplement pour masquer l'en-tête de votre page.


0 commentaires

0
votes

Vous pouvez utiliser l'attribut render de la Route. Exemple:

<Route path='/pageWithoutHeader' render={() => <Page1 />} />
<Route path='pageWithHeader' render={() => 
      <Header/>
      <Page2 />}
/>

Cette méthode est meilleure que d'utiliser le composant d'en-tête à l'intérieur de la page.


0 commentaires

0
votes

Un scénario qui apparaît souvent pour moi est certaines pages de la base de données ayant Header = false ou Page Title = false. Une bonne solution pour cela serait un contexte.

const Header = () => {
  const headerActive = useHeader();

  if (!headerActive) {
    return null;
  }

  return (
    <header>I'm a header</header>
  );
}
...
const PageWithoutHeader = () => {
  useHeader(false);

  return (
    <div>Page without header</div>
  );
}

Vous pouvez également le simplifier davantage en utilisant un hook personnalisé. Quelque chose comme:

export const useHeader = (active) => {
  const { headerActive, setHeaderActive } = useContext(AppContext);
  useEffect(() => {
    if (active !== undefined) {
      setHeaderActive(active);
    }
  },[setHeaderActive, active]);

  return headerActive;
}

Ensuite dans les composants d'origine:

import React, { createContext, useContext, useState, useEffect } from "react";
import { Switch, Route } from "react-router-dom";

const AppContext = createContext({});

const Header = () => {
  const { headerActive } = useContext(AppContext);

  if (!headerActive) {
    return null;
  }

  return (
    <header>I'm a header</header>
  );
}

const PageWithHeader = () => {
  const { setHeaderActive } = useContext(AppContext);
  useEffect(() => {
    setHeaderActive(true);
  },[setHeaderActive]);

  return (
    <div>Page with header</div>
  );
}

const PageWithoutHeader = () => {
  const { setHeaderActive } = useContext(AppContext);
  useEffect(() => {
    setHeaderActive(false);
  },[setHeaderActive]);

  return (
    <div>Page without header</div>
  );
}

export const App = () => {
  const [headerActive, setHeaderActive] = useState(true);

  return (
    <AppContext.Provider value={{ headerActive, setHeaderActive }}>
      <Header />
      <Switch>
        <Route path="page-1">
          <PageWithHeader />
        </Route>
        <Route path="page-2">
          <PageWithoutHeader />
        </Route>
      <Switch>
    </AppContext.Provider>
  );
}

Vous pouvez également obtenir de la fantaisie et utiliser le hook useLocation associé avec useRef pour garder une trace des chemins d'accès précédents et actuels afin que vous puissiez avoir un état par défaut sans avoir à déclarer useHeader dans chaque page.


0 commentaires