React Router with - Ant Design Sider: cómo llenar la sección de contenido con componentes para el elemento de menú relevante

8

Estoy tratando de usar el menú lateral AntD como un panel de pestañas.

Quiero poner componentes dentro del contenido para que el panel de contenido represente el componente relacionado cuando se hace clic en un elemento del menú.

¿Cómo consigo que esta estructura tome componentes como contenido para cada elemento del menú?

import React from 'react';
import { compose } from 'recompose';
import { Divider, Layout, Card, Tabs, Typography, Menu, Breadcrumb, Icon } from 'antd';
import { PasswordForgetForm } from '../Auth/Password/Forgot';


const { Title } = Typography
const { TabPane } = Tabs;
const { Header, Content, Footer, Sider } = Layout;
const { SubMenu } = Menu;


class Dashboard extends React.Component {
  state = {
    collapsed: false,
  };

  onCollapse = collapsed => {
    console.log(collapsed);
    this.setState({ collapsed });
  };

  render() {
    return (
      <Layout style={{ minHeight: '100vh' }}>
        <Sider collapsible collapsed={this.state.collapsed} onCollapse={this.onCollapse}>
          <div  />
          <Menu theme="light" defaultSelectedKeys={['1']} mode="inline" >

            <Menu.Item key="1">
              <Icon type="fire" />
              <span>Next item</span>
              <PasswordForgetForm />
            </Menu.Item>  
            <Menu.Item key="2">
              <Icon type="fire" />
              <span>Next item</span>
              <Another component to render in the content div />
            </Menu.Item>

          </Menu>
        </Sider>
        <Layout>
          <Header style={{ background: '#fff', padding: 0 }} />
          <Content style={{ margin: '0 16px', background: '#fff' }}>

            <div style={{ padding: 24, background: '#fff', minHeight: 360 }}>
            RENDER RELEVANT COMPONENT HERE BASED ON MENU ITEM SELECTED
            </div>
          </Content>
          </Layout>
      </Layout>
    );
  }
}

export default Dashboard;

Cuando intento representar esto, no se generan errores, pero el div de contenido no se actualiza con PasswordForgetForm que especifiqué como contenido para el elemento del menú.

Intenté la sugerencia de Chris a continuación, que funciona bien para representar el componente para cada uno de los diferentes divs de contenido de elementos de menú en el segmento de diseño, sin embargo, la desventaja de este enfoque es que con cada actualización de página, la ruta va al componente de contenido para ese elemento de menú en particular, en lugar de la página de prueba que tiene el menú, y el componente de contenido con ese contenido relevante. Si se aprueba este enfoque como razonable, ¿hay alguna manera de mantener la página actualizada en la página original, en lugar de tratar de ir a una url de elemento de menú de subconjunto?

Actualizar

Encontré esta literatura . Creo que esto podría ser lo que necesito saber, pero el lenguaje es demasiado técnico para que lo entienda. ¿Alguien puede ayudar con una versión sencilla en inglés de este tema?

También encontré este tutorial que usa Pose para ayudar con el renderizado. Si bien esta estructura es lo que estoy tratando de lograr, parece que Pose ha quedado en desuso. ¿Alguien sabe si es necesario ir más allá de reaccionar enrutador para obtener este resultado, o hay una solución dentro de reaccionar que pueda buscar implementar?

Este ejemplo es similar a lo que quiero, pero no puedo encontrar nada que defina la barra lateral (que parece ser un argumento necesario para que esto funcione).

También he visto esta publicación . Si bien algunas de las respuestas son copiar y pegar los documentos, me pregunto si la respuesta de Adam Gering está más cerca de lo que necesito. Estoy tratando de mantener el menú Sider en la ruta / dash en todo momento. Esa URL no debería cambiar, independientemente de qué elemento del menú en el lado en el que el usuario haga clic, PERO el contenido div en el componente / guión debe actualizarse para representar el componente en la ruta de ruta que he especificado.

APLICANDO LA SUGERENCIA DE CHRIS

