React router - pass function to children - javascript

I´m developing a react-app using react-router, redux and redux-saga with the following structure:
App.js: Place where all my routes are located
<Switch>
<Route path="/" exact component={Index}/>
<Route path="/user" exact component={Userlist}/>
<Route path="/user/:id" exact component={Userdetail}/>
</Switch>
Userlist.js:
Stateful component connected to redux and fetching data from my backend. Data is listed in a table and has a link to Userdetail component.
In the Userlist component all state management has to be done (e. g. update/delete an user)
After clicking on one row in the table the Userdetail component is rendered (I pass the user data via Router-Link state to my component).
<Link to={{
pathname: `/user/${props.user._id}`,
state: {
user: user
}
}} className="stretched-link"/>
Passing the data to my userdetail list is working as it should by using state and access it via props.location in my userdetail component.
Userdetail.js:
In this component all other data of the user is displayed. Also there are two buttons which handle the update and delete part. The reference of these two function should be passed as props from Userlist to Userdetail component.
Now I´m coming to my question:
How can I pass function references from Userlist.js to the corresponding child component (Userdetail.js)?
I know there is a way to pass multiple props to a route using the render method instead of the component, but struggle to implement this according to my app structure. If possible I want to maintain all my routes in App.js or a separate file.
Thank you very much in advance

Related

Passing props to dynamically rendered components in React

