How to use dynamic routing to pass information / react - javascript

The ButtonList returns a list of filtered Buttons that should each link to a new page. I would like to do it by adding dynamic routes, but I don't know how to write the code... How do I use history.push correctly? Any help would be highly appreciated.
export default function Home() {
const [searchValue, setSearchValue] = useState("");
const history = useHistory();
const filteredData = data.filter(item =>
item.name.toLowerCase().includes(searchValue.toLowerCase())
);
function handleSearch(value) {
setSearchValue(value);
}
const selectedItem = filteredData.find(item => item.name);
function handleSelect(item) {
history.push(`/home/${item.name}`);
}
return (
<WrapperDiv>
<StyledHeader />
<Switch>
      
<Route exact path="/home">
      
<Searchbar onSearch={handleSearch} />
<ButtonList data={filteredData} onClick={handleSelect} /> 
</Route>
<Route exact path="/home/{...}">
<StyledDiv>
<Button item={selectedItem} light />
<AccordionList />
</StyledDiv>
</Route>
</Switch>
</WrapperDiv>
);
}
export default function Button({ children, handleSelect }) {
return (
<button
to={{
pathname: "/home/" + data.name,
data: data
}}
onClick={handleSelect}
>
{children}
</button>
);
}
export const data = [
{
name: "Apple",
id: 1
},
{
name: "Banana",
id: 2
},
{
name: "Blueberry",
id: 3
}
];

one good way to achieve it would be to use React Router which makes creating dynamic routes really easy.
Writing the whole code is a little difficult here, therefore I found a good example of an easy way to implement react-router with your code.
Example
import { useHistory } from "react-router-dom";
function MyButton() {
let history = useHistory();
function triggerClick() {
history.push("/somewhere");
}
return (
<button type="button" onClick={triggerClick}>
Go somewhere
</button>
);
}

Related

load routes after getting roles from an api

I created a react app, I added role based mechanism, the idea is that after the athentication, directly I send a request to an api to get the roles of the user, because the token contains only the username and I can not add any information on the payload.
so while getting thr roles from the api, I added a loder component that will block the user from using the app until the roles are loaded, at this point everything is working well, but when I reloaded the page, the app redirect me to the default route everytime, because the routes are not created yet, I would like to know how to block the app until the routes also are loaded? :
App.tsx :
const App: React.FC = () => {
const useRoles = useRoleBased(); // hook to get roles
return (
<>
{useRoles?.loading(
<Loader open backgroundColor="#ffffff" circleColor="primary" />
)}
<Box className={`mainSideBar ${openSideBar && 'openSideBar'}`}>
<Router />
</Box>
</>
);
};
export default App;
Router.tsx :
const routes = [
{ path: '/logout', element: <ConfirmLogout /> },
{
path: '/dashboard-page',
element: <DashboardPage />,
allowedRoles: [Roles.Director, Roles.RequestFullAccess],
},
{
path: '/profil',
element: <RequestPage />,
allowedRoles: [Roles.Manager],
},
];
const Router: React.FC = () => {
return <RolesAuthRoute routes={routes}></RolesAuthRoute>;
};
export default Router;
RolesAuthRoute.tsx :
export function RolesAuthRoute({ routes }: { routes: IRoutes[] }) {
const userRoles = useSelector((state: any) => state?.roles?.roles);
const isAllowed = (
allowedRoles: Roles[] | undefined,
userRoles: string[]) =>
process.env.REACT_APP_ACTIVATE_ROLE_BASED_AUTH === 'false' ||
!allowedRoles ||
allowedRoles?.some((allowedRole) => userRoles?.includes(allowedRole)
);
return (
<Routes>
{routes.map((route) => {
if (isAllowed(route?.allowedRoles, userRoles))
return (
<Route
path={route?.path}
element={route?.element}
key={route?.path}
/>
);
else return null;
})}
<Route path="*" element={<Navigate to="/" replace />} /> //this route is created in all cases
</Routes>
);
}
You could return early (conditional rendering) to stop the router from rendering prematurely. You'll need to modify the hook to return the loading state as boolean instead of rendering the component as it seems to be currently implemented.
const App: React.FC = () => {
const useRoles = useRoleBased(); // hook to get roles
if(useRoles.isLoading){
return <Loader open backgroundColor="#ffffff" circleColor="primary" />
};
return (
<>
<Box className={`mainSideBar ${openSideBar && 'openSideBar'}`}>
<Router />
</Box>
</>
);
};
export default App;

Dynamic Routes for my list of projects in React

