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.
Related
I am new to React JS and I am currently building a simple application. I am using Route in order to navigate between components and everything work fine, but if I am on a page and I click again in the menu to navigate to the page, it doesn't refresh its content.
I just want the component to refresh its content every time I click on the item menu.
This is my sidebar class:
class Sidebar extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Router>
<Route render={({ location, history }) => (
<React.Fragment>
<SideNav
onSelect={(selected) => {
const to = '/' + selected;
if (location.pathname !== to) {
history.push(to);
}
}}>
<SideNav.Toggle />
<SideNav.Nav>
<NavItem eventKey="Cars">
<NavIcon>
Cars
</NavIcon>
</NavItem>
<NavItem eventKey="Bicycles">
<NavIcon>
Bicycles
</NavIcon>
</NavItem>
</SideNav.Nav>
</SideNav>
<main>
<Switch>
<Route exact path="/" component={props => <Home />} />
<Route
exact path="/Cars"
render={() => !isAllowed ?
<Home /> :
<Cars/>
} />
<Route
exact path="/Bicycles"
render={() => !isAllowed ?
<Home /> :
<Bicycles />
} />
</Switch>
</main>
</React.Fragment>
)}
/>
</Router>
)
}
}
This is my Cars Component class:
import React, { Component } from 'react';
class Cars extends Component {
render() {
return (
<div style={{ textAlign: 'center', marginLeft: '295px' }} >
<form>
<h1>Hello</h1>
<p>Enter your car name:</p>
<input
type="text"
/>
</form>
</div>
)
}
}
export default Cars;
For ex. if I text something in input and after that I click on the item menu, I want that input to be refreshed.
In order to "refresh" (or in React world called Re-render) the content of the component you need to change it's state, and that is how React works. As I can see you don't have any state in your component so if you can specify what you wanna "refresh" we can help you.
The heart of every React component is its “state”, an object that determines how that component renders & behaves. In other words, “state” is what allows you to create components that are dynamic and interactive.
Quick example from somewhere on the internet :
import React from 'react';
class Person extends React.Component{
constructor(props) {
super(props);
this.state = {
age:0
this.incrementAge = this.incrementAge.bind(this)
}
incrementAge(){
this.setState({
age:this.state.age + 1;
});
}
render(){
return(
<div>
<label>My age is: {this.state.age}</label>
<button onClick={this.incrementAge}>Grow me older !!<button>
</div>
);
}
}
export default Person;
The age of inside of the label is being Re-rendered (or "refresh") every time when the user clicks on it since its state is changing.
Here is an official documentation and I would recommend you read it, it will clarify a lot of issues you are facing.
https://reactjs.org/docs/state-and-lifecycle.html
I am looking for a way to use ReactDOM.render to create a Link within a react router. The setup more or less looks like this:
const router = (
<div>
<Router>
<Route path="/map" component={Map}/>
</Router>
</div>
);
The relevant parts of Map.jsx look like this:
const MapPopup = () => {
return (
<Link to={`/map/add`} />
)
}
class Map extends React.Component {
componentDidMount() {
this.map = L.map('map')
//...stuff...
this.map.on('contextmenu', event => {
popup
.setLatLng(event.latlng)
.addTo(this.map)
.setContent(
ReactDOM.render(
MapPopup(),
document.querySelector('.leaflet-popup-content')
)[0]
)
.openOn(this.map)
})
}
render() {
return (
<React.Fragment>
<div id="map" />
</React.Fragment>
)
}
}
I am basically trying to add a Link to the map popup provided by leaflet (I can't use react-leaflet for this project). If I however return the MapPopup directly in the render function it works (obviously not in the popup but the Link does work this way).
<React.Fragment>
<div id="map" />
<MapPopup />
</React.Fragment>
Does anyone have an idea how I can tackle this rather unusual problem?
I am using "react-router-dom": "4.3.1".
This is the expected error since <Link> component expects ancestor component to be of router type (<BrowserRouter>, <MemoryRouter>, <Router> ... ), refer this thread for a more details.
For your scenario to circumvent this limitation ReactDOM.createPortal could be utilized instead of ReactDOM.render:
<Route
path="/popup"
render={() => (
<Popup>
<div>
Some content goes here
<Link to="/map"> Back to map</Link>
</div>
</Popup>
)}
/>
where
class Popup extends React.Component {
render() {
return ReactDOM.createPortal(
this.props.children,
document.querySelector("#link-render-div")
);
}
}
and
Here is a demo for your reference
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
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
I have the following router config:
ReactDOM.render((
<Router history={History.createHistory()}>
<Route path="/" component={Page}>
<IndexRoute component={OverviewDashboard}/>
<Route path="/:env" component={Env}>
<IndexRoute component={EnvOverview}/>
</Route>
</Route>
</Router>
), document.getElementById('page'));
and the following component definitions:
const Page = React.createClass({
render: function () {
return (
<main>
<Header />
{this.props.children}
</main>
);
}
});
const OverviewDashboard = React.createClass({
render: function () {
return (
<section>
<Env name="Env1" />
<Env name="Env2" />
</section>
);
}
});
const Env = React.createClass({
render: function() {
return (
<section className="env">
<header>{this.props.params && this.props.params.env || this.props.name}</header>
{this.props.children}
</section>
);
}
});
const EnvOverview = React.createClass({
render: function() {
return (
<div>
<Jobs env={this.props.params.env}/>
<Runtime env={this.props.params.env}/>
</div>
)
}
});
I've ommitted the definitions of <Header>, <Jobs> and <Runtime> because they are not relevant.
When i go to / link {this.props.children} within <Env> component is undefined and thus not rendered.
However when I go to "/Env1" {this.props.children} is set to <EnvOverview> correctly and displayed.
I am new to React and probably I am using the routing component incorrectly. Could someone explain how do I render <EnvOverivew> also when accessing / link ?
When you go to /Env, react-router renders your /:env route, whose component is Env. Inside this route, you have an IndexRoute. This IndexRoute renders the EnvOverview component as its parent route children. Thus, you get {this.props.children} on this case.
When you enter the / route, it triggers your IndexRoute inside App, whose component is OverviewDashboard. It doesn't enter the /:env route because you didn't provide a parameter to match :env.
Inside your OverviewDashboard, you are manually rendering two Envs, without providing their children (check this). Thus, inside these two Envs, {this.props.children} equals undefined.
I don't know what your requirements are, but you could change the OverviewDashboard render method to this:
render: function () {
return (
<section>
<Env name="Env1"><EnvOverview name="Env1" /></Env>
<Env name="Env2"><EnvOverview name="Env2" /></Env>
</section>
);
}
And inside EndOverview render method, change env={this.props.params.env} to env={this.props.params.env || this.props.name}.
I don't know if it's the best solution to your case, but I hope why {this.props.children} was undefined is clarified ;)