location missing in props (ReactJS) - javascript

I'm kind of new to reactjs and I'm learning step by step. I'm facing a problem and that is when I try to access the location parameter in the props it returns me undefined. I tried to find the solution but most of the people have written I have to add my component in the router but I'm wrapping it in the router but still, I don't have access to location parameter
export const abcdContainer = withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(abcd));
I'm trying to access the location parameter in the and component but the problem is there is no location parameter in it. Can somebody tell me what is it that is going wrong
Please if anybody know what is wrong please tell me I have spent half of my day and I can't figure it out
CODE AND VERSION ADDED WITH URL
Router version => 2.8.1
URL : http://localhost:3000/somePage?someParam=cm9oYW5qYWxpbHRlYWNoZXJAZ21haWwuY29t
abcdContainer.js
const mapStateToProps = (state, ownProps) => {
// some code over here
}
const mapDispatchToProps = (dispatch, ownProps) => {
// some code over here
};
export const abcdContainer = withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(abcd));
abcd.jsx
class abcd extends Component {
constructor(props, context) {
super(props, context);
this.state = {
// setting state over here
};
}
abcdFunction(){
if (this.props.location.query.someParam !== undefined){ // trying to extract someParam value from location
// some code over here
}
}
render() {
// some code over here
}
}
export default CSSModules(abcd, styles, { allowMultiple: true });
Here is the flow. The router redirect to the container and then the container redirect to the real component
Route.js
export const Routes = ({ store }) => (
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={aContainer}>
<IndexRoute component={IContainer} />
// some routes
<Route path="/openAbcd" component={() => (<abcdContainer caller="aaaaaaa" />)} />
// some routes
</Route>
// some routes
</Router>
</Provider>
);
Routes.propTypes = {
store: React.PropTypes.object.isRequired,
};

<Route path="/openAbcd" component={() => (<abcdContainer caller="aaaaaaa" />)} />
If you use an inline arrow function to render your component why you don't just pass the props directly to the component? Then you will not need withRouter(). Like this:
<Route path="/openAbcd" component={props => (<abcdContainer caller="aaaaaaa" {...props} />)} />
Also the docs of react-router v2.8.1 says about withRouter():
A HoC (higher-order component) that wraps another component to provide props.router.
It doesn't provide location but router as a prop. I recommend you to update react-router to v4 or at least v3.
EDIT: "But why were the props not being inserted implicitly?":
React knows two types of components. Stateless functional components and class-based components. Functional components are functions that accept a single props object argument with data and return a React element. Take a look at this line of your code again:
<Route path="/openAbcd" component={() => (<abcdContainer caller="aaaaaaa" />)} />
You passed an arrow function () => (<abcdContainer caller="aaaaaaa" />) to your <Route> element which is an inline definition of a functional component that takes props as a parameter and returns a rendered React element, in this case this is your <abcdContainer>. But as you can see you omitted the props parameter in your function by defining it with empty parenthesis: () => (<AComponent />). React does not automatically pass props to child components that are rendered inside a functional component. When wrapping your <abcdContainer> into an inline functional component you are responsible for passing props to it yourself.
But if you pass the class/variable of your class-based/functional component to the component prop of your <Route> element like you did it in your other routes then it will render this component an implicitly pass props to it because it isn't wrapped in a functional component:
// this will directly render <aContainer> and pass the props to it
<Route path="/" component={aContainer}>
What you did is creating a "functional unnamed wrapper component" that doesn't take any props and also doesn't pass any props further down.
And note that you should not use withRouter() extensively. This HOC is only there to inject a router into components that do not get rendered by a matching route itself and are e.g. much deeper down your component tree. In your case you do not need it.

Related

Avoid re-rendering of parent components with react-router-dom v6 nested routes

In the process of upgrading my react app to react-router-dom v6, I had to refactor how the nested routing is handled. In the previous versions of react-router-dom, I managed to declare nested routes in child components, and that avoided re-rendering the parent component every time the route changed in the app.
The configuration could be summarized as something like this:
const Component = React.lazy(() => import('./path/to/Component'));
<Switch>
<Route path="..." component={Component} />
</Switch>
Then, inside the Component:
const NestedComponent = React.lazy(() => import('./path/to/NestedComponent'));
const Component = ({match}) => {
...
<Route path={match.url} component={NestedComponent} />
...
}
Following the upgrade guide for react-router-dom v6, I refactored the components above into something as the following:
const Component = React.lazy(() => import('./path/to/Component'));
<Routes>
<Route path=".../*" element={
<React.Suspense fallback={"loading 1..."}>
<Component />
</React.Suspense>
} />
</Routes>
Then in the component:
const NestedComponent = React.lazy(() => import('./path/to/NestedComponent'));
const Component = ({match}) => {
...
<Routes>
<Route path={match.url} element={
<React.Suspense fallback={"loading 2..."}>
<NestedComponent />
</React.Suspense>
} />
</Routes>
...
}
What I expected would have been that the Component component would not re-render while changing the route, only the NestedComponent (hence, showing only loading 2... inside the Component). But instead, when I change the route, all the components re-render. Also, I'm not sure whether with the new syntax (using element instead of component in the Route component) makes sense combined with React.lazy.
Is there a way to avoid the re-rendering of the parent component?
With the new syntax, does it make sense to use React.lazy? Or is there another way to lazily load components?