I am new to React, I have JSON data with projects I want to dynamically link. I have created a component called "ProjectSingle" to display each one, however I can't seem to get the data to display.
const App = () => {
const [projects, setProjects] = useState([]);
const getProjects = () => {
fetch('http://localhost:8000/projects')
.then((res) =>{
return res.json();
})
.then((data) => {
setProjects(data);
})
}
useEffect(() => {
getProjects();
AOS.init({disable: 'mobile'});
},[]);
return (
<Router>
<div className="App">
<Switch>
{projects.map( project =>(
<Route exact path='/project/:slug' render={props => (
<ProjectSingle {...props} project={project}/>
)}/>
))}
</Switch>
<Footer/>
</div>
</Router>
);
}
export default App;
The requested JSON is below, I'm using "slug" to generate the slug.
{
"projects": [
{
"title": "Project One",
"image": "/img/wp-logo.png",
"slug": "project-one",
"key": 1
},
{
"title": "Project Two",
"image": "/img/wp-logo.png",
"slug": "project-two",
"key": 2
},
}
Although I'm not sure what data to put in my single component to get the data of the project iteration
const ProjectSingle = ({project}) => {
const { slug } = useParams();
return (
<>
<h1>{project.title}</h1>
</>
)
}
export default ProjectSingle;
Firstly, you only need one <Route path="/project/:slug" /> ... </Route> assigned. The :slug param can be used to dynamically determine what the content of this route will be.
<Route
exact
path="/project/:slug"
render={(props) => <ProjectSingle {...props} projects={projects} />}
/>
const ProjectSingle = ({projects}) => {
const { slug } = useParams();
const project = projects.find(p => p.slug === slug)
return (
<>
<h1>{project.title}</h1>
</>
)
}
export default ProjectSingle;
The route's child (ProjectSingle) will then useParams() to retrieve the value associated with :slug and use that to find the correct project from projects to render.
There may be an even more clever way of using the render() of the route and determining the desired slug & project at that stage - making the ProjectSingle a pure component without any knowledge of routing.
There are several important steps to achive this.
Include libraries like react-router, react-router-dom
Create a history
Render the Route with history
4.Define Navigation to the routes
Here is a sample as per your requirement:
Note: As per your design the routes will render in the same page. I have updated it to be in single file here. complete working example in codesandbox
import React, { useState } from "react";
import { Router, Switch, Route, useParams } from "react-router";
import { Link } from "react-router-dom";
import createHistory from "history/createBrowserHistory";
import "./styles.css";
const history = createHistory();
const Footer = () => {
return <div>Footer here.</div>;
};
const ProjectSingle = ({ project }) => {
const { slug } = useParams();
//TODO useEffect to get project details
return (
<>
<h1>Details of {slug} </h1>
<div>{JSON.stringify(project)}</div>
</>
);
};
export default function App() {
const [projects, setProjects] = useState([
{
title: "Project One",
image: "/img/wp-logo.png",
slug: "project-one",
key: 1
},
{
title: "Project Two",
image: "/img/wp-logo.png",
slug: "project-two",
key: 2
}
]);
/*const getProjects = () => {
fetch('http://localhost:8000/projects')
.then((res) =>{
return res.json();
})
.then((data) => {
setProjects(data);
})
}
useEffect(() => {
getProjects();
AOS.init({disable: 'mobile'});
},[]);
*/
return (
<Router history={history}>
<div className="App">
{projects.map((project) => {
return (
<div key={project.key}>
<Link to={`/project/${project.slug}`}> {project.title} </Link>
</div>
);
})}
<Switch>
{projects.map((project) => (
<Route
key={project.key}
exact
path="/project/:slug"
render={(props) => <ProjectSingle {...props} project={project} />}
/>
))}
</Switch>
<Footer />
</div>
</Router>
);
}

How to pass location state and URL params using React-Router?

When I click on the link in the HoverBooks Component to get to a new page where I can render the book location state in Book component, but when I press on it nothing happens. I think the error is in Route:
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route path="/book:/book.Key">
<Book />
</Route>
<Route path="/signin">
<Signin />
</Route>
<Route path="/">
<Header />
<Home />
</Route>
</Switch>
</Router>
</div>
)
}
export default App
import React from 'react'
import { useLocation } from 'react-router-dom'
const Book = () => {
const {
state: { book },
} = useLocation()
console.log({ book })
return (
<div key={book.key}>
<h1>{book.bookName}</h1>
</div>
)
}
export default Book
const HoverBooks = ({ ...book }) => {
const [inHoverBooks, setInHoverBooks] = React.useState(false)
return (
<>
<Link
to={{
pathName: `/book/${book.key}`,
state: {
book,
},
}}
>
<img
onMouseLeave={() => setInHoverBooks(false)}
onMouseEnter={() => setInHoverBooks(true)}
src={book.image}
key={book.key}
/>
</Link>
{inHoverBooks && (
<div className="hover__containter">
<h3>{book.bookName}</h3>
<h2>{book.by}</h2>
<h2>{book.Narreted}</h2>
<h2>{book.length}</h2>
<h2>{book.rating}</h2>
</div>
)}
</>
)
}
export default HoverBooks
Below is the correct form, e.g. /:someName, to define a route with URL params:
<Route path="/book/:bookKey">
<Book />
</Route>
And here is the right syntax to make a Link for the above route:
<Link
to={{
pathname: `/book/SOME_BOOK_KEY`, // replace SOME_BOOK_KEY with some value
state: {
book, // e.g. const book = { key: 'js', bookName: 'Learn JavaScript'}
},
}}
>
<img src="some_src" alt="something" />
</Link>
And you useParams and useLocation react-hooks to access the "URL params" and "location state" in a component:
const Book = () => {
const {
state: { book },
} = useLocation()
const { bookKey } = useParams();
console.log(book, bookKey)
// prints "book" object (from location state) and "bookKey" (from URL params)
return (
<div key={book.key}>
<h1>{book.bookName}</h1>
</div>
)
}
I would suggest you to add typescript to your ReactJS app. It helps you find errors early by doing "static Type-checking".
With react router you need to pass the component you want to render to the Route like this
const ComponentA = (props) => {...}
<Route path="/component-a" component={ComponentA} />
And here is how to link to component a
<Link to="/component-a" >Go to component A</Link>

