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.
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.
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.
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.
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:
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With