Chris ha ofrecido amablemente una sugerencia a continuación. Lo intenté, pero la circunstancia en la que no funciona como se desea es cuando se hace clic en el elemento del menú (y el componente relevante se carga correctamente en el div de contenido, pero si actualizo la página, la actualización intenta cargar una página con un url que es para el componente que se supone que está dentro del contenido div en la página del tablero.

import React from 'react';
import {
    BrowserRouter as Router,
    Route,
    Link,
    Switch,

  } from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import { compose } from 'recompose';
import { Divider, Layout, Card, Tabs, Typography, Menu, Breadcrumb, Icon } from 'antd';
import { withFirebase } from '../Firebase/Index';
import { AuthUserContext, withAuthorization, withEmailVerification } from '../Session/Index';

// import UserName from '../Users/UserName';
import Account from '../Account/Index';
import Test from '../Test/Index';



const { Title } = Typography
const { TabPane } = Tabs;
const { Header, Content, Footer, Sider } = Layout;
const { SubMenu } = Menu;


class Dashboard extends React.Component {
  state = {
    collapsed: false,
  };

  onCollapse = collapsed => {
    console.log(collapsed);
    this.setState({ collapsed });
  };

  render() {
    const {  loading } = this.state;
    // const dbUser = this.props.firebase.app.snapshot.data();
    // const user = Firebase.auth().currentUser;
    return (
    <AuthUserContext.Consumer>
      {authUser => (  

        <div>    
         {authUser.email}
          <Router>  
            <Layout style={{ minHeight: '100vh' }}>
              <Sider collapsible collapsed={this.state.collapsed} onCollapse={this.onCollapse}>
                <div  />
                <Menu theme="light" defaultSelectedKeys={['1']} mode="inline" >
                  <SubMenu
                    key="sub1"
                    title={
                      <span>
                      <Icon type="user" />
                      <span>Profile</span>
                      </span>
                    }
                  >

                      <Menu.Item key="2"><Link to={ROUTES.ACCOUNT}>Account Settings</Link></Menu.Item>


                      <Menu.Item key="3"><Link to={ROUTES.TEST}>2nd content component</Link></Menu.Item>
</SubMenu>

                </Menu>
              </Sider>
              <Layout>

                <Header>                </Header>      
                <Content style={{ margin: '0 16px', background: '#fff' }}>

                  <div style={{ padding: 24, background: '#fff', minHeight: 360 }}>
                      <Switch>
                          <Route path={ROUTES.ACCOUNT}>
                          <Account />
                          </Route>
                          <Route path={ROUTES.TEST}>
                          < Test />
                          </Route>

                      </Switch>    
                  </div>
                </Content>
                <Footer style={{ textAlign: 'center' }}>


                 test footer
                </Footer>
              </Layout>
            </Layout>
          </Router>  
        </div>
      )}
    </AuthUserContext.Consumer>  
    );
  }
}

export default Dashboard;

En mi archivo de rutas, tengo:

export const ACCOUNT = '/account';

También encontré este tutorial , que utiliza el mismo enfoque descrito anteriormente, y no se redirige de la misma manera que mi código lo hace al actualizar la página. El tutorial usa Route en lugar de BrowserRouter, pero de lo contrario no puedo ver ninguna diferencia.

PRÓXIMO INTENTO

Vi esta publicación (gracias Matt). He tratado de seguir las sugerencias en esa publicación.

Eliminé el contenedor externo del Enrutador de la página del Tablero (hay un Enrutador alrededor de App.js, que es donde está configurada la ruta al Tablero).

Luego, en mi Tablero, cambié la división de Contenido a:

Yo añadí:

let match = useRouteMatch();

al método de representación dentro de mi componente Panel de instrumentos (como se muestra aquí ).

Agregué useRouteMatch a mi declaración de importación de react-router-dom.

Esto produce un error que dice:

Error: llamada de enlace no válida. Los ganchos solo se pueden llamar dentro del cuerpo de un componente de función. Esto podría suceder por una de las siguientes razones: 1. Es posible que tenga versiones que no coinciden de React y el renderizador (como React DOM) 2. Puede estar rompiendo las Reglas de Hooks

Hay más mensajes en el error anterior, pero el desbordamiento de la pila no me permite publicarlo porque tiene un enlace corto

No sé qué he hecho mal en estos pasos, pero si comento la declaración let, la página se carga.

Cuando voy a / Tablero y hago clic en "cuenta", espero que el componente de la cuenta se procese dentro del contenido div que tengo en el diseño de la página Panel. En su lugar, redirige directamente a una página en localhost3000 / account (para la cual no hay referencia de página, es solo un componente para representar dentro de la página del Panel).

Entonces, esto es peor que el problema con el que comencé, porque al menos al principio, la redirección solo ocurrió al actualizar la página. Ahora sucede de inmediato.

Encontré este repositorio que tiene ejemplos de cada tipo de ruta. No puedo ver lo que está haciendo que no soy.

Esta publicación parece haber tenido el mismo problema que yo, y la resolvió usando el mismo enfoque que he probado. Es una publicación de 2018, por lo que el paso del tiempo hace que el enfoque sea anticuado, pero no puedo ver ninguna diferencia entre lo que implementa esa solución de publicación y mi intento original.

PRÓXIMO INTENTO

Pensé que había encontrado un enfoque que funciona en la respuesta de Lyubomir en esta publicación .

Yo tengo:

<Menu.Item key="2">
                      <Link to=

{ ${this.props.match.url}/account}> Configuración de la cuenta

Luego, en el div en el componente del guión donde quiero mostrar este componente, tengo:

<div>
                      <Switch>
                          <Route exact path={`${this.props.match.url}/:account`} component={Account} />

Esto funciona. En la actualización de la página, puedo mantener las cosas funcionando como deberían.

SIN EMBARGO, cuando agrego un segundo elemento de menú con:

<Menu.Item key="16">
                    <Link to={`${this.props.match.url}/learn`}>
                      <Icon type="solution" />
                      <span>Lern</span>
                    </Link>  
                  </Menu.Item>

En lugar de representar el componente de aprendizaje cuando se hace clic en el elemento de menú para / learn (y la barra de URL cambia para aprender), se representa el componente de cuenta. Si elimino la visualización de la cuenta de la declaración de cambio, entonces puedo mostrar el componente de aprendizaje correcto.

Con este intento, tengo los elementos del menú funcionando para que coincidan con una extensión de URL del Tablero (es decir, localhost / dash / account o localhost / dash / learn) solo para UN elemento del menú. Si agrego un segundo elemento del menú, la única forma en que puedo representar correctamente el segundo componente es eliminando el primer elemento del menú. Estoy usando el interruptor con rutas exactas.

Quiero que esta solución funcione con múltiples elementos de menú.

He intentado alternar ruta para url (por ejemplo:

<Link to={`${this.props.match.url}/learn`}>

es lo mismo que:

<Link to={`${this.props.match.path}/learn`}>

He leído la explicación en este blog y aunque no entiendo completamente estas opciones. He leido esto . Sugiere que la declaración de coincidencia ahora es un método heredado para representar componentes (ahora que los ganchos están disponibles). No puedo encontrar ningún material de capacitación que muestre cómo usar ganchos para lograr el resultado esperado.

Mel
fuente
Incluya también el PasswordForgottenFormcomponente.
AleksandrH
Es solo un componente constante con una forma. Cada elemento del menú tiene un componente separado (algunos son componentes de clase) que se representa en el div de contenido de la sección de diseño en el componente de prueba.
Mel

Respuestas:

4

La mejor solución es usar React Router <Link>para hacer que cada elemento del menú se vincule a una ruta específica, y luego en el contenido, <Switch>para representar el componente correspondiente. Aquí está el documento: React router

Renderizar con React Router

<Router>
  <Layout style={{ minHeight: "100vh" }}>
    <Sider
      collapsible
      collapsed={this.state.collapsed}
      onCollapse={this.onCollapse}
    >
      <div />
      <Menu theme="light" defaultSelectedKeys={["1"]} mode="inline">
        <Menu.Item key="1">
          // Add a react router link here
          <Link to="/password-forget-form">
            <Icon type="fire" />
            <span>Next item</span>
          </Link>
        </Menu.Item>
        <Menu.Item key="2">
          // Add another react router link
          <Link to="/next-item">
            <Icon type="fire" />
            <span>Next item</span>
          </Link>
        </Menu.Item>
      </Menu>
    </Sider>
    <Layout>
      <Header style={{ background: "#fff", padding: 0 }} />
      <Content style={{ margin: "0 16px", background: "#fff" }}>
        <div style={{ padding: 24, background: "#fff", minHeight: 360 }}>
          // Render different components based on the path
          <Switch>
            <Route path="/password-forget-form">
              <PasswordForgetForm />
            </Route>
            <Route path="/next-item">
              <Another component to render in the content div />
            </Route>
          </Switch>
        </div>
      </Content>
    </Layout>
  </Layout>
</Router>;

Renderizar con teclas de menú

App.js

import React, { useState } from "react";
import { Layout } from "antd";
import Sider from "./Sider";
import "./styles.css";

const { Content } = Layout;
export default function App() {
  const style = {
    fontSize: "30px",
    height: "100%",
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  };

  const components = {
    1: <div style={style}>Option 1</div>,
    2: <div style={style}>Option 2</div>,
    3: <div style={style}>Option 3</div>,
    4: <div style={style}>Option 4</div>
  };

  const [render, updateRender] = useState(1);

  const handleMenuClick = menu => {
    updateRender(menu.key);
  };

  return (
    <div className="App">
      <Layout style={{ minHeight: "100vh" }}>
        <Sider handleClick={handleMenuClick} />
        <Layout>
          <Content>{components[render]}</Content>
        </Layout>
      </Layout>
    </div>
  );
}

Sider.js

import React from "react";
import { Menu, Layout, Icon } from "antd";

const { SubMenu } = Menu;

export default function Sider(props) {
  const { handleClick } = props;
  return (
    <Layout.Sider>
      <Menu theme="dark" mode="inline" openKeys={"sub1"}>
        <SubMenu
          key="sub1"
          title={
            <span>
              <Icon type="mail" />
              <span>Navigation One</span>
            </span>
          }
        >
          <Menu.Item key="1" onClick={handleClick}>
            Option 1
          </Menu.Item>
          <Menu.Item key="2" onClick={handleClick}>
            Option 2
          </Menu.Item>
          <Menu.Item key="3" onClick={handleClick}>
            Option 3
          </Menu.Item>
          <Menu.Item key="4" onClick={handleClick}>
            Option 4
          </Menu.Item>
        </SubMenu>
      </Menu>
    </Layout.Sider>
  );
}

Editar antd-menu-click-rendering

SUV97
fuente
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Samuel Liew
Gracias a ambos Chris y Matt. Aprecio mucho toda la ayuda para tratar de resolver esto.
Mel
0

Encontré la respuesta de Lyubomir en esta publicación. Funciona. Rutas anidadas con react router v4 / v5

El enlace del elemento del menú es:

<Menu.Item key="2">
                      <Link to=
                       {`${this.props.match.url}/account`}>
                        Account
                      </Link>
                    </Menu.Item>

La ruta de visualización es:

<Route exact path={`${this.props.match.path}/:account`} component={Account} />

Hay dos puntos antes del nombre del componente. No estoy seguro de por qué. Si alguien sabe cómo se llama este enfoque, agradecería una referencia para poder tratar de entenderlo.

Mel
fuente
En realidad, hablé pronto. Si bien esto funciona. Hay una falla Solo funciona con un enlace. Si agrego una ruta a un segundo enlace de menú (llamado Aprendizaje), siguiendo el mismo enfoque, tengo que eliminar la pantalla de Cuenta para poder representar el componente de Aprendizaje. Si trato de mantenerlos a ambos, el componente de la cuenta se muestra incluso cuando he pedido el componente de Aprendizaje.
Mel
Usar :es un paramscomodín. Entonces /app/accountes lo mismo que /app/sdfsfjfdjfjfdnfjnsdfjn, que es lo mismo /app/doefmdy así sucesivamente. Simplemente asigna la cadena a una variable de parámetros declarada; vea el ejemplo aquí: reacttraining.com/react-router/web/example/url-params , que declara la cadena como parámetros :id). Si desea utilizar parámetros, deberá utilizar la ruta Regex (para múltiples parámetros) o hacer que sus rutas sean más únicas en /app/dashboardcomparación con /app/settings/5e0b97003e95542de2d8e3a1, donde 5e0b97003e95542de2d8e3a1hay parámetros únicos id.
Matt Carlotta
En resumen, /app/:idcoincidiría con la misma ruta que /app/:account, que coincidiría con la misma ruta que /app/settings. Debido al comodín param, no son lo suficientemente únicos como para enfrentarse.
Matt Carlotta
@MattCarlotta, si quieres los puntos, estoy feliz de dártelos.
Mel
Estoy bien, gracias. Si todas sus preguntas han sido respondidas, puede eliminar la recompensa.
Matt Carlotta