Routing in a large application on React


    Hi, my name is Boris Shabanov , I am the head of Frontend-development in the department of development of advertising technologies of Rambler Group. Today I will tell you about how routing problems arose on our application, and how we solved them.


    Advertising Technologies - a large department in the Rambler Group that implements a full stack of advertising technologies (SSP, DMP, DSP). For end users, namely advertisers and agencies, we make a user-friendly interface called Summer , into which they can launch their own advertising campaigns, watch statistics on them and manage everything.



    The interface Summer is a set of screens, forms, linked by links to each other. Being in one place, in a few clicks, the user can find the information he needs.



    After several years of operation, the requirements for the project became more serious, and we realized that we need to fix it. Since from the very beginning the project was on React, we took the React Create App as the basis for the new version . And GraphQL - to interact with the server, namely - Apollo client . Especially in the department there is a great expertise.


    But we have a question, and what can be used to route our application? We went to Google to look for a solution with the words "react" and "router", and, in fact, on the first 5 pages we found nothing except React Router . "Well, ok ..." - we thought, smart people recommend, we must take. After a short period of questions did not become less. For example, take a simple example of using react-router:


    import React from"react";
    import { BrowserRouter as Router, Route, Link } from"react-router-dom";
const ParamsExample = () => (
      <Router>
        <div>
          <h2>Accounts</h2>
          <ul>
            <li>
              <Link to="/netflix">Netflix</Link>
            </li>
            <li>
              <Link to="/zillow-group">Zillow Group</Link>
            </li>
            <li>
              <Link to="/yahoo">Yahoo</Link>
            </li>
            <li>
              <Link to="/modus-create">Modus Create</Link>
            </li>
          </ul>
          <Route path="/:id" component={Child} />
          <Route
            path="/order/:direction"
            component={ComponentWithRegex}
          />
        </div>
      </Router>
    );

    There are questions:


    • How to deal with a URL with a large number of parameters?
    • What should I do if the Product manager comes and asks to change the URL of the page to a more “friendly” one? "Carpet commit" throughout the project did not really want to do.

    In the development process, we began to think and look for solutions, but at the same time we lived on the React-router again.


    In the next sprint, we took the help section. Not a complicated page, and within a few hours we made it.


    But the whole point is that Help is a section that all other pages link to, and there are a lot of links to it. And the link to each answer is formed dynamically. For us, this was another reason to think about changing the router.


    After a long search on the Internet, we found an acceptable solution - Router5 .



    Router5 is a library that has nothing to do with React, you can use it with Angular, with jQuery, with anything. It should immediately be said that Router5 is not a continuation of React-router @ 4, these are two different things, developed by different people, and they are not related to each other.


    Accordingly, when choosing a library, we began to look at how popular it is and whether it makes sense to use it at all. If we compare the number of stars on github'e, then the advantage in the direction of react-router.



    But we decided to take a chance. We looked at how the project lives, whether someone is developing it, saw that people bring new things to it, and the project is very much alive.



    From the documentation, you can find out that he has a lot of integrations with different frameworks and libraries. In particular, the documentation describes integration with react and redux, but if you search well, you can find examples with mobX there.



    How are the interfaces built, and how are routers built in Router5?
    Here is the same story as in React-router: each route is a container for its children.


    Suppose you have a site that consists of a landing page (we’ll have it at home), and there is a kind of admin panel that consists of two sections - a list of users and a list of groups of these users. We can further configure all this in such a way that our Home section will look like a landing page. The admin section will then have a wrapper for all of its child pages and elements, i.e. there will be a footer and header there, for example. All nested pages already in the admin panel will be wrapped in this footer and header.



    For such a site, the Router5 config will be as follows:


    import createRouter from'router5';
    import browserPlugin from'router5/plugins/browser';
    const routes = [
        {
            name : 'home',
        },
        {
            name : 'admin',
            children : [
                {
                    name : 'roles',
                },
                {
                    name : 'users',
                },
            ]
        },
    ];
    const options = {
        defaultRoute: 'home',
        // ...
    };
    const router = createRouter(routes, options)
      .usePlugin(browserPlugin());
    router.start();

    In the example we have a simple version, but in fact you can learn much more from the documentation and choose a more convenient format for yourself.


    As I said earlier, we use React on our projects in the division, so I will continue to tell and show examples more towards React. The example code below shows how to raise the project using Router @ 5.


    // app.jsimport ReactDOM from'react-dom'import React from'react'import App from'./App'import { RouterProvider } from'react-router5'import createRouter from'./create-router'const router = createRouter()
    router.start(() => {
        ReactDOM.render((
            <RouterProviderrouter={router}><App /></RouterProvider>
        ), document.getElementById('root'))
    })

    // Main.jsimport React from'react'import { routeNode } from'react-router5'import { UserView, UserList, NotFound } from'./components'functionUsers(props) {
        const { previousRoute, route } = props
    ​
        switch (route.name) {
            case'users.list':
                return<UserList />
            case 'users.view':
                return <UserView />
            default:
                return <NotFound />
        }
    }
    ​
    export default routeNode('users')(Users)

    Further more interesting. The thing that we liked and very elegantly fit into the project is how the transitions are made. Suppose we have a site with the structure described above. And our user wants to go from the landing page to the "List of Users" section. What will router5 do in this case? First of all, it deactivates the home section, causing the appropriate events. You can handle events both in the route itself and in the middleware, in which you can handle all these transitions. Further events of activation of routes admin and admin.users are generated.



    We have in the application all the middleware collected in one place. These are middlewares that are responsible for loading components (we try to “cut” the application into pieces as much as possible and load the parts that the user needs now), localize, retrieve data from the server, check access rights and collect analytics of transitions. As a result, the developers do not even think about how they get the data, check the rights and what to display on the screen. They do the next section, and Router5 solves all the problems.


    Another problem that we wanted to solve once and for all is loading the application, broken into different pieces. In my opinion, downloading an application with React-router is similar to the adventures of a Hedgehog in the fog. When loading any pieces and data at each stage, your application is not insured against errors receiving data from the server. And in this case, your designers should come up with the state of the screens for each emergency situation. It seems in this place the likelihood of a developer error is high (he may just forget about it), and these risks need to be minimized.


    In Router5, this is decided by the fact that each transition is transactional, and the change of the visual part will not occur until all middleware has been worked out and the router is not convinced that our application can draw what we want from it. This approach has its drawbacks and advantages, but in our case there were more advantages. The issue with the loading indicator is immediately resolved. Our application uses one download indicator, which is controlled by a router.


    Unfortunately, working with the middleware 3 or 4 months ago was not fully disclosed in the documentation. But after digging into the issues, we found a great example showing how to deploy and build an application using React, Redux, and Router5.


    Each of the URLs of our application stores in itself a set of data that we need to display data (identifiers, additional data filtering parameters, etc.). Serialization and deserialization of these parameters from the URL to router5 and back does not look supernatural, but it exists.


    exportdefault {
      name: 'help',
      path: '/help*slugs',
      loadComponent: () =>import('./index'),
      encodeParams: ({ slugs }) => slugs.join('/'),
      decodeParams: ({ slugs }) => slugs.split('/'),,
      defaultParams: {
        slugs: [],
      },
    };

    For our project, this was an important parameter for choosing a router, because our application has a lot of filters and pop-ups. And it should display in the URL the state that the user now sees on the screen. If the user wants to show something to his colleague, he simply fumbles the URL from the address bar of his browser.


    The following are basic examples:


    const router = router5([
        { name: 'admin', path: '/admin' },
        { name: 'admin.users', path: '~//users?param1' },
        { name: 'admin.user', path: '^/user/:id' },
        { name: 'help', path: '^/help/*splat' }
    ]);
    console.log(router.buildPath('admin'));                         // '/admin'console.log(router.buildPath('admin.users');                    // '/users'console.log(router.buildPath('admin.users', { param1: true })); // '/users?param1=true'console.log(router.buildPath('admin.users', { id: 100 }));      // '/user/100'console.log(router.buildPath('admin.user', { id: 100 }));       // '/user/100'console.log(router.buildPath('help', { splat: [1, 2, 3] }));    // '/help/1/2/3'

    In the React component, the link is formed as follows:


    import React from'react'import { Link } from'react-router5'functionMenu(props) {
        return (
            <nav><LinkrouteName="home">Home</Link><LinkrouteName="about">About</Link><LinkrouteName="user"routeParams={{id:100 }}>User #100</Link></nav>
        )
    }
    ​
    exportdefault Menu

    When creating simple websites consisting of 3-4 pages with a minimum number of transitions, I do not use Router5, but I use React-router. But in projects with multiple pages and links, my choice will be towards Router5.


    You can also watch a video of my performance on RamblerFront & # 5 here:



    PS In the first half of October 2018, we plan to hold another RamblerFront & # 6. Follow the announcement here on habr.com.


    Also popular now: