0

I am working on React and Node

Now, I want to create a dynamic URL to show user profile

In Node with EJS we could have simply done something like this

route.get("/:id", function(req, res) {
  playground.find({}, function (error, plyground) { //MongoDb find syntex
    if (error) {
     console.log(error);
    } else {
 playground.findById(req.params.id).populate("comments").exec(function(error, playground)  {
    if (error) {
      console.log("error -> Show route or more information about playground")
    }
    else {
      res.render("playground/show", {playground:playground, plyground:plyground})
    }
  });
}
})
})

Here since EJS and Node both work on the same address, hence whenever user reaches /:id it would find the user info from the DB and render it.

In case of react, my node application is running on localhost:8080 and react app on localhost:3000

I am interacting by either making API calls using axios or redirecting to react webpage address.

Now, I want to show user profile in React when someone reaches

 route.get("/:id", function(req, res) {

Question: So can someone please tell me how will I be able to create dynamic URL react app?

Ps: I am using react-route for navigating in my react app

import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom'
//Remaining imports 

const route = () => {
    return(
    <BrowserRouter>
      <div>
      <Switch>
        <Route path ="/" exact render ={(props) => <Home {...props}/>} />
        <Route path ="/signup" exact render ={(props) => <Signup {...props}/>} />
        <Route path ="/Home" exact render={(props) => <MainHomeScreen {...props}/>} />
    </Switch>
      </div>
    </BrowserRouter>
    )
}

export default route;

3 Answers 3

2

On the server side we just send the data and we handle it on the client side, although you can also server side render it.

route.get("/:id", (req, res) => {
    const data = getDataFromDB(req.params.id);
    res.send(data);
});

Now on react side let's start from the routing

<Switch>
  <Route path ="/:id" render ={(props) => <DisplayData {...props}/>} />
</Switch>

Now react-router-dom injects a match prop that we can access the id parameter just like express.

So in DisplayData component we add componentDidMount lifecycle hook that will fetch the data based on the id parameter and set the state.

class DisplayData extends Component {
  componentDidMount = () => {
    const id = this.props.match.params.id;
    axios(id)
      .then(data => this.setState({ data }))
      .catch(err => /* do something */);
  }
}
Sign up to request clarification or add additional context in comments.

Comments

2

There are 2 approaches (that I know off) to solve your problem and both have their pros & cons.

Approach 1 - Use react-router routes for client side routing, and express/koa routes for server side routing.

This is basically what you have shown in your OP - use route.get(...) on server side and BrowserRouter on client side. This approach has potential to create duplicate routes handling (and in separate places) which is the biggest con IMO. The duplication however, is not a big issue since it can be minimized to a certain extent with common modules. If you prefer to use a single route definition and more of unified solution, read on to approach #2.

Approach 2 - Use react-router routes for both server & client side routing.

Here, you'd use StaticRouter for server side routing and BrowserRouter for client side routing. This is all laid out very nicely at react-router-dom/docs/guides/server-rendering. It'd be a disservice and rather pointless exercise to copy paste the code from above doc but here are some key takeaways:

  • your routes are now in one common place, say ./shared/routes.js
  • your js would look something like

    // app.js
    import { routes } from "./shared/routes.js";
    
    const App = () => (
    <Switch>
        {routes.map(route => (
            <Route {...route} />
        ))}
    </Switch>
    );
    
    // server.js
    import { createServer } from "http";
    import React from "react";
    import ReactDOMServer from "react-dom/server";
    import { StaticRouter } from "react-router";
    import App from "./App";
    
    createServer((req, res) => {
        const context = {};
    
        const html = ReactDOMServer.renderToString(
            <StaticRouter location={req.url} context={context}>
                <App />
            </StaticRouter>
        );
    
        if (context.url) {
            res.writeHead(301, { Location: context.url });
            res.end();
        } else {
            res.write(`
            <!doctype html>
            <div id="app">${html}</div>
            `);
            res.end();
        }
    }).listen(3000);
    
    // or if using express/koa
    app.get('/*', (req, res) => {
        const context = {};
        const app = ReactDOMServer.renderToString(
            <StaticRouter location={req.url} context={context}>
                <App />
            </StaticRouter>
        );
        ...
    })
    
    // client.js
    import ReactDOM from "react-dom";
    import { BrowserRouter } from "react-router-dom";
    import App from "./App";
    
    ReactDOM.render(
        <BrowserRouter>
            <App />
        </BrowserRouter>,
        document.getElementById("app")
    );
    

And if you need to fetch data or do some other hydration at server, you can use the data loading techniques described here: Data Loading.

Again, this code is not your production ready code. The key points to remember are that you need to:

  • create a common route definition
  • on server, render the routes using StaticRouter
  • on client, render the same routes using BrowserRouter
  • you can use promises or async/await to hydrate app on server using the route definition file if needed (axios, isomorphic-fetch etc. are good libraries that work on both server and client equally well).

If I misunderstood your question, and if all you want to know is how to create 2 routes that can distinguish between / and /:id; then I'd say, on top of what I mentioned above, what you need to do now is a matter of simple ordering the routes and matching with exactness. So you can define your :/id route first and then your / route to prevent collision or, modify your routes to something that is semantically correct like users/:id.

react-router (and most every router) will match / for almost everything. exact prop makes it conservative but that's more of a band-aid. You'd be better off by leaving / to your homepage and defining routes as entity/:id in general.

Comments

1

There is not an easy or short way of explaining it but here is an example to get you going... Let's say you had a portfolio site and in there you had individual portfolio items...

Here is how you do dynamic routing with react-router:

Your router:

const AppRouter = () => (
  <BrowserRouter>
    <div>
      <Switch>
        <Route path="/portfolio" component={PortfolioPage} exact={true} />
        <Route path="/portfolio/:id" component={PortfolioItemPage} />
      </Switch>
    </div>
  </BrowserRouter>
);

Then here is your ProfolioPage:

const PortfolioPage = () => (
  <div>
    <h1>My Work</h1>
    <p>Checkout the stuff I've done:</p>
    <Link to="/portfolio/1">Item One</Link>
    <Link to="/portfolio/2">Item Two</Link>
  </div>
);

And finally your PortfolioItemPage (you can get access to that id via {props.match.params.id} - see below):

const PortfolioItem = (props) => (
  <div>
    <h1>A Thing I've Done</h1>
    <p>This page is for the item with the id of {props.match.params.id}</p>
  </div>
);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.