React Router with - Ant Design Sider: como preencher a seção de conteúdo com componentes para o item de menu relevante

8

Estou tentando usar o sider do menu AntD como um painel de guias.

Desejo colocar componentes dentro do conteúdo para que o painel de conteúdo renderize o componente relacionado quando um item de menu for clicado.

Como faço para que essa estrutura aceite componentes como o conteúdo de cada item de menu?

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;

Quando tento renderizar isso, nenhum erro é gerado, mas a div do conteúdo não é atualizada com o PasswordForgetForm que eu especifiquei como o conteúdo do item de menu.

No entanto, tentei a sugestão de Chris - que funciona bem para renderizar o componente para cada um dos diferentes itens de menu divs no segmento de layout - a desvantagem dessa abordagem é que, a cada atualização de página, o caminho vai para o componente de conteúdo para esse item de menu específico, em vez da página de teste que contém o menu - e o componente de conteúdo com esse conteúdo relevante. Se essa abordagem for aprovada como sensata, existe uma maneira de manter a página atualizada na página original, em vez de tentar acessar um URL de item de menu de subconjunto?

Atualizar

Eu encontrei essa literatura . Eu acho que isso pode ser o que eu preciso saber, mas a linguagem é técnica demais para eu entender. Alguém pode ajudar com uma versão em inglês simples deste assunto?

Eu também encontrei este tutorial que usa o Pose para ajudar na renderização. Embora essa estrutura seja o que estou tentando alcançar, parece que o Pose foi preterido. Alguém sabe se é necessário ir além do react-router para obter este resultado, ou existe uma solução no react que eu possa implementar?

Este exemplo é semelhante ao que eu quero, mas não consigo encontrar nada que defina a barra lateral (que parece ser um argumento necessário para fazer isso funcionar).

Eu também vi este post . Embora algumas das respostas sejam uma cópia e pasta dos documentos, pergunto-me se a resposta de Adam Gering está mais próxima do que eu preciso. Estou tentando manter o menu Sider na rota / dash o tempo todo. Esse URL não deve mudar - independentemente de qual item de menu no sider o usuário clica, mas a div de conteúdo no componente / dash deve ser atualizada para renderizar o componente no caminho de rota especificado.

APLICANDO A SUGESTÃO DE CHRIS

