Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React get props from Router with React Hooks

I'm trying to refactor my code using React Hooks, and I don't really understand how I can get props passed down to my components via React Routers using Hooks.

The old (normal) React code looks like this:

App.js

import React from 'react';
import { withRouter } from "react-router-dom";
import {Routes} from './routes/Routes';

function App() {
    const childProps={something: "else"};
    return (
        <div className="App">
            <Routes childProps={childProps} />
        </div>
    );
}

export default withRouter(App);

Routes.js

import {Switch, Route} from 'react-router-dom';
import Game from '../game/Game';
import Scenario from '../game/Scenario';

const CustomRoute = ({ component: C, props: cProps, ...rest }) =>
    <Route
        {...rest}
        render={(props) =>
            <C {...props} {...cProps} />
        }
    />;

export const Routes = ({childProps}) => 
    <Switch>
        <Route path="/" exact component={Game} props={childProps} />
        <CustomRoute path="/scenario/:id" exact component={Scenario} props={childProps}/>
    </Switch>

Game.js

import React from 'react';

const Game = () => {
  return (
    <div className="Game">
      <header className="Game-header">
        <a href="/scenario/0">
          START
        </a>
      </header>
    </div>
  );
};

export default Game;

Scenario.js

export default class Scenario extends Component {
    constructor(props) {
        super(props);

        this.state = {
            scenarios: null,
            scenarioId: null,
            currentScenario: null
        }
    }

    async componentDidMount() {
        const scenarioId = await this.props.match.params.id;
        const scenarios = await data.scenarios;
        this.setState({scenarios, scenarioId});
        this.getScenario();
    }

    getScenario = () => {
        this.state.scenarios.forEach((scenario) => {
            if (scenario.id === this.state.scenarioId) {
                const currentScenario = scenario;
                this.setState({currentScenario});
            }
        })
    }

    render() {
        return (
            <div>
                {this.state.currentScenario != null
                    ? this.state.currentScenario.options.length === 1
                        ? (
                            <div>
                                <div>{this.state.currentScenario.text}</div>
                                <div>{this.state.currentScenario.options[0].text}</div>
                                <a href="/">Go Back</a>
                            </div>
                        )
                        : (
                            <div>
                                <div>{this.state.currentScenario.text}</div>
                                <div>{this.state.currentScenario.options.map((option, index) => (
                                    <div key={index}>
                                        <a href={`/scenario/${option.to}`}>
                                            {option.text}
                                        </a>
                                    </div>
                                ))}</div>
                            </div>
                        )
                    : null
                }
            </div>
        );
    }
};

So I found online this code which would change the way I can get props from the Router:

HookRouter.js

import * as React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

const RouterContext = React.createContext(null);

export const HookedBrowserRouter = ({ children }) => (
  <BrowserRouter>
    <Route>
      {(routeProps) => (
        <RouterContext.Provider value={routeProps}>
          {children}
        </RouterContext.Provider>
      )}
    </Route>
  </BrowserRouter>
);

export function useRouter() {
  return React.useContext(RouterContext);
};

New App.js

import React from 'react';
import { withRouter } from "react-router-dom";
import {Routes} from './routes/Routes';
import {HookedBrowserRouter, useRouter} from './routes/HookRouter';

function App() {
    const childProps={something: "else"};
    return (
        <HookedBrowserRouter>
        <div className="App">
            <Routes childProps={childProps} />
        </div>
        </HookedBrowserRouter>
    );
}

export default withRouter(App);

And I get his far with the new Scenario.js

import React, { Component, useState, useEffect } from 'react';
import data from '../data/fake';
import {useRouter} from '../routes/HookRouter';

const RouterContext = React.createContext(null);

const HookSceneario = () => {
    const [scenarios, setScenarios] = useState(null);
    const [scenarioId, setScenarioId] = useState(null);
    const [currentScenario, setCurrentScenario] = useState(null);

    // Similar to componentDidMount and componentDidUpdate:
        // Update the document title using the browser API
        // console.log(React.useContext(RouterContext));

    useEffect(() => {
        console.log(scenarios);
    });

    return (
        <div>
            // ...
        </div>
    );
}

So useState replaces the this.state inside the class constructor, and useEffect is supposed to replace componentDidMount but I can't find a way to get the props from the Router.

like image 497
Viet Avatar asked Jun 22 '19 16:06

Viet


People also ask

How do I get props from Link react router?

To recap, if you need to pass data from Link through to the new component that's being rendered, pass Link s a state prop with the data you want to pass through. Then, to access the Link s state property from the component that's being rendered, use the useLocation Hook to get access to location.

How do you pass Props With react hooks?