I have a page that is displaying several of my star components that each have their own name and a prop called starType
I am generating several different of these stars with the following code
if (num > 0) {
return (
<div className="starWrapper">
<Star
name={`${makeid()}`}
starType={`${starList[Math.floor(Math.random() * 6 + 1)]} ${posList[Math.floor(Math.random() * 9 + 1)]}`}
></Star>
{makeStars((num - 1))}
</div>
);
And this is the star component
<NavLink to={props.name}>
<h1 className="star-label">{props.name}</h1>
<div className={``}>
<div className={`starBall ${props.starType}`}></div>
</div>
</NavLink>
At the moment I want the user to be able to click on each star and have it lead to a page. I have achieved that with react-router's dynamic routing
<Route
exact
path="/:id"
render={(props) => <GenerateSystem {...props} />}
/>
the issue is I want the page that is generated from my generateSystem component to have the starType prop passed to it by the star component. I am aware of React's one way data flow and I think that might be the issue. How can I pass prop data from an auto generated component to another auto generated component?
My full code is viewable here. The components I'm talking about are in the interstellar-view and systems folder.
since you are passing name through URL params so passing starType using query params is an easy option.
So URL would look like this www.example.com/123?starType=red-giant
In your star.jsx, make a modification like this
<NavLink to={`/${props.name}?starType=${props.starType}`}>
...
</NavLink>
In your App.js, make a modification like this
<Switch >
<Route exact path="/:id" component={GenerateSystem} />
<Route exact path="/sol" component={SolSystem} />
<Route exact path="/" component={Interstellar} />
</Switch>
(We do not need to render and pass props since we can use useParams in GenerateSystem.js)
In your GenerateSystem.js, make a modification like this
import React from "react";
import { Link, useLocation, useParams } from "react-router-dom";
function useQuery() {
return new URLSearchParams(useLocation().search);
}
export const GenerateSystem = (props) => {
const {name} = useParams();
const query = useQuery();
const starType = query.get('starType')
return(<div className={starType}>System <p>{name}</p></div>)
}
Refs:
https://reactrouter.com/web/api/Hooks/useparams
https://reactrouter.com/web/example/query-parameters
EDIT:
You can use Redux-store/Context-API to have a global store, so that name and starType can be stored globally and can be accessed in different components
More Use-cases Example -> for other people that came here:
As in React-Router-Dom V6-> there is no render method any more,
See Why does have an element prop instead of render or component?
We mentioned this in the migration guide from v5 to v6, but it's worth repeating here.
In React Router v6 we switched from using v5's and APIs to . Why is that?...
So I needed another way of dynamically rendering all routes for the Router, with a pre declared array with all routes:
const routingList = [{title: 'Home', search: '/', component: Home, icon: 'fa-home'},{...}]
<Routes>
{
routingList.map((routing) => {
let Child = routing.component;
return <Route key={routing.search} path={routing.search} element={<Child {...routing.compProps} />} />;
})
}
<Route path="*" element={<Notfound />} />
</Routes>
(BTW: if you also need the useLocation or the other hooks, and you are using React Class and not React functions, see my answer here:
Component with router props - For: Hooks can only be called inside of the body of a function component
)

React access a variable from child component

I have a main layout for my app and all components are rendered inside it, but this main layout needs to access a variable PageTitle from it's child components, I have looked in react documentation and come up with React Context, but I could'nt figure out how to use it in this case, sorry I am new to React.
Every component rendered by routes has a variable called PageTitle, the MainLayout should use this variable to render current page title on the top of the app, these are all functional components.
<MainLayout>
<Route path='/' exact component={HomePage} />
<Route path='/invoice' exact component={Invoice} />
</MainLayout>
Update:
I can create a context and store the value like this, but I couldn't figure out how to change it in child components.
Also I think this is a bit overkill, and there should a better solution.
export const AppContext = React.createContext({ PageTitle: 'Home' });
<AppContext.Consumer>{value=>value.PageTitle}</AppContext.Consumer>

React Router V3 - Nested Route Component unmounts on path param change

I just noticed that in react router (v3.x) a component unmounts and remounts if a path param changes. Is this the expected behaviour?
Route:
<Route path="/landing/register/:step" component={Register}/>
Now, lets say I am on route "/landing/register/personal-data" and I am navigating via <Link/> or router.push({...}) to the next registration step "/landing/register/address", the Register-component gets first unmounted and then mounted again, loosing all its state.
Is this the correct way or am I doing something wrong?
EDIT:
It seems that the problem is that I am using nested routes, where I use a component for the parent route.
This example works (not re-mounting Register-Comp on path param change):
<Route path="/landing">
<Route path="register/:step" component={Register}></Route>
</Route>
But when I use a component for the parent route, it doesnt (not re-mounting AppView-Comp, but Register-Comp on path param change):
<Route path="/landing" component={AppView}>
<Route path="register/:step" component={Register}></Route>
</Route>
I solve this problem by nesting routes in child components, like this:
// Router class
<Route path="/landing/register" component={Register}/>
//Register component
<BrowserRouter>
<div>
<Route path="/landing/register/personal-data" component={PersonalData}/>
<Route path="/landing/register/payment-data" component={PaymentData}/>
...other routes
</div>
</BrowserRouter>
But in this case i store user data in redux store instead of component state, however you can store it on your component state it is not problem.

react router this.props.location

I need a help with react-router v2+
I have to change class of navbar when route changed
for example for route /profile className will be "profile-header"
I tried to use this.props.location in navbar component but it shows undefined
Hope your help
Your navbar component (as you described it in your question) is probably not the route component, right? By route component I mean the one that you use in your react-router configuration that is loaded for a specific route.
this.props.location is accessible only on such route component, so you need to pass it down to your navbar.
Let's take an example:
Your router config:
<Router>
<Route path="/" component={App}>
// ...
</Router
Route component App:
class App extends React.Component{
// ...
render() {
return <Navbar location={this.props.location}/>
}
}
There could be a scenario where you may not have access to props.location to pass to the nav component.
Take for example - We had a header component in our project which was included in the routing switch to make it available to all routes.
<Switch>
<Fragment>
<Header/>
<Route path='..' component={...}/>
<Route path='..' component={...}/>
</Fragment>
</Switch>
In the above scenario there is no way to pass the location data to the Header component.
A better solution would be to us the withRouter HOC when a component is not being rendered by your router.
You will still have access to the router properties history, match and location when you wrap it in the withRouter HOC:
import { withRouter } from 'react-router-dom'
....
....
export default withRouter(ThisComponent)
react-router v4
From documentation:
<Route> component property should be used, when you have an existing component. <Route> render property takes an inline function, that returns html.
A <Route> with no path will always match.
Based on this, we can make a <Route> wrapper to any level of your html structure. It will always be displayed and have access to the location object.
As per your request, if a user comes to /profile page, the <header> will have profile-header class name.
<Router>
<Route render={({ location }) =>
<header className={location.pathname.replace(/\//g, '') + '-header'}>
// header content...
</header>
<div id="content"></div>
<footer></footer>
} />
</Router>
I couldn't solve it with the solutions given here and here is what worked for me:
I imported history into my component and assigned history.location.pathname to a variable which I later used for dynamic style manipulation.
In case you are rendering the component with pre-defined location.state values, first set your state with props.location.state then use your state data in your elements.

React Router + Redux - Dispatch an async action on route change?

I have a universal react app that's using redux and react-router.
I have several routes as follows:
/2016
/2015
/2014
/2013
etc.
Each route requires data from an API. Currently, i have the <Link> elements in the Navigation component dispatch an async action onClick, which populates the store with data from the API for that route.
For MVP, i'm just overwriting the post: {} contents in the store with the new post contents when the route changes, that way we get any new content that was on the API.
I've realise that having the action dispatchers on the <Link> buttons isn't optimal, as hitting the back button does not re-trigger the action dispatch to get the content for the previous route.
Is there a way to get React Router to trigger the dispatch action anytime a route change occurs? (Limiting it to listen to a specific set of routes would be a bonus).
I realise i should be getting the history from the store, but for now, it's easier to hit the API again by triggering an action dispatch in order to get the new content.
Cheers.
The 'lifecycle' hook onEnter and onChange has been removed in React-router 4 which makes most of the other answers to this question out-dated.
Whilst I recommend you to use your components lifecycle methods to achieve your goal, here is an answer to your question which works on React-router 4.
What works today is listen to the history change using History library created by the developers of React router themself and dispatch async actions from there.
// history.js
import createHistory from "history/createBrowserHistory"
const history = createHistory()
// Get the current location.
const location = history.location
// Listen for changes to the current location.
const unlisten = history.listen((location, action) => {
//Do your logic here and dispatch if needed
})
export default history
Then import the history in your application
// App.js
import { Router, Route } from 'react-router-dom';
import Home from './components/Home';
import Login from './components/Login';
import history from './history';
class App extends Component {
render() {
return (
<Router history={history}>
<div>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
</div>
</Router>
)
}
}
Source: History library
React router docs
Yeah React Router has onEnter and onLeave hooks. You could build your routes to take your store instance, so you can access it in those helpers:
const createRoutes = (store) => {
const fetchPosts = () => store.dispatch({
types: ['FETCH_POSTS', 'FETCH_POSTS_SUCCESS', 'FETCH_POSTS_FAIL',
url: '/posts'
});
return (
<Route path="/" component={App}>
<Route path="posts" component={PostList} onEnter={fetchPosts}/>
<Route path="posts/:id" component={PostDetail} />
</Route>
)
}
A better solution is to use something like redial or redux-async-connect. This allows you to co-locate your component's data dependencies with your components, while retaining the ability to test your components without touching the network.
Edit: This applies to an old, no longer supported version of react-router.
I prefer to have actions dispatched from the render prop itself:
<Route to="path" render={ props => {
this.props.toggleInfoLayer(true);
return <UserInfo />;
}} />
This is assuming you are using Redux's mapDispatchToProps argument.
I tried using the history change event handler as mentioned in the accepted answer, but I found it undesirable to be dispatching actions from a rogue file. One more place I had to think about, when Redux already provides plenty too many.

Categories