reactjs props getting lost in Route creation - javascript

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.

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?

React Router Link to same component with different URL not re-rendering

I have a component called Posts that lists all posts in a blog. I have links around the post's usernames which link to the same (Posts) component only with a different URL containing a user id to query the posts with. The problem is that the Posts component will not re-render upon visiting this URL, although the URL in the browser changes accordingly.
Routes in App.js:
<Routes>
<Route path='/' element={<Posts/>}/>
<Route path='/user/:user_id/posts' element={<Posts/>}/>
</Routes>
Links in Posts.js
<Link to={`/user/${post.user.id}/posts`}>
{post.user.username}
</Link>
I have tried various things including trying to add a key to the component but it didn't work. I also saw a "force re-render" code snippet but it was for a previous version of React Router.
How can I force the rendering of the Posts component in React Router v6?
If Posts is rendered by a route that has changing route params
<Routes>
<Route path='/' element={<Posts/>}/>
<Route path='/user/:user_id/posts' element={<Posts/>}/>
</Routes>
then it should "listen" for changes to the route's match params.
In Posts use the useParams hook to access the user_id route match param, and use an useEffect hook with a dependency on user_id to run/rerun any logic.
import { useParams } from 'react-router-dom';
...
const { user_id } = useParams();
useEffect(() => {
if (user_id) {
// do something with user_id
}
}, [user_id]);
If Posts is a class component, then either convert it to a function component so it can use React hooks, or create a wrapper component or a new withRouter replacement to use the hook and inject params as a prop.
I'm not sure but maybe this kind of code would work I think...?
<Routes>
<Route path='/' element={props => <Posts {...props} />}/>
<Route path='/user/:user_id/posts' element={props => <Posts {...props} />}/>
</Routes>
And if it does not works,
maybe just using <a> instead of <Link> might be work.
<a href={`/user/${post.user.id}/posts`}>
{post.user.username}
</a>

Set local variable in method inside the html in reactjs

I have this method render, within which I am creating a local variable category. I need to set the value of this variable inside the html in return statement using JSX.
I am using curly brackets to assign the value to the local variable, but this is not working for me. eg {category='business'}
This is what I am doing currently
export default class App extends Component {
render() {
let category = 'about';
return (
<Router>
<div>
<NavBar/>
<Switch>
<Route path="/about">
</Route>
<Route path="/business">
{category='business'}
</Route>
<Route path="/entertainment">
{category='entertainment'}
</Route>
<Route path="/general">
{category='general'}
</Route>
<Route path="/health">
{category='health'}
</Route>
<Route path="/science">
{category='science'}
</Route>
<Route path="/sports">
{category='sports'}
</Route>
</Switch>
<News pageSize={5} country="in" category={category}/>
</div>
</Router>
)
}
}
This one instead shows the category on screen.
Note : I know there are multiple ways in which I can achieve this but I wanted to know how to set local variable within my method
First of all, look into functional components.
A Route component expects a component, not logic. Handle the actual variable change in that component or elsewhere via react-router's useLocation hook.
What you're attempting to do is to change the state of the variable. Cue React's useState and useEffect hooks (the component needs to be a functional component for hooks to work).
First import the hook:
import { useState } from 'react';
Then, in your case:
const [category, setCategory] = useState('about') // initial value
and when you need to update it:
setCategory('science') // new value

location missing in props (ReactJS)

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.

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