web-dev-qa-db-ja.com

react-router-dom:無効なフック呼び出し、フックは関数コンポーネントの本体の内部でのみ呼び出すことができます

ルートをネストしようとしました: Catalog コンポーネントに製品のカタログがあり、URL "backoffice/catalog"と一致します。

URLが "backoffice/catalog/edit"と一致する場合、 Edition コンポーネントにルーティングしたいのですが、 Edition コンポーネントを Catalog の子にして、小道具を共有します。

ネストされたルートが機能しない理由が本当にわかりません。保存してください!私のアプリに問題がある場合は、遠慮なく教えてください。Javascriptはよく知っていますが、React:D

これが私のAppコンポーネントです:

import React from "react";
import { Route, Switch } from "react-router-dom";
import { Home } from "./components/Static/Home.js";
import { Dashboard } from "./components/Backoffice/Dashboard.js";
import { Catalog } from "./components/Backoffice/catalog/Catalog.js";
import { Login } from "./components/Login/Login.js";
import { Signup } from "./components/Signup/Signup.js";
import { PrivateRoute } from "./components/PrivateRoute.js";
import "./scss/App.scss";
import {Header} from "./components/Structure/Header";
import {BOHeader} from "./components/Structure/Backoffice/Header";
import {List} from "./components/Listing/List";

function App()
{
  return (
    <div className="App">
      <div className="App-content">
          <Switch>
              <Route path='/backoffice' component={BOHeader} />
              <Route path='/' component={Header} />
          </Switch>
          <Switch>
              <Route exact path='/' component={Home} />
              <Route exact path='/login' component={Login} />
              <Route exact path='/signup' component={Signup} />
              <Route path='/listing' component={List}/>
              <PrivateRoute exact path='/backoffice' component={Dashboard}/>
              <PrivateRoute exact path='/backoffice/catalog' component={Catalog}/>
          </Switch>
      </div>
    </div>
  );
}

export default App;

これが私のカタログコンポーネントです(ルートはrenderメソッドで作成されます:

import React from 'react';
import Data from '../../../Utils/Data';
import {Product} from './Product';
import {Edition} from './Edition';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    useRouteMatch,
    useParams
} from "react-router-dom";

export class Catalog extends React.Component
{
    state = {
        title: '',
        products: [],
        editionProduct: null
    };

    obtainProducts = () =>
    {
        Data.products.obtain()
            .then(products => {this.setState({products: products});})
    };

    editProductHandler = product =>
    {
        this.setState({editionProduct: product});
    };

    saveProductHandler = product =>
    {
        Data.products.save(product).then(() => {
            this.state.products.map(item => {
                item = item._id === product._id ? product : item;
                return item;
            })
        });
    };

    deleteProductHandler = event =>
    {
        const productId = event.target.closest('.product-actions').dataset.productid;
        let products = this.state.products.filter(product => {
            return product._id !== productId;
        });
        this.setState({products: products}, () => {
            Data.products.remove(productId);
        });
    };

    displayProducts = () =>
    {
        return this.state.products.map(product => {
           return (
                <li key={product._id} className='catalog-item'>
                   <Product
                       deleteProductHandler={this.deleteProductHandler}
                       editProductHandler={this.editProductHandler}
                       data={product}
                   />
               </li>
            )
        });
    };


    componentWillMount()
    {
        this.obtainProducts();
    }

    render() {
        const Products = this.displayProducts();
        let { path, url } = useRouteMatch();
        return (
            <div className={this.state.editionProduct ? 'catalog edit' : 'catalog'}>
                <h1>Catalog</h1>
                <Switch>
                    <Route exact path={path}>
                        <ul className='catalog-list'>{Products}</ul>
                    </Route>
                    <Route path={`${path}/edit`}>
                        <Edition saveProductHandler={this.saveProductHandler} product={this.state.editionProduct} />
                    </Route>
                </Switch>
            </div>
        );
    }
}

何か案は ?

6
Janus

TypeScriptでReact Routerを設定するときに同じ問題があったので、4つのステップでAndriiの回答をもう少し詳しく説明します。

1-npm/yarnパッケージ

yarn add react-router-dom --save
yarn add @types/react-router-dom --save-dev

または

npm install react-router-dom --save
npm install @types/react-router-dom --save-dev

2-index.tsx


1)高次のコンポーネント(この場合はアプリ)をインポートするときは、中かっこを使用しないでください。デフォルトでアプリがエクスポートされます。

2)BrowserRouterは、次のエラーを防ぐために、「default withRouter(Class)」としてエクスポートされるクラスではなく、上位レベルにある必要があります。

「ルーターの外でRouteまたはwithRouter()を使用しないでください」

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import * as serviceWorker from './serviceWorker';
import App from './app';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

serviceWorker.unregister();

3-app.tsx


1)react-router-dom、withRouter&RouteComponentProps(または独自のPropType定義)からインポートします。

2)React.Componentを拡張し、RouteComponentPropsインターフェースを使用します。

3)ルーティングデータを共有するコンポーネントに小道具を渡します。

4)高次クラスをdefault withRouterとしてエクスポートします。

import React, { ReactElement } from 'react';
import { Switch, Route, withRouter, RouteComponentProps } from 'react-router-dom';
import { ExpensesPage } from './pages/expenses/expenses.page';
import { HomePage } from './pages/home/home.page';
import { HeaderComponent } from './components/header/header.component';
import './app.scss';

class App extends React.Component<RouteComponentProps> {
  public render(): ReactElement {
    return (
      <div className='playground'>
        <HeaderComponent {...this.props} />
        <div className="playground-content">
          <Switch>
            <Route exact path='/' component={HomePage} {...this.props} />
            <Route exact path='/expenses' component={ExpensesPage} {...this.props} />
          </Switch>
        </div>
      </div>
    );
  }
}

export default withRouter(App);

4-header.component


クラスを拡張するRouteComponentPropsを介して、通常は履歴、場所、マッチとしてルーティングプロップにアクセスできます。

import React, { ReactElement } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import './header.component.scss';

export class HeaderComponent extends React.Component<RouteComponentProps> {
  public render(): ReactElement {
    const { location } = this.props;
    console.log(location.pathname);

    return (
      <header className="header">
        {/* ... */}
      </header >
    );
  }
}

私がこれをwebpackとreduxのないシンプルな環境で機能させるには少しチャレンジがあったのでそれが役に立てば幸いです。前回、次のバージョンで正しく動作します:

{
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-router-dom": "^5.1.2",
    "sass-loader": "^8.0.2",
    "style-loader": "^1.1.3",
    "TypeScript": "^3.8.2",
    "webpack": "^4.41.6",
    "webpack-dev-server": "^3.10.3",
},
{
    "@types/react-router-dom": "^5.1.3",
    "webpack-cli": "^3.3.11"
}

1
Daniel Santana