reactjs props getting lost in Route creation

I'm attempting to pass a user's auth state down to components via a react-router-dom switch block (I believe I ought to look at implementing redux but that's a question for later).
There's a Home view that gets passed all the login information after the user authenticates, and I can see the authState object as a prop in the home component using react devtools:
import React from "react";
import Dashboard from "../Dashboard";
import {Switch, Route} from "react-router-dom";
import NoMatch from "../NoMatch";
function Home(props) {
// authState exists
return (
<Switch {...props}>
// authState exists
<Route exact path="/" render={(...props) => <Dashboard {...props} />} /> // authState gone
<Route component={NoMatch} />
</Switch>
);
}
export default Home;
after executing this it renders the Dashboard, and tracing down the component chain with react devtools I can see that the prop has been passed from the Home component to the Switch component successfully. However, once it gets to the Route the prop is gone, and the only props available in the Dashboard component are the history, location and match props.
Why are the props missing and what's the right way to pass them down?
Couple of improvements needed in your code:
Passing props to Switch component is unnecessary
No need to collect router props using the rest syntax only to spread them later
Main Problem:
props inside the function passed to render prop refers to the router props instead of the props passed to Home component. You are using the same identifier for two different things.
Solution
Use different names for router props and the props passed to Home component that you want to pass down to Dashboard component
function Home(props) {
return (
<Switch>
<Route
exact
path="/"
render={(routerProps) => <Dashboard {...routerProps} {...props} />}
/>
<Route component={NoMatch} />
</Switch>
);
}
Alternatively, if you don't need access to router props in the Dashboard component, remove the props parameter from the render prop function.
<Route
exact
path="/"
render={() => <Dashboard {...props} />}
/>
Now, you won't have access to router props inside the Dashboard component but the auth state of the user will be passed down to Dashboard component.
In the most recent versions of the react-router-dom you must replace render and component attributes with element. You cannot pass a callback function there in which there were specified the route props anymore.
Instead you can use a hook in your routed component:
const params = useParams();
to obtain the parameters.
See more in the official documentation.

Programmatically creating routes from object does not work when doing it within a component

I am attempting to create a standard component for my application that returns a list of compatible routes with paths and children components systematically derived from an object featuring all of my application pages (eg. /log-in).
The issue is, I can place the route-returning script directly into the root react component, but when breaking that same code out into its own component and rendering in the root, it does not return any children, even though it returns the same thing.
This may have something to do with React route match, but I am not sure.
Example:
The object with all my pages:
...
const views = [
{
path: "/auth/sign-up",
exact: true,
component: SignUp,
secure: true
},
{
path: "/auth/sign-in",
exact: true,
component: SignIn,
secure: true
},
]
The Viewer component meant to catch the paths and return the compatible routes:
As an example, I am filtering based on whether the value of secure is true or not from the object above.
function Viewer(props) {
return (
views.filter(view => view.secure === props.secure).map((view, index) => (
<Route
key={index}
path={view.path}
exact={view.exact}
children={<view.component />}
/>
))
)
}
My root react component, featuring the Viewer component (From above), which does not work:
export default function Root() {
return (
<switch>
<Viewer secure={true} />
</switch>
);
}
...
My root react component where the routing code is placed directly inside, which does work:
export default function Root() {
return (
<Switch>
{views.filter(view => view.secure === true).map((view, index) => (
<Route
key={index}
path={view.path}
exact={view.exact}
children={<view.component />}
/>
))}
</switch>
);
}
...
Can you please assist me in understanding the difference between these two examples, the one using the routing code placed directly in the root element versus the root where it features the Viewer component which should do the same thing. Any info you can spare would be appreciated.
The cause of your issue is related how react-router-dom works, I mean Switch component is intended to render just one Route component at time, so the children for Switch component must be directly a Route or Redirect component instead of a wrapper for that Routes, if you want to do something like that with a Viewer component, you should move your Switch component inside your Viewer component, something like this
function Viewer(props) {
const routes = views.filter(view => view.secure === props.secure).map((view, index) => (
<Route
key={index}
path={view.path}
exact={view.exact}
children={<view.component />}
/>
));
return <Switch>{routes}</Switch>;
}
Here you have the official documentation that tells you about that and explain how it works.
Switch component will iterate over all its children to figure out if the Route should be render or not related to the url path, so for that reason is mandatory that the direct children of a Switch component must be Route or Redirect components cause these contains all the props , path is one of these props, so it is used to match the current url path to be compared with the path prop of the Route component.

React Router DOM can't read location state

I've seen this question before but only applied to Class components so I am not sure how to apply the same approach for functional components.
It's simple, I have a link <Link to={{ pathname="/first-page" state: { name: "First person" }>First Page</Link> and then in the component FirstPage.js I need to read the name state so I have tried the following:
import React from "react";
export default props => {
React.useEffect(() => {
console.log(props)
}, []);
return (
<div>
<h1>First Page</h1>
<p>Welcome to first page, {props.location.state.name}</p>
</div>
);
};
I have been reading React Router location documentation and it should pass the state as a component property but it isn't. I am quite sure there is something I am doing wrong or not seeing at all.
In case you wanna give a try on the whole code, I will leave here a CodeSandbox project to "test" this.
Therefore, any ideas on what am I doing wrong? Thanks in advance.
This isn't an issue of class-based vs. functional component, but rather how Routes work. Wrapped children don't receive the route params, but anything rendered using the Route's component, render, or children prop do.
Route render methods
<Switch>
<Route path="/first-page" component={FirstPage} />
<Route path="/second-page" component={SecondPage} />
</Switch>
The other option is to export a decorated page component using the withRouter HOC, or if a functional component, use hooks.
withRouter
You can get access to the history object’s properties and the closest
<Route>'s match via the withRouter higher-order component. withRouter
will pass updated match, location, and history props to the wrapped
component whenever it renders.
const FirstPage = props => {
React.useEffect(() => {
console.log(props)
}, []);
return (
<div>
<h1>First Page</h1>
<p>Welcome to first page, {props.location.state.name}</p>
</div>
);
};
export default withRouter(FirstPage);
hooks
React Router ships with a few hooks that let you access the state of
the router and perform navigation from inside your components.
const FirstPage = props => {
const location = useLocation();
console.log(location);
return (
<div>
<h1>First Page</h1>
<p>Welcome to first page, {location.state.name}</p>
</div>
);
};
export default FirstPage;
I found a problem in your router. Instead of using:
<Route path="/first-page">
<FirstPage/>
</Route>
Use:
<Route path="/first-page" component={FirstPage}/>
Otherwise use the hook provided by the library let location = useLocation();, this way you will have access to the location object.

React Router Route Params in Redux

Using redux and react router, I would like to access a route parameter on a component other than the one specified by the route.
I've got react-router, redux, and react-router-redux set up as follows and I would like to access the reportId parameter from my redux store.
const history = syncHistoryWithStore(browserHistory, store);
const store = createStore(reducers);
const routes = (
<Router history={history}>
<Route path="/" component={MainLayout} >
<IndexRoute component={Home} />
<Route path="reports">
<Route path=":reportId" component={ReportViewerContainer} />
</Route>
</Route>
</Router>
);
ReactDOM.render(
<Provider store={store}>{router}</Provider>,
document.getElementById('root')
);
I've tried hooking up react-redux-router to store the route state, but I've found that there is nothing useful stored inside of redux besides the full path to the active route. This has left me with the options of either parsing out the reportId from the path in redux, or creating my own custom action in redux and updating it with the componentWillReceiveProps lifecycle method inside of my ReportViewerContainer component. There must be a better way?
If you want to access the router parameter inside the component, the best way to achieve this by using withRouter
https://egghead.io/lessons/javascript-redux-using-withrouter-to-inject-the-params-into-connected-components
You will find the better understanding with above example.
If you want to access the router in the redux store, there's a HOC called withRouter (docs). If I remember correctly, this will pass any router state to your component as props.
The only change you need to make to your component would be this:
import { withRouter } from 'react-router'
export default withRouter(ReportViewerContainer);
So I think you can use context to fetch the location that will be passed through the Router. ( Refer that link on how it works.) This should give you a solid direction to work on.
This can also help you down the lane to work with internationalization as well.
class MyComponent extends React.Component {
static contextTypes = {
router: React.PropTypes.object
};
render(){
// By declaring context type here, and childContextTypes
// on the parent along with a function with how to get it,
// React will traverse up and look for the `router` context.
// It will then inject it into `this.context`, making it
// available here.
}
}

Categories

Resources