How to pass props to route components when using useRoutes?

I'm trying out the new useRoutes hook from react-router-dom and it seems to be pretty interesting. The only problem, is that I can't figure out how I would pass props down to the components.
Before, I'd have a Route component, and I would select parts of local or global state there and pass it on, but how would I do it with useRoutes?
In the below sandbox, I'm trying to change the background based on the boolean value of isLoading. How would I pass isLoading on?
Edit
Here's the code:
import React from "react";
import { BrowserRouter as Router, Outlet, useRoutes } from "react-router-dom";
const Main = ({ isLoading }) => (
<div
style={{
height: "40vh",
width: "50vw",
backgroundColor: isLoading ? "red" : "pink"
}}
>
<Outlet />
</div>
);
const routes = [
{
path: "/",
element: <Main />
}
];
const App = ({ isLoading }) => {
const routing = useRoutes(routes);
return (
<>
{routing}
{JSON.stringify(isLoading)}
</>
);
};
export default function Entry() {
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
setInterval(() => {
setIsLoading(!isLoading);
}, 3000);
}, [isLoading]);
return (
<Router>
<App isLoading={isLoading} />
</Router>
);
}
Edit
I've considered passing in an isLoading argument to routes, but I feel like that won't be an efficient approach, because the whole tree will rerender at any route, regardless of whether or not it depends on isLoading or doesn't. Would a better approach be to use Switch and a custom Route component for routes that depend on isLoading and just use useSelector in that custom Route component?
I think this would work:
const routes = (props) => [
{
path: "/",
element: <Main {...props} />
}
];
const App = ({ isLoading }) => {
const routing = useRoutes(routes({isLoading}));
return (
<>
{routing}
{JSON.stringify(isLoading)}
</>
);
};
Instead of passing the array, you can create a function that returns a constructed array to the useRoutes hook:
const routes = (isLoading) => [
{
path: "/",
element: <Main isLoading={isLoading} />
}
];
code sandbox:
https://codesandbox.io/s/dark-dream-hx1y9
more pretty would be:
const routes = (props) => [
{
"/": () => <Main {...props} />
}
];
const App = ({ isLoading }) => {
const routing = useRoutes(routes(isLoading));
return (
<>
{routing}
{JSON.stringify(isLoading)}
</>
)
};

ReactRouterDom, AuthRoute returns react render functions are not valid as react child warning

My Router is a simple component containing public and private routes. I have created an AuthRoute referring to the great tutorial from here
So, my Router looks like:
<Router>
<div>
<Navigation />
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route path={ROUTES.SIGN_UP} component={SignUp} />
<Route path={ROUTES.SIGN_UP_SUCCESS} component={SignUpSuccess} />
<AuthenticationRoute path={ROUTES.HOME} component={Home} />
</div>
</Router>
and my AuthenticationRoute looks like this:
export const AuthenticationRoute = ({ component: Component, ...rest }) => {
const [authChecking, setAuthChecking] = useState(true);
const [{ isAuth }, dispatch] = useStateValue();
useEffect(() => {
checkLoggedIn().then(res => {
setAuthChecking(false);
dispatch({
op: 'auth',
type: 'toggleSessionAuth',
toggleSessionAuth: res
});
});
}, [])
if(authChecking)
return null;
if(!isAuth) {
return <Redirect to='/' />;
}
return <Route {...rest} render={(props) => (
<Component {...props} />
)
} />
}
Everything looks fine, however, my console returns such warning:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from the render. Or maybe you meant to call this function rather than return it.
I have tried different solutions using component/render etc, however, I could not find a solution to this problem and I have no idea what I am doing wrong.
For testing purposes, instead of rendering Component, I tried to render simple <div>test</div> and it worked fine. However, when I am passing a JSX component in props, it returns the warning shown above.
Implementation oh Home Component (Home.js):
export const Home = () => {
const [{ user }, dispatch] = useStateValue();
const { history } = useReactRouter();
const moveTo = path => {
dispatch({
op: 'other',
type: 'setView',
setView: path
});
history.push(path);
}
return (
<div className="pageMenuWrapper">
<h1 className="homeTitle">Hi {() => user ? `, ${user.username}` : ``}.</h1>
<div className="wrapper">
<Tile image={leagueico} alt="text" onClick={() => moveTo(ROUTES.TEST)}/>
<Tile comingSoon />
</div>
</div>
);
}
export default Home;
Could anyone help me solve this little problem?

Categories

Resources