In order to pass the isActive prop from the parent (MyCard) to the child (MyButton), we need to add MyButton as a child of MyCard. So let's import MyButton at the top of the file. import MyButton from "./Button"; Then, let's add MyButton as a child in the return statement of MyCard component.

Can you pass props through react router?

With the react-router v5, we can create routes by wrapping with a component, so that we can easily pass props to the desired component like this.


1 Answers

I think this illustrates well what you're trying to do:

Remember:

The component rendered by a <Route> can always have access to the routeProps (match, location and history).

If it's rendered by the component prop, as in <Route ... component={Home}/> this is automatic.

If it's rendered by the render prop, you need to spread them, as in:

// You can spread routeProps to make them available to your rendered Component
const FadingRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={routeProps => (
    <FadeIn>
      <Component {...routeProps}/>
    </FadeIn>
  )}/>
)

Link on CodeSandbox


Result:

enter image description here


Full Code:

index.js

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import AllRoutes from "./AllRoutes";

function App() {
  return (
    <Router>
      <AllRoutes />
    </Router>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

AllRoutes.js

import React from "react";
import { Switch, Route } from "react-router-dom";
import Home from "./Home";
import Component1 from "./Component1";

function AllRoutes() {
  return (
    <Switch>
      <Route exact path="/" component={Home} />
      <Route exact path="/comp1" component={Component1} />
    </Switch>
  );
}

export default AllRoutes;

Home.js

import React from "react";
import { Link } from "react-router-dom";

function Home(props) {
  return (
    <div>
      I am HOME component
      <ul>
        <li>
          <Link to={"/comp1"}>Component1</Link>
        </li>
      </ul>
      I have access to routeProps: YES
      <br />
      Because I'm directly rendered from a Route
      <ul>
        <li>{"props.match:" + props.match.toString()}</li>
        <li>{"props.location:" + props.location.toString()}</li>
        <li>{"props.history:" + props.history.toString()}</li>
      </ul>
    </div>
  );
}

export default Home;

Component1.js

import React from "react";
import { Link } from "react-router-dom";
import Component1Child from "./Component1Child";
import RouterContext from "./RouterContext";

function Component1(props) {
  const routeProps = {
    match: props.match,
    history: props.history,
    location: props.location
  };

  return (
    <RouterContext.Provider value={routeProps}>
      <div>
        <b>I am Component1</b>
        <ul>
          <li>
            <Link to={"/"}>Home</Link>
          </li>
        </ul>
        I have access to routeProps: YES
        <br />
        Because I'm directly rendered from a Route.
        <br />
        And I automatically 'inherit' them when I'm rendered through the Route
        'component' prop
        <ul>
          <li>{"props.match:" + props.match.toString()}</li>
          <li>{"props.location:" + props.location.toString()}</li>
          <li>{"props.history:" + props.history.toString()}</li>
        </ul>
        <Component1Child />
      </div>
    </RouterContext.Provider>
  );
}

export default Component1;

Component1Child.js

import React from "react";
import Component1GrandChild from "./Component1GrandChild";

function Component1Child(props) {
  return (
    <div>
      <b>I am Component1Child</b> <br />
      <br />
      I have access to routeProps: NO
      <br />
      Because I'm NOT directly rendered from a Route.
      <br />I am rendered by Componen1 and routeProps are not automatically
      passed down.
      <ul>
        <li>{"props.match:" + props.match}</li>
        <li>{"props.location:" + props.location}</li>
        <li>{"props.history:" + props.history}</li>
      </ul>
      <Component1GrandChild />
    </div>
  );
}

export default Component1Child;

Component1GrandChild.js

import React from "react";
import useRouteProps from "./useRouteProps";

function Component1GrandChild(props) {
  const [match, location, history] = useRouteProps();
  return (
    <div>
      <b>I am Component1GrandChild</b> <br />
      <br />
      I have access to routeProps: YES
      <br />
      Because I'm consuming the routeProps provided by Component1 (which is the
      one directly rendered by the Route)
      <br /> And I'm consuming that through a custom hook called useRouteProps.
      <br />I am rendered by Componen1 and routeProps are not automatically
      passed down.
      <ul>
        <li>{"props.match:" + match}</li>
        <li>{"props.location:" + location}</li>
        <li>{"props.history:" + history}</li>
      </ul>
    </div>
  );
}

export default Component1GrandChild;

RouterContext.js

import React from "react";

const RouterContext = React.createContext(null);

export default RouterContext;

useRouteProps.js

import { useContext } from "react";
import RouterContext from "./RouterContext";

function useRouteProps() {
  const routeProps = useContext(RouterContext);
  return [routeProps.match, routeProps.location, routeProps.history];
}

export default useRouteProps;
like image 67
cbdeveloper Avatar answered Oct 03 '22 03:10

cbdeveloper