Looping through route maps in React Main.js - reactjs

I am using react-router-dom for my blogs.
My Main.js looks like
const Main = () => {
return (
<Switch> {/* The Switch decides which component to show based on the current URL.*/}
<Route exact path='/' component={Home}></Route>
<Route exact path='/1' component={art1}></Route>
<Route exact path='/2' component={art2}></Route>
{/* <Route exact path='/3' component={art3}></Route> */}
</Switch>
);
}
and I want to make it understand automatically as component = "art"+path. How should I go about it?

UPDATED ANSWER
Code has been further reduced by using dynamic imports with React.lazy. (See initial answer further below for original code)
import { lazy, Suspense } from "react";
import { Switch, Route } from "react-router-dom";
const Main = () => {
return (
<Switch> {/* The Switch decides which component to show based on the current URL.*/}
<Route exact path='/' component={Home}></Route>
<Suspense fallback="">
{
[...Array(3).keys()].map((i) => {
const artComp = lazy(() => import(`./components/Art${i+1}`));
return (<Route exact path={`/${i+1}`} key={i+1} component={artComp}></Route>);
})
}
</Suspense>
</Switch>
);
}
View updated demo on codesandbox
What the code does:
Create a Suspense component to wrap the Routes which components will be dynamically generated via React.lazy.
Inside the map function:
Generate component dynamically
Set the Route's component property to the above component
Explanation:
With React.lazy, you can dynamically import a module and render it into a regular component.
React.lazy takes a function that calls a dynamic import. This returns a promise which resolves to the imported module.
Note: the components in the imported modules should be default exports, otherwise the promise resolves to undefined.
Lazy loading means loading only what is currently needed. As the components will be lazy components, they'll be loaded only when first rendered (i.e. when you click on the corresponding link for the first time).
Notes:
No fallback has been specified for the Suspense in the answer.
With a Suspense, you can specify a fallback content (e.g. a loading indicator) to render while waiting for the lazy component to load.
The fallback content can be an HTML string, e.g.
fallback={<div>Loading...</div>}
or a component, e.g.:
fallback={<LoadingIndicator/>}
(21.08.21) React.lazy and Suspense are not yet available for server-side rendering. If you want to do this in a server-rendered app, React recommends Loadable Components.
For more information, view the React documentation about React.lazy.
INITIAL ANSWER
How about this?
import Art1 from '...';
import Art2 from '...';
import Art3 from '...';
// and so on
const Main = () => {
const artComponents = {
art1: Art1,
art2: Art2,
art3: Art3,
// and so on
};
return (
<Switch> {/* The Switch decides which component to show based on the current URL.*/}
<Route exact path='/' component={Home}></Route>
{
[...Array(3).keys()].map((i) => {
const artComp = artComponents[`art${i+1}`];
return (<Route exact path={`/${i+1}`} key={i+1} component={artComp}></Route>)
})
}
</Switch>
);
}
What this does:
Import "art"+path components into file
Note: component name should start with a capital letter.
Store imported components in an object
Create an empty array with 3 empty slots
Note: replace 3 by the number of "art"+path routes you need to generate.
Access the array's indexes
Create an array with those indexes
For each array index:
Compute the required "art"+path based on the index, and get the matching component from the object in step 2
Return a <Route>, with the path set based on the index and the component set to the matching component above
E.g. for index 0:
artComp will be artComponents[1] a.k.a. Art1
path will be /1
component will be Art1

import { useRouteMatch } from 'react-router-dom';
let { path} = useRouteMatch();
after getting the path remove the "/" from the path in the component concatenation
<Route exact path={path} component={art${path.substring(1)}}>

What you want to do isn't possible because you cannot create a variable name from a string in Javascript. I assume that you have something like
art1 = () => {
// return some JSX
}
and
art2 = () => {
// return some JSX
}
When you have variable names like this that only differ in a number, then you are almost certainly doing something wrong.

Related

React-Router Switch with <Router>..</Router> block (component with props) cannot read URL param with "props.match.params"