Chris gentilmente ofereceu uma sugestão abaixo. Eu tentei, mas a circunstância em que ele não funciona como desejado é quando o item de menu é clicado (e o componente relevante é carregado corretamente na divisão de conteúdo, mas se eu atualizar a página, a atualização tentará carregar uma página com um URL que é para o componente que deveria estar dentro da div de conteúdo na página do painel.

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;

No meu arquivo de rotas, tenho:

export const ACCOUNT = '/account';

Eu também encontrei este tutorial - que usa a mesma abordagem descrita acima - e não é redirecionado da mesma maneira que meu código é atualizado na página. O tutorial usa Route em vez de BrowserRouter - mas, caso contrário, não vejo diferenças.

PRÓXIMA TENTATIVA

Vi este post (obrigado Matt). Eu tentei seguir as sugestões nesse post.

Eu removi o invólucro do roteador externo da página Painel (existe um roteador em torno do App.js, que é onde a rota para o Painel é configurada).

Em seguida, no meu Painel, alterei a div Conteúdo para:

Eu adicionei:

let match = useRouteMatch();

para o método render dentro do meu componente Dashboard (como mostrado aqui ).

Adicionei useRouteMatch à minha declaração de importação do react-router-dom.

Isso produz um erro que diz:

Erro: chamada inválida. Ganchos só podem ser chamados dentro do corpo de um componente de função. Isso pode acontecer por um dos seguintes motivos: 1. Você pode ter versões incompatíveis do React e do renderizador (como React DOM) 2. Você pode estar violando as Regras dos Ganchos

Há mais mensagens no erro acima, mas o estouro de pilha não me permite publicá-lo porque possui um link curto

Não sei o que fiz de errado nessas etapas, mas se eu comentar a instrução let, a página será carregada.

Quando vou a / Dashboard e clico em "account", espero que o componente da conta seja renderizado dentro da div de conteúdo que tenho no layout da página Dashboard. Em vez disso, ele redireciona diretamente para uma página em localhost3000 / account (para a qual não há referência de página - é apenas um componente para renderizar dentro da página Painel).

Portanto, isso é pior do que o problema com o qual comecei - porque, pelo menos no início, o redirecionamento aconteceu apenas na atualização da página. Agora isso acontece imediatamente.

Encontrei este repositório com exemplos de cada tipo de rota. Não consigo ver o que está fazendo e não estou.

Este post parece ter tido o mesmo problema que eu e o resolveu usando a mesma abordagem que eu tentei. É uma publicação de 2018, para que a passagem do tempo torne a abordagem desatualizada - mas não vejo diferença entre o que essa solução de publicação implementa e minha tentativa original.

PRÓXIMA TENTATIVA

Eu pensei que tinha encontrado uma abordagem que funcione na resposta de Lyubomir neste post .

Eu tenho:

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

{ ${this.props.match.url}/account}> Configurações da conta

Em seguida, na div no componente traço onde quero exibir esse componente, tenho:

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

Isso funciona. Na atualização da página, posso manter as coisas funcionando como deveriam.

NO ENTANTO, quando adiciono um segundo item de menu com:

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

Em vez de renderizar o componente Aprendizado quando o item de menu para / aprender é clicado (e a barra de URL muda para aprender), o componente Conta é renderizado. Se eu excluir a exibição da conta do extrato do switch, posso exibir o componente de aprendizado correto.

Com essa tentativa, tenho os itens de menu trabalhando para corresponder a uma extensão de URL do painel (por exemplo, localhost / dash / account ou localhost / dash / learn) apenas para UM item de menu. Se eu adicionar um segundo item de menu, a única maneira de processar corretamente o segundo componente é excluindo o primeiro item de menu. Estou usando o switch com caminhos exatos.

Quero que esta solução funcione com vários itens de menu.

Eu tentei o caminho alternativo para o URL (por exemplo:

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

é o mesmo que:

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

Eu li a explicação neste blog e, embora não compreenda completamente essas opções. Eu li isso . Ele sugere que a instrução match agora é um método herdado para renderizar componentes (agora que os ganchos estão disponíveis). Não consigo encontrar nenhum material de treinamento que mostre como usar ganchos para alcançar o resultado esperado.

Mel
fonte
Por favor, inclua o PasswordForgottenFormcomponente também.
AleksandrH
É apenas um componente const com um formulário. Cada item de menu possui um componente separado (alguns são componentes de classe) que é renderizado na div de conteúdo da seção de layout no componente de teste.
Mel

Respostas:

4

A melhor solução é usar o React Router <Link>para fazer com que cada item de menu seja vinculado a um caminho específico e, em seguida, no conteúdo, usando <Switch>para renderizar o componente correspondente. Aqui está o documento: React router

Renderizar com roteador de reação

<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 com teclas de menu

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 renderização antd-menu-click

SUV97
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Samuel Liew
Obrigado Chris e Matt. Eu aprecio muito toda a ajuda na tentativa de resolver isso.
Mel
0

Encontrei a resposta de Lyubomir neste post. Funciona. Rotas aninhadas com o roteador de reação v4 / v5

O link do item de menu é:

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

O caminho de exibição é:

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

Há dois pontos antes do nome do componente. Não sei por que. Se alguém souber como essa abordagem é chamada - eu ficaria grato por uma referência para que eu possa tentar entendê-la.

Mel
fonte
Na verdade - falei em breve. Enquanto isso funciona. Há uma falha. Funciona apenas com um link. Se eu adicionar uma rota a um segundo link de menu (chamado Aprendizado), seguindo a mesma abordagem, tenho que excluir a exibição da Conta para poder renderizar o componente Aprendizado. Se eu tentar manter os dois, o componente da conta é processado mesmo quando eu solicitei o componente Aprendizado.
Mel
O uso :é um paramscuringa. Assim /app/accounté o mesmo que /app/sdfsfjfdjfjfdnfjnsdfjn, que é o mesmo que /app/doefmde assim por diante. Ele apenas atribui a string a uma variável de params declarada - veja o exemplo aqui: reacttraining.com/react-router/web/example/url-params , que declara a string como params :id). Se você quiser usar parâmetros, precisará usar o caminho Regex (para vários parâmetros) ou tornar seus caminhos mais exclusivos /app/dashboardversus /app/settings/5e0b97003e95542de2d8e3a1, onde 5e0b97003e95542de2d8e3a1existem parâmetros únicos id.
Matt Carlotta
Em resumo, /app/:idcorresponderia à mesma rota que /app/:account, que corresponderia à mesma rota que /app/settings. Por causa do curinga param, eles não são únicos o suficiente para serem comparados.
Matt Carlotta
@MattCarlotta - se você quiser os pontos - fico feliz em dar a você.
Mel
Estou bem, obrigado. Se todas as suas perguntas foram respondidas, você pode remover a recompensa.
Matt Carlotta