How to dynamically create a React component based on url param? - reactjs

I'd like my React component to generate based on the url param, in this case, the :id param. I'm struggling rendering the component. I have the different Components defined in separate files (ex. Loader.js, Radio button.js, Accordion menu.js).
Here's my (reduced for clarity) code that is continuously failing :)
import React from 'react';
import { Switch, Link, Route } from 'react-router-dom';
import Grid from '../Components/Grid'
function Overview () {
const components = [
{id: 'accordion-menu',
name: 'Accordion menu'},
{id: 'radio-button',
name: 'Radio button'},
{id: 'loader',
name: 'Loader'},
]
const componentPage = ({match}) => {
const findId = components.find((el) => {
match.params.id = el.id;
return findId.name;
}
)}
return (
<Router>
<div className="components">
<h3>Components</h3>
<p>This header and the menu will always appear on this page!</p>
<menu>
{components.map(({id, name}) => (
<li>
<Link to={`/components/${id}`}>{name}</Link>
</li>
))}
</menu>
<Switch>
<Route exact path={'/components/'} component={Grid}/>
<Route path={'/components/:id'} component={componentPage}/>
</Switch>
</div>
</Router>
)
}
export default Overview;

const componentPage = ({match}) => {
const findId = components.find((el) => {
match.params.id = el.id;
return findId.name;
}
)}
I think what you mean here is to return el.name not findId.name.
the other thing is you are trying to display the function as a component which won't work.

Related

rendering a component inside a react page using Menu