When I use React-Router the old-fashioned way, with a simple component={MyComponent} in my Switch Route, I can get its URL param with props.match.params. This works fine:
Container:
<Switch>
<Route path="/newAgreement/:type" component={NewAgreement} />
</Switch>
NewAgreement:
export default function NewAgreement(props) {
return (
<div>
TYPE: {props.match.params.type} <-- THIS WORKS
</div>
);
}
The modern React-Router allows you to pass custom properties in a Route block, <Route>..</Route>, which wasn't possible with the old method. I have custom properties I need to pass, so my Switch Route looks like
<Switch>
<Route path="/newAgreement/:type">
<NewAgreement userInfo="someCustomProperty" />
</Route>
</Switch>
The new method works when I don't specify any : URL params, it passes my properties as it should. But now, with the new block method and an : URL param, it started breaking with the error:
TypeError: Cannot read property 'params' of undefined
So how can I both pass custom properties within a <Route>..</Route> block to my component, and obtain :... URL params?
In the module where you declared the NewAgreement component do this;
import React from "react"
import {useParams} from "react-router"
const NewAgreement = () => {
const params = useParams();
console.log(params.id)
return (
<>
...
</>
)
}
You can find more information about it on the documentation

How to allow user to save inputs for further review

I have made a small calculating website and I would like the authenticated user to save his/her calculations.
Overview:
I have a main page with 8 input fields and several labels/graphs displaying results information. Whenever you change one of the inputs, the labels and graphs update thanks to hooks.
The goal: loading data
I made a "Save" button, which, when clicked :
saves all the inputs to firebase (this works fine already)
create a link in /myaccount/dashboard, which redirects you to the main page with your saved inputs (that I need help with)
I am having a hard time finding for resources online. However, while looking for responsive calculating websites, I came across this one : https://optionstrat.com
Even-thought I have no idea what they are calculating it does what I'm looking for, ie : you can "save a trade" and then go to you account where all the saved trades are displayed.
Does anyone know a good tutorial how to do so ?
Thanks you :)
Edit
This is my save function in my App.js:
function Savecalc(){
const calcRef = db.ref("User_"+auth.currentUser.uid);
const newCalc = calcRef.push();
newCalc.set({inputs:{a,b,c},outputs:{x,y}});
/* Then attribute an URL to a saved calculation*/
}
Then, in my Dashboard.js, I would have:
const db=app.database();
export default function Dashboard() {
/* getting the user calculations */
return (
<div>
<!-- Mapping of the user's calculations -->
</div>
)
I suspect what you are probably looking for is a little library called react-router-dom. This library essentially provides a collection of navigational components that you can employ to navigate around your app. Here is a basic example. Once you install it into your project, you should create a separate AppRouter.js file that might resemble something along the lines:
import React from 'react';
// Install the react-router-dom package
import { Router, Route, Switch } from 'react-router-dom';
// Further install the history package
import { createBrowserHistory } from 'history';
// Import your dashboard component and all other components you wish to create a route to
// This is just an example
import HomePage from '../components/Homepage';
import Dashboard from '../components/Dashboard';
// You will need to create a Page Not Found component that redirects when a wrong URL is inserted
import NotFoundPage from '../components/NotFoundPage';
export const history = createBrowserHistory();
const AppRouter = () => (
<Router history={history}>
<Switch>
<Route path='/' component={HomePage} exact={true} />
<Route path='/myaccount/dashboard' component={Dashboard} />
<Route component={NotFoundPage} />
</Switch>
</Router>
);
export default AppRouter;
Then in your main/app.js file you will need to add the AppRouter component. It should look something like this:
const Application = () => (
<Provider store={store}> // if using react-redux, otherwise ignore Provider
<AppRouter />
</Provider>
);
ReactDom.render(<Application />, document.getElementById('app'));
You can also create public and private routes (e.g. accessible only for signed in users). Examples of these can be found here.
If you are saving the user's calculations, you could then query them on whatever page you want to display them on e.g. /myaccount/dashboard.
You can then map over them and display them in the UI as links, where the link could be something like /myaccount/dashboard/YlxIJ2zOxI9KYJ5Dag6t where YlxIJ2zOxI9KYJ5Dag6t is the auto-generated document ID from Firestore.
Assuming you are using React Router, you can then have a route such as:
<Route exact path="/myaccount/dashboard/:id">
On this page, you can get the ID of the document from the parameters using React Router's useParams hook like so:
const { id } = useParams();
You can then query the specific calculation's info in useEffect, and display them however you want.
Let me know if this is what you need, or if you need more help with any step!

Slash issues in sites which use React router

Already week I stumbled a bug which related React-router.Firstly I think this bug is only in my site.Then I find out that a giant corporation's that use a ReactJS have same issue.
If you wrote more than one slash in url and press enter you will see the (small)site(s) show header and footer.
And giant project's behaviour will be unusual.Some of components will be break some of them will not shown.
Example of this project's.
Marvel, Airbnb
So.I try to clean up my URL.
if (window.location.pathname.length > 1) {
// const url = window.location.pathname.slice(0, 1);
const url = window.location.pathname.replace(/\/\//g, "");
history.push(`${url}`);
}
I don't know why this try not work.
Codesandbox for test.If you write more than one slash in url it will show only list of component's.How to fix that?
Consider this, as a replacement to your Main.
It will fix the double or more slashes issure.
It fixed the static render for every route as only only component.
Full CodeSaneBox.
import React, { Component } from "react";
import { Switch, Route } from "react-router-dom";
import { withRouter } from "react-router-dom";
import Home from "./Home";
import Roster from "./Roster";
import Schedule from "./Schedule";
// The Main component renders one of the three provided
// Routes (provided that one matches). Both the /roster
// and /schedule routes will match any pathname that starts
// with /roster or /schedule. The / route will only match
// when the pathname is exactly the string "/"
class Main extends Component {
componentDidMount() {
this.fixDoubleSlash();
}
// Will check for double slashes and redirect to the correct path.
fixDoubleSlash = () => {
const {
location: { pathname },
history: { push }
} = this.props;
if (pathname.match(/\/{2,}/g)) {
push(pathname.replace(/\/{2,}/g, "/"));
}
};
render() {
return (
<Switch>
<Route exact path="/" component={Home} />
<Route path="/roster" component={Roster} />
<Route path="/schedule" component={Schedule} />
</Switch>
);
}
}
export default withRouter(Main);
use window.history.pushstate
if (window.location.pathname.length > 1) {
// const url = window.location.pathname.slice(0, 1);
const url = window.location.pathname.replace(/\/\//g, "");
window.history.pushState('page2', 'Title', url);
}
You need to define one component as NotFound which will render in case user tries to access any path which doesn't exist in the application.
in your Route add this
<Route component={NotFound} />
and this should be present at the end of all route otherwise you request for any URL it will go to NotFound.

Dynamically import component in React based on config entries

I have multiple apps as part of one React-redux-typescript project. All of these individual apps are part of a core-application. I want to dynamically set up routing.
My current routes look like this:
Routes.tsx
import HomePageApp from "../Components/Home/HomeApp";
import TestApp from "../Components/Test/TestApp";
export default function Routes() {
return (
<Switch>
<RedirectIfAuthenticated
exact={true}
isAuthenticated={true}
path={Path.homePath} --> "/"
component={HomePage} ---> AppName coming from import statement on top
redirectPath={Path.homePath} --> "/"
/>
<RedirectIfAuthenticated
isAuthenticated={true}
path={Path.apps.test} --> "/test"
component={TestApp} --> AppName from import on top
redirectPath={Path.homePath} --> "/"
/>
</Switch>
);
}
And RedirectIfAuthenticated simply redirects to correct applications' landing pages.
RedirectIfAuthenticated.tsx
export default function RedirectIfAuthenticated({
component,
redirectPath,
isAuthenticated,
...rest
}: IRedirectIfAuthenticatedProps) {
const Component = component;
const render = (renderProps: RouteComponentProps<any>) => {
let element = <Component {...renderProps} />;
return element;
};
return <Route {...rest} render={render}/>;
}
I've a config file like this:
Manifest.ts
export let manifest = {
apps: [
{
componentPath: "/Test/App",
path: "/test",
name: "Test"
},
...more objects for other apps
]
};
In my Routes.tsx, I want to make use of my manifest to render the RedirectIfAuthenticated component.
so I can figure out this change:
for brevity showing the dirty approach but the actual code iterates over the manifest using .map and renders RedirectIfAutenticated.
const app = manifest.apps.find(app => app.name === "Test");
<Switch>
<RedirectIfAuthenticated
isAuthenticated={true}
path={app.path} --> "/test"
component={What should I do here? How to pass component reference by path??}
redirectPath={"/"} ==> I typically get this from my manifest..
/>
</Switch>
One option is to do this:
component={require("path from manifest").default}
but our tslint throws a bunch of errors at this. Other than this I can't figure out how to pass the component reference here dynamically. Looking for a better approach.
The Routes.tsx needs to be dynamic so that adding new apps is a matter of configuration so I can't do imports on top because I dont know what's gonna be added in config. Thanks.
I was able to use dynamic imports to achieve this. I used this article to understand a few concepts.
private loadComponentFromPath(path: string) {
import(`../../ScriptsApp/${path}`).then(component =>
this.setState({
component: component.default
})
);
}
One important distinction here is if I don't give the path from the root of the app, I get an error saying "Unable to find the module". This is why I've given the full path from the root.

How do I go to a specific page in React based on a URL param

As an example when entering http://localhost:3000/ui/?goto=/ui/entity/e2 in the browser I'd like to go to the Entity component e2.
This is my router:
<Route path="/ui/" component={App}>
<IndexRoute component={EntitiesPage} />
<Route component={Entity} path="entity/:id" />
<Route component={NotFound} path="*" />
</Route>
This is the App component:
import React from 'react'
const App = React.createClass( {
render() {
let gotoUrl = this.props.location.query.goto;
if (gotoUrl) {
// go here: gotoUrl;
} else {
return (
<div className="App">
{this.props.children}
</div>
)
}
}
})
export default App
this.context is empty.
this.props has:
history
location
route
routeParams (empty)
routes
UPDATE:
I've ended up using this:
import React from 'react'
import { withRouter } from 'react-router'
const App = React.createClass( {
componentWillMount() {
let gotoUrl = this.props.location.query.goto;
if (gotoUrl) {
this.props.router.replace(gotoUrl);
}
},
render() {
return (
<div className="App">
{this.props.children}
</div>
);
}
})
export default withRouter(App)
One thing that might be tripping you up is that render should have no side effects.
A "side effect" is anything that changes what's going on in your app*: updating state, making AJAX calls, or in this case, altering the page location. The render method should only read from the current state of the component, then return a value.
Because you're already using React.createClass, the best way to handle this is by adding a separate method that React handles specially: componentWillMount. I'd recommend you put your "redirect" logic here.
In order to properly change the page location, you'll need access to the browser history object which react-router manipulates. You can import this from the react-router library itself and directly call methods on it:
// At top of file
import { browserHistory } from 'react-router'
// Then, in your component:
componentWillMount() {
let gotoUrl = this.props.location.query.goto;
if (gotoUrl) {
// NOTE: this may have security implications; see below
browserHistory.push(gotoUrl);
}
}
Source: documentation.
I'd suggest that, instead of using query.goto, you instead select a parameter that can be easily validated, such as the entity ID itself (a simple regex can make sure it's valid). Otherwise, an unscrupulous user might send a link to another user and cause them to access a page that they didn't mean to.
*Note: there are stricter definitions of "side effect" out there, but this one is pretty useful for React development.
You should use browserHistory
import { browserHistory } from 'react-router';
...
if (gotoUrl) {
browserHistory.push(gotoUrl)
}
Let me know if this works

Resources