react router - pass props via url arguments - reactjs

I am developing a multilingual and multi-currency app that uses react-router, react-router-redux & react-router-bootstrap for routing.
Currently users arrive at the app with a default language and currency however I'd like to be able to provide some arguments in the url to set either the language or currency - something along the lines of http://myapp.com/page1?lang='fr'
I'd also like to have the ability to use these arguments on any page, so that:
http://myapp.com/page1?lang='fr' and http://myapp.com/page2/example1/test?lang='ru' are both handled the same way. Basically I want the router to check for the presence of these arguments in any url that is passed to it, and if they are present execute a function.
I can't find any clear information about this in the documentation for react-router but I'm sure it's possible. Can anyone point me in the right direction?

You can access the query params via
this.props.location.query.langs from inside the component also mentioned by #Lucas.
I have provided a working example for better understanding.
Apart from that In your scenario I will suggest you to look into onEnter hooks available in react router.
let Router = ReactRouter.Router;
let RouterContext = Router.RouterContext;
let Route = ReactRouter.Route;
let Link = ReactRouter.Link;
function redirectBasedOnLang(nextState, replace) {
// check nextState.location.query
// and perform redirection or any thing else via
console.log("called");
console.log(nextState.location.query);
}
const Dashboard = (props) => {
return (
<b className="tag">{"Dashboard"}</b>
);
}
FrDashboard
const FrDashboard = (props) => {
return (
<div>
<h3> Dashboard </h3>
<hr/>
Primary language: {props.location.query.lang}
</div>
);
}
class App extends React.Component {
render() {
return (
<div>
<h2>App</h2>
{/* add some links */}
<ul>
<li><Link to={{ pathname: '/dashboard', query: { lang: 'fr' } }} activeClassName="active">Dashboard</Link></li>
</ul>
<div>
{this.props.children}
</div>
</div>
)
}
};
App.contextTypes = {
router: React.PropTypes.func.isRequired,
};
ReactDOM.render(<Router history={ReactRouter.hashHistory}>
<Route path="/" component={App}>
<Route path="dashboard" component={Dashboard} onEnter={redirectBasedOnLang} />
<Route path="dashboardfr" component={FrDashboard} />
</Route>
</Router>, document.getElementById('test'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router/3.0.0/ReactRouter.min.js"></script>
<div id="test"></div>
let Router = ReactRouter.Router;
let RouterContext = Router.RouterContext;
let Route = ReactRouter.Route;
const Link = (props) => {
return (
<b className="lang" onClick={() => props.onClick(props.lang)}>{props.lang}</b>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.handleLang = this.handleLang.bind(this);
this.state = { langs: [] };
}
handleLang(lang) {
let langs = [].concat(this.state.langs);
if(langs.indexOf(lang) != -1) {
langs = langs.filter(item => item != lang);
}
else langs.push(lang);
this.setState({langs: langs});
this.context.router.push({
pathname: '/',
query: { langs: langs }
});
}
render() {
return (
<div>
<Link onClick={this.handleLang} lang={"fr "} />
<Link onClick={this.handleLang} lang={"en "} />
<Link onClick={this.handleLang} lang={"hi "} />
<div>
<hr/>
<br/>
active languague :
<div>{this.props.location.query.langs ? [].concat(this.props.location.query.langs).join(',') : ''}</div>
</div>
</div>
)
}
};
App.contextTypes = {
router: React.PropTypes.func.isRequired,
};
ReactDOM.render(<Router history={ReactRouter.hashHistory}>
<Route path="/" component={App} />
</Router>, document.getElementById('test'));
.lang {
display: inline-block;
background: #d3d3d3;
padding: 10px;
margin: 0 3px;
cursor: pointer;
width : 50px;
border : 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router/3.0.0/ReactRouter.min.js"></script>
<div id="test"></div>

You can access the location prop passed by react-router to the component. You can see that it has a query property. In that way you can simply access this.props.location.query.lang and you'll get the value that's in your query string.
You can check this example in react-router project.

Related

Delay in loading the array crushs the app

Yo-yo everyone,
along my path of practicing the art of React, I noticed a bug that I couldn't seem to find a good source to help me understand what causes the problem.
My array in a child component takes too long to load, resulting in an error.
The data is fetched from "jsonplaceholder," users list.
Data is set as a state.
Sent to "UserProfilePage".
Sent to "UserProfileComponent".
Trying to reach the URL "/user/1" will not succeed since the object is undefined.
*) Commenting the "UserProfileComponent," and then uncomment without refreshing will successfully load the page.
*) Coping (not fetching) the data to the App.js, assigning it to the state, will not crush the system.
APP.js
import { Component } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import "./App.css";
import Navigation from "./components/header/Navigation";
import PostsLog from "./components/Posts/PostsLog";
import UserProfileCollection from "./pages/UserProfileCollection";
import UserProfilePage from "./pages/UserProfilePage";
const POST_ENDPOINT = "https://jsonplaceholder.typicode.com/posts";
const USER_ENDPOINT = "https://jsonplaceholder.typicode.com/users";
class App extends Component {
constructor() {
super();
this.state = {
exUsersArray: [],
exPostsArray: [],
};
}
async componentDidMount() {
try {
const responseUser = await fetch(USER_ENDPOINT);
const responsePost = await fetch(POST_ENDPOINT);
const dataResponseUser = await responseUser.json();
const dataResponsePost = await responsePost.json();
this.setState({ exUsersArray: dataResponseUser });
this.setState({ exPostsArray: dataResponsePost });
} catch (error) {
console.log(error);
}
}
render() {
const { exUsersArray, exPostsArray } = this.state;
console.log(exUsersArray);
return (
<div className="app">
<Navigation />
<main>
<Switch>
{/* REROUTES */}
<Route path="/" exact>
<Redirect to="/feed" />
</Route>
<Route path="/users" exact>
<Redirect to="/user" />
</Route>
{/* REAL ROUTES */}
<Route path="/feed">
<PostsLog usersInfo={exUsersArray} usersPosts={exPostsArray} />
</Route>
<Route path="/user" exact>
<UserProfileCollection usersInfo={exUsersArray} />
</Route>
{/* DYNAMIC ROUTES */}
<Route path="/user/:userId">
<UserProfilePage usersInfo={exUsersArray} />
</Route>
</Switch>
</main>
</div>
);
}
}
export default App;
UserProfilePage.js
import { useParams } from "react-router-dom"
import UserProfileComponent from "../components/UserProfileComponent";
const UserProfilePage = ({usersInfo}) => {
const params = useParams();
const foundUser = usersInfo.find((user) => Number(user.id) === Number(params.userId))
console.log("found user ", foundUser);
// console.log(usersInfo);
console.log(params, " is params");
return(
<div>
<UserProfileComponent userProfile={foundUser}/>
<p>Yo YO</p>
</div>
)
}
export default UserProfilePage;
UserProfileComponent
const UserProfileComponent = ({userProfile}) => {
console.log(userProfile)
return (
<div className="text-group">
<div className="wrap-post">
<p>
<strong>Info</strong>
</p>
<img
src={`https://robohash.org/${userProfile.Id}.png`}
id="small-profile"
alt="user profile in circle"
/>
<p><u><strong>ID</strong></u> : {userProfile.id}</p>
<p>Name: {userProfile.name}</p>
<p>#{userProfile.username}</p>
<p>Email: {userProfile.email}</p>
<p>
{userProfile.address.street} {userProfile.address.suite}<br/>
{userProfile.address.zipcode} {userProfile.address.city}
</p>
<p>Global position</p>
<p>{userProfile.address.geo.lat}, {userProfile.address.geo.lang}</p>
<p>{userProfile.phone}</p>
<p>{userProfile.website}</p>
<p>Company</p>
<p>{userProfile.company.name}</p>
<p>{userProfile.company.catchPhrase}</p>
<p>{userProfile.company.bs}</p>
</div>
</div>
);
};
export default UserProfileComponent;
Complete repository here.
I will be happy to any tips to help me understand what happened here.
Appreciation will be given to any tip that will help me be a better programmer.
Best wishes y'all.
it seems like usersInfo hasn't loaded a quick way to fix it is to just add this to the users component.
UserProfilePage.js
import { useParams } from "react-router-dom"
import UserProfileComponent from "../components/UserProfileComponent";
const UserProfilePage = ({usersInfo}) => {
const params = useParams();
if(!usersInfo) {
return <p>Loading...</p>
}
const foundUser = usersInfo.find((user) => Number(user.id) === Number(params.userId))
console.log("found user ", foundUser);
// console.log(usersInfo);
console.log(params, " is params");
return(
<div>
<UserProfileComponent userProfile={foundUser}/>
<p>Yo YO</p>
</div>
)
}
export default UserProfilePage;
UserProfileComponent.js
const UserProfileComponent = ({userProfile}) => {
if(!userProfile) {
return <p>Loading...</p>
}
console.log(userProfile)
return (
<div className="text-group">
<div className="wrap-post">
<p>
I see that you're rendering your compoonent without doing any null check in UserProfileComponent. Actually to be a better programmer or doing better work, you have to control every null case in order not to crash your app.
<p><u><strong>ID</strong></u> : {userProfile.id}</p>
<p>Name: {userProfile.name}</p>
<p>#{userProfile.username}</p>
<p>Email: {userProfile.email}</p>
<p>
{userProfile.address.street} {userProfile.address.suite}<br/>
{userProfile.address.zipcode} {userProfile.address.city}
</p>
<p>Global position</p>
<p>{userProfile.address.geo.lat}, {userProfile.address.geo.lang}</p>
<p>{userProfile.phone}</p>
<p>{userProfile.website}</p>
<p>Company</p>
<p>{userProfile.company.name}</p>
<p>{userProfile.company.catchPhrase}</p>
<p>{userProfile.company.bs}</p>
You'll see that there's no null check. It would be better if you have some null check on your userProfile
Also, my suggestion is, you can create a loading in your state.
Before sending your request, you can set the loading to true.
And when your loading is true, you can show some spinner or sth like that. When your request finishes, you can set the loading variable to false and you can show your data.
The main point is, always use a loading variable to check the loading state instead of checking the null | undefined state of your data.

Can't navigate to URL param using nested routes

I built a small example of nested routes using the useRoutes hook. I don't understand what I am doing different than the examples.
Here's a codesandbox.
I am unable to navigate to the :customerId URL param while in the 'customers' route.
import React from "react";
import { BrowserRouter, Link, RouteObject, useRoutes } from "react-router-dom";
const contentRoutes: RouteObject[] = [
{
element: <div>Home Page</div>,
index: true
},
{
element: (
<div>
Customers <Link to="microsoft">Microsoft</Link> (this is the link that doesn't work)
</div>
),
path: "customers",
children: [
{
path: ":customerId",
element: <div>Customer Microsoft</div>
}
]
}
];
const Content: React.FC = () => {
const content = useRoutes(contentRoutes);
return <div style={{ display: "flex" }}>{content}</div>;
};
export default function App() {
return (
<BrowserRouter>
<div className="App">
<div style={{ display: "flex", gap: "10px", marginBottom: '20px' }}>
<Link to="/">Home</Link>
<Link to="customers">Customers</Link>
</div>
<Content />
</div>
</BrowserRouter>
);
}
Looks like I haven't completely understood the concept of Outlet.
In the examples showing the Routes and Route React components, they use the Outlet component to display children routes.
In my example, I should just change to:
<div>
Customers <Link to="microsoft">Microsoft</Link>
<Outlet /> // HERE, use Outlet to paint children routes.
</div>

How can i pass a component to another component via props

I have this scenario where my web application would have a header with 3 options. Depending on the options selected, the headers will be re-rendered with new options. Since I am new to React, my immediate idea of the code structure would be to have a empty main Header.js file which would render another component which is unique to the page's option. However, my googling didn't return any searches that would help me understand how to pass components to another via react-router v4.
An example:
Header: Steak | Pasta | Burgers
If the user selects Steak, the same header would now display the following:
Header: Black-Pepper | Mushroom | Chilli
The contents of the header is supposed to change according to what the user selected previously
Thank you and I hope I do not get mark down because I really have no idea how else to ask this question.
I prepare you a basic idea for you, with react-router-dom:
const {Router, Route, IndexRoute, Link} = ReactRouter;
// A main React component using this.props.children will pull in all the children Routes in the router function at the bottom.
const App = React.createClass({
render: function() {
return(
<div>
{this.props.children}
</div>
);
}
});
const Home = React.createClass({
render: function() {
return(
<div>
<ul>
<li><Link to="link-steak">Steak</Link></li>
<li><Link to="link-pasta">Pasta</Link></li>
<li><Link to="link-burgers">Burgers</Link></li>
</ul>
</div>
);
}
});
const LinkOne = React.createClass({
render: function() {
return(
<div>
<ul><li>steak 1</li><li>steak 2</li><li>steak 3</li></ul>
<Link to="/">back</Link>
</div>
);
}
});
const LinkTwo = React.createClass({
render: function() {
return(
<div>
<ul><li>pasta 1</li><li>pasta 2</li><li>pasta 3</li></ul>
<Link to="/">back</Link>
</div>
);
}
});
const LinkThree = React.createClass({
render: function() {
return(
<div>
<ul><li>burger 1</li><li>burger 2</li><li>burger 3</li></ul>
<Link to="/">back</Link>
</div>
);
}
});
ReactDOM.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="link-steak" component={LinkOne} />
<Route path="link-pasta" component={LinkTwo} />
<Route path="link-burgers" component={LinkThree} />
</Route>
</Router>
), document.getElementById('app'));
https://codepen.io/ene_salinas/pen/KGbEoW?editors=0010
There are a few ways to go about this but without knowing your project in depth here's a general approach you could use:
(You can create functional components and use them or just write all the JSX in the Header component.)
Header component extends React.Component {
state= {
selected: "burger"
}
// Create a method for each: "burger", "steak" etc...
this.setBurgerMenu = () => {
this.setState({selected: "burger"}, () => {})
}
render() {
return (
<div>
<button onClick={this.setBurgerMenu}>Burger</button>
<button onClick={this.setSteakMenu}>Steak</buttton>
<button onClick={this.setChiliMenu}>Chili</button>
<div>
{this.state.selected === "burger" && <BurgerMenu />}
{this.state.selected === "steak" && <SteakMenu />}
{this.state.selected === "chili" && <ChiliMenu />}
<div>
</div>
)
}
}
If you're making a nav bar and these menu items simply point to a link then the easiest solution would be to use a library with menus and sub menus.

Active NavLink to parent element

I'm using React Router v4 and I have a case where on my navigation links, I want to enable the active className to the NavLink parent element, not the NavLink itself.
Is there a way to access the path (match) even though I'm not inside the Switch element?
Or do I have to keep state? Because I'm feeling it's kinda missing the idea of router.
Here's my example, I want to apply the active className to li element not NavLink:
const {
HashRouter,
Switch,
Route,
Link,
NavLink,
} = ReactRouterDOM
const About = () => (
<article>
My name is Moshe and I'm learning React and React Router v4.
</article>
);
const Page = () => (
<Switch>
<Route exact path='/' render={() => <h1>Welcome!</h1>} />
<Route path='/about' component={About}/>
</Switch>
);
const Nav = () => (
<nav>
<ul>
<li><NavLink exact to="/">Home</NavLink></li>
<li><NavLink to="/about">About</NavLink></li>
</ul>
</nav>
);
class App extends React.Component {
render() {
return (
<div>
<Nav />
<Page />
</div>
);
}
}
ReactDOM.render((
<HashRouter>
<App />
</HashRouter>),
document.querySelector("#app"));
https://codepen.io/moshem/pen/ypzmQX
It doesn't seem like it is very easy to achieve. I used withRouter HOC described in react router docs. It gives access to { match, location, history } from props inside components located outside of Routess. In the example I wrapped Nav component to get location and its pathname. Here is the example code:
class Nav extends React.Component {
getNavLinkClass = (path) => {
return this.props.location.pathname === path ? 'active' : '';
}
render() {
return (
<nav>
<ul>
<li className={this.getNavLinkClass("/")}><NavLink exact to="/">Home</NavLink></li>
<li className={this.getNavLinkClass("/about")}><NavLink to="/about">About</NavLink></li>
</ul>
</nav>
)};
}
Nav = withRouter(Nav);
You will probably have to take care of params in your routes (if you have any), to match properly. But you still have to match for each path you have in your NavLink, which might not be pretty code. But the idea is that when the route is changed, Nav is rerendered and correct li is highlighted.
Here is a working example on codesandbox.
Can be achived with Route component
<ul>
<Route path="/about">
{({ match }) => <li className={match ? 'active' : undefined}><Link to="/about">About</Link></li>
</Route>
</ul>
Reference: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Route.md#children-func
If you abandon the NavLink components altogether, you can create your own components that emulate the "activeness" of a NavLink by using useHistory() and useLocation() from react-router-dom.
Dashboard.js
const routeItems = [
{ route: '/route1', text: 'Route 1' },
{ route: '/route2', text: 'Route 2' },
];
<Router>
<NavBar routeItems={routeItems} />
</Router>
In NavBar.js, we just need to check to see if the current active route is the same as the route for any individual item on the
NavBar.js
import { useHistory, useLocation } from 'react-router-dom';
const NavBar = (props) => {
const { routeItems } = props;
const history = useHistory();
const location = useLocation();
const navItems = routeItems.map((navItem) => {
return (
<div style={{
backgroundColor: navItem.route === location.pathname ? '#ADD8E6' : '',
}}
onClick={() => {
history.push(navItem.route);
}}
>
{navItem.text}
</div>
);
});
return (navItems);
};
export default NavBar;
I found simpler solution for my case I have nested items but I know the base of each nest
for example the base of nest is /customer it contains items like so
/customer/list , /customer/roles ...
So did put some logic in isActive prop in the parent NavLink
code with explanation down :
<NavLink
to={item.route}
activeClassName={classes.activeItem}
onClick={e => handleItemClick(e, key)}
isActive={(match, location) => {
// remove last part of path ( admin/customer/list becomes admin/customer for example )
const pathWithoutLastPart = location.pathname.slice(0, location.pathname.lastIndexOf("/"));
// if current parent is matched and doesn't contain childs activate it
if (item.items.length === 0 && match) {
return true;
}
// if sliced path matches parent path
in case of customer item it becomes true ( admin/customer === admin/customer )
else if (pathWithoutLastPart === item.route) {
return true;
}
// else inactive item
else {
return false;
}
}}
>
...
</NavLink>
Now parent active with his child

How to achieve Dynamic routing in React Router 4?

I have a list of articles like this:
<div>
{
this.props.articles.map(article => {
return (
<ArticleCard key={article._id} article={article} />
)
})
}
</div>
In the ArticleCard component, I'm only showing the title of my article. I want to put a link to it which would create new URL like 'article-title' and show the content.
How to achieve this?
In your ArticleCard, you have to create a Link that will route to your full Article. This link will include the id of the article you are trying to render (ex. articles/${article._id})
By writing the Route path of the component Article as articles/:id, this will allow us to catch that id when Article is rendered (accessible via this.props.match.params.id)
Then, assuming that id is used to fetch the article from some other API, a good place to call that would be the componentDidMount of your Article component.
Here is a small example which may help you out:
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
Switch
} from 'react-router-dom'
const ParamsExample = () => (
<Router>
<Switch>
<Route exact path="/" component={ArticleList} />
<Route path="/articles/:id" component={Article} />
</Switch>
</Router>
)
const article = {
_id: 1,
title: 'First Article'
};
const ArticleList = () => (
<div>
<ArticleCard key={article._id} article={article} />
</div>
);
const ArticleCard = ({ article }) => (
<div>
<h2>{article.title}</h2>
<Link to={`/articles/${article._id}`}>SEE MORE</Link>
</div>
);
class Article extends React.Component {
componentDidMount() {
console.log('Fetch API here: ', this.props.match.params.id);
}
render() {
return (
<div>
{`Fetching...${this.props.match.params.id}`}
</div>
);
}
}
export default ParamsExample

Resources