I have a menu and the main body of the page. I have created different pages as components with some text. All I want is that when I click on the side bar menu, the components are displayed in the main page. How can I make this work?
const items2 = [{
label: 'Rice',
key: 'rice',
},
{
label: 'AB Test',
key: 'ab',
}]
const MainLayout = () => {
const {
token: { colorBgContainer },
} = theme.useToken();
const navigate = useNavigate();
const onClick = (e)=>{navigate(`/${e.key}`)}
return (
<Layout>
<Sider >
<Menu
mode="inline"
items={items2}
onClick = {onClick}
/>
</Sider>
<Content >
//I Want to open the pages here
</Content>
</Layout>
</Content>
To render a component inside other component, React provides a special props name children.
To achieve your requirement, you can do like this:
MainLayout.js:
export const MainLayout = ({children}) => {
const {
token: { colorBgContainer },
} = theme.useToken();
const navigate = useNavigate();
const onClick = (e)=>{navigate(`/${e.key}`)}
return (
<Layout>
<Sider>
<Menu
mode="inline"
items={items2}
onClick={onClick}
/>
</Sider>
<Content>
{children}
</Content>
</Layout>
)
}
In MainLayout.js, you only need to write {children} inside component <Content>, React will do the rest for you to pass the content of Rice or AB or whatever for you. In each page, just place <MainLayout> at the top of the outer of rest of your code.
Please see 2 example files below.
Rice.js:
import MainLayout from './MainLayout';
export const Rice = () => {
// Do some stuffs here...
return (
<MainLayout>
<div>
<h2>Best rated rice in the World</h2>
<ol>
<li>Pinipig</li>
<li>Riz de Camargue</li>
...
</ol>
<div>
</MainLayout>
)
}
Corn.js:
import MainLayout from './MainLayout';
export const Corn = () => {
// Do some stuffs here...
return (
<MainLayout>
<div>
<h2>Best Corn Side Dish Recipes</h2>
<ol>
<li>Whipped-Cream Corn Salad</li>
<li>Classic Grilled Corn on the Cob</li>
...
</ol>
<div>
</MainLayout>
)
}
You can read more and play around with the example code from React's official documents.
It is the basic concept of React, so before you start to build something big, I suggest to follow this docs first or find some series of React tutorials for beginner, they will explain key concepts of React so you would not save more time.
You need to use react-router-dom to navigate when you click other MenuItem. Create your own RouterProvider and put it in the Content.
<Content>
<RouterProvider router={router}>
</Content>
EDIT
Now you have two way to navigate to your Component. First is using Link and set it to your label.
{
label: <Link title="Rice" to="/rice">Rice</Link>,
key: 'rice',
}
Second way is hook useNavigate
const navigate = useNavigate();
const onClick = (e)=>{navigate(`/${e.key}`)}
//Add to Menu
<Menu
onClick={onClick}
//other props
/>

How to map a filter of a string value in an array of strings

I have an app built with React and Express Node, and in it I have 3 separate components. The first component is a gallery, where user selects an image to create a post with an background image. When button is clicked, user is taken to a form. The user will edit the inputs with some text and save the form which has a axios.post request to send the data to mongo db through express route. After saving user clicks view post that takes them to another component with axios.get request displaying image and input data to the user.
I have routes that have a unique http path to show the component that is active. My question is how can I map the routes to dynamically load the name of the image that comes from mongodb collection, instead of manually writing in the paths image name ie: path={"/getinputwaterfall/:id"}, path={"/getinputcross/:id"}, path={"/getinputfreedom/:id"} . I would like to have instead somthing like: path={"/getinput{urlName}/:id"}.
In the mongoDB collection I have a URL and name string array. The URL string is an http path from firebase and the name string are images names.
Is this possible to do?
Bellow is the code and my attempts to do this.
import React, {useState, useEffect} from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import axios from "axios";
import "bootstrap/dist/css/bootstrap.min.css";
import "./index.css";
//gallery imports
import Cross from "./Components/Gallery/posts/Cross";
import Waterfall from "./Components/Gallery/posts/Waterfall";
import Freedom from "./Components/Gallery/posts/Freedom";
import CrossPost from "./Components/Gallery/get/CrossPost";
import WaterfallPost from "./Components/Gallery/get/WaterfallPost";
import FreedomPost from "./Components/Gallery/get/FreedomPost";
function App() {
const [name, setName] = useState([]);
const loadImage = async () => {
try {
let res = await axios.get("http://localhost:5000/geturls");
console.log(res.data)
setName(res.data.map(n=>n.name)); //array of names
} catch (error) {
console.log(error);
}
};
useEffect(() => {
loadImage();
}
,[]);
return (
<div className="App">
<Router>
<Switch>
{/* routes for gallery */}
<Route path={"/waterfall"} component={Waterfall} />
<Route path={"/cross"} component={Cross} />
<Route path={"/freedom"} component={Freedom} />
<Route path={"/getinputwaterfall/:id"} component={WaterfallPost} />
<Route path={"/getinputcross/:id"} component={CrossPost} />
<Route path={"/getinputfreedom/:id"} component={FreedomPost} />
{/* what I tryed to map */}
{name.filter(name => name === `${name}` ).map((urlName) => (
<Route exact path={`/getinputt/${urlName}`} component={CrossPost} />
))}
</Switch>
</Router>
</div>
);
}
export default App;
update: I applied the first option of the answer to my code for those who wish to see the complete solution: Note: I had to remove the '/' in /getinput/${name}/:id to make my code work! Thanks Drew!
const imagePostRoutes = [
{ name: "cross", component: CrossPost },
{ name: "freedom", component: FreedomPost },
{ name: "waterfall", component: WaterfallPost },
];
return (
<div className="App">
<Router>
<Switch>
{imagePostRoutes.map(({ component, name }) => (
<Route
key={name} path={`/getinput${name}/:id`} component={component}
/>
))}
</Switch>
</Router>
</div>
);
}
I was first suggesting to create a "routes" config array that can be mapped.
const imagePostRoutes = [
{ name: "cross", component: CrossPost },
{ name: "freedom", component: FreedomPost },
{ name: "waterfall", component: WaterfallPost },
];
...
{imagePostRoutes.map(({ component, name }) => (
<Route key={name} path={`/getInput/${name}/:id`} component={component} />
))}
The second suggestion is to use a single generic dynamic route where a match parameter could specify the post type and a general post component to render the specific image post component. This is a very stripped down minimal version.
Define the route.
<Route path="/getInput/:imagePostType/:id" component={ImagePost} />
Create a Map of match param to component to render.
const postComponents = {
cross: CrossPost,
freedom: FreedomPost,
waterfall: WaterfallPost,
};
Create a component to read the match params and load the correct post component from the Map.
const ImagePost = () => {
const { id, imagePostType } = useParams();
const Component = postComponents[imagePostType];
if (!Component) {
return "Unsupported Image Post Type";
}
return <Component id={id} />;
}

react-router#5 route `onEnter` method not calling, scrolltoview not working

according to my requirement, when a user click on
<Link to="/products/shoe#product9">Go to projects and focus id 9</Link> I would like to show him the product. (hello page) for that I do this:
import React from "react";
import { Link, Route, Switch, Redirect } from "react-router-dom";
import "./products.scss";
const Shoes = React.lazy(() => import("./shoes/shoes.component"));
const Cloths = React.lazy(() => import("./cloths/cloths.component"));
function hashScroll() {
alert("called");
const { hash } = window.location;
if (hash !== "") {
setTimeout(() => {
const id = hash.replace("#", "");
const element = document.getElementById(id);
if (element) element.scrollIntoView();
}, 0);
}
}
export default class Products extends React.Component {
render() {
return (
<div>
<header>
<Link to="/products/shoe">Shoes</Link>
<Link to="/products/cloths">Cloths</Link>
</header>
<h1>Products page</h1>
<main>
<Switch>
<Redirect exact from="/products" to="/products/shoe" />
<Route path="/products/shoe" onEnter={hashScroll}>
<Shoes />
</Route>
<Route path="/products/cloths">
<Cloths />
</Route>
</Switch>
</main>
</div>
);
}
}
I attached an onEnter function to call and scroll, so when there is a #hash let it scroll. It's not working at all. Please navigate to Hello page, from you click the link to go to products page.
Live Demo
onEnter is no longer working in react-router
What you can do is pass a prop to the component
<Shoes onEnter={hashScroll} />
inside the Shoes component execute it on componentDidMount.
componentDidMount = () => {
if (this.props.onEnter) {
this.props.onEnter();
}
};
demo

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