0

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?

  • 1
    Why do you have components named `art1` and `art2`? How are these different? Can you create a single component `Art` instead and reuse it? If you don't know how, please show the code for `art1` and `art2`. – Code-Apprentice Aug 20 '21 at 13:57

3 Answers3

2

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:

  1. Create a Suspense component to wrap the Routes which components will be dynamically generated via React.lazy.

  2. 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:

  1. Import "art"+path components into file

    Note: component name should start with a capital letter.

  2. Store imported components in an object

  3. Create an empty array with 3 empty slots

    Note: replace 3 by the number of "art"+path routes you need to generate.

  4. Access the array's indexes

  5. Create an array with those indexes

  6. For each array index:

  7. Compute the required "art"+path based on the index, and get the matching component from the object in step 2

  8. 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
Akasha
  • 969
  • 5
  • 8
  • 1
    This code works but can it be further reduced? I don't want the file to have one line per article. I want it to automatically get the routed path. – Abhijay Mitra Aug 21 '21 at 12:41
  • Also I am getting warning `Each child in a list should have a unique "key" prop.` – Abhijay Mitra Aug 21 '21 at 13:01
  • It should be possible to further reduce the code. I'll test some options and update the answer. The code sample has also been edited to include a key prop on Route (the demo on codesandbox already had it). This will remove the warning. (A key is a property included on each element in a list - here, a list of Routes - to uniquely identify them. [For more information, view the React documentation about lists and keys](https://reactjs.org/docs/lists-and-keys.html).) – Akasha Aug 21 '21 at 13:39
  • The answer has been updated to include a reduced version of the code. – Akasha Aug 21 '21 at 16:38
  • Thanks a lot Akasha. Really appreciate. – Abhijay Mitra Aug 21 '21 at 17:18
  • 1
    Also, in case the number of blogs is large, say n, every time the client wants to visit a blog, Main.js executes O(n) queries totalling O(n * n) queries which is very slow. Can you suggest a way out? – Abhijay Mitra Aug 21 '21 at 18:14
  • In `Main.js`, instead of creating multiple `Routes`, you could create on-demand only the `Route` that you need. When you click on a `Link`, pass the path to the event handler, and lift the state up to `Main.js`. In `Main.js`, inside the `Suspense`, instead of looping through an array, you would simply use the path stored in the state to generate the component dynamically and create the `Route`. – Akasha Aug 21 '21 at 23:19
0
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)}}>

  • I don't think this will work. `art1` and `art2` are components. Presumably the OP has `art1 = () => { /* return some JSX */}` somewhere. You cannot build a variable name from a string in Javascript. – Code-Apprentice Aug 20 '21 at 13:59
  • Also, you can't use `useRouteMatch()` to build the route that you are trying to match in the first place. – Code-Apprentice Aug 20 '21 at 14:11
0

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.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268