Simplest, no-dependency router is... state

09 Apr 2018

Let's face it. Learning React seems hard enough.

It only gets harder with each additional buzzword you add to the mix — GraphQL, Redux, MobX... React Router... the list can seem never-ending. Those are all tools that have their use; there's no denying that.

But for someone who's just getting into the React world, they only add to confusion and complexity, and make the goal of "knowing React" look even more distant.

So you figured you want your app to have several pages. To be able to navigate between them.

Do you have to dive into React-Router immediately now? Or is there an alternative?

Why yes, there is!

React is pretty powerful, and we can implement the simplest router in the world using one of the core mechanisms of React: state.

Simplest router, V1

If you think about it, the router is just a glorified conditional statement that decides which page to render:

class App extends Component {
  render() {
    const currentPage = 'index';
    if (currentPage === 'index') {
      return <Index />;
    } else if (currentPage === 'b') {
      return <PageB />;
    } else {
      return <div>404 Not Found</div>
    }
  }
}

Storing the current page name in a local variable is not ideal as we can’t change it... so we can store it in the state instead:

class App extends Component {
  state = {
    currentPage: 'index',
  };

  render() {
    const { currentPage } = this.state;

    if (currentPage === 'index') {
      return <Index />;
    } else if (currentPage === 'b') {
      return <PageB />;
    } else {
      return <div>Something went wrong</div>
    }
  }
}

And since Index might want to go to PageB, we can pass each page a function to switch to another page:

class App extends Component {
  state = {
    currentPage: 'index',
  };

  goTo = (pageName) => this.setState({ currentPage: pageName });

  render() {
    const { currentPage } = this.state;

    if (currentPage === 'index') {
      return <Index goTo={this.goTo} />;
    } else if (currentPage === 'b') {
      return <PageB goTo={this.goTo} />;
    } else {
      return <div>Something went wrong</div>
    }
  }
  }

const Index = ({ goTo }) => (
  <div>
    <h2>Index Page</h2>
    <a onClick={() => goTo('b')}>Page B</a>
  </div>
);

const PageB = ({ goTo }) => (
  <div>
    <h2>Page B</h2>
  </div>
);

Pages can have additional data. Simplest router, V2

Some pages might need a bit of additional data. For example, the profile page might need a username to show the correct profile.

For that, we would need to make a couple of changes:

  • currentPage in the state should store not only the page name, but also additional parameters.

    We can achieve this by storing an object like { name: 'a', params: null } or { name: 'profile', params: { username: 'xxx' } } in currentPage.

  • The goTo function should accept additional parameters for the page.

    Those should be stored in currentPage's params.

  • The router (aka App component) should pass those additional parameters to the current page.

class App extends Component {
  state = {
    // CHANGE:
    // we are now allowing to store page's params
    currentPage: { name: 'index', params: null },
  };

  // CHANGE:
  // goTo now accepts optional params for the page
  goTo = (pageName, params) => {
    const currentPage = { name: pageName, params: params };
    this.setState({ currentPage: currentPage });
  }

  render() {
    const { currentPage } = this.state;

    // CHANGE:
    // since currentPage is now an object, we use currentPage.name in conditionals
    if (currentPage.name === 'index') {
      return <Index goTo={this.goTo} />;
    } else if (currentPage.name === 'profile') {
      // CHANGE:
      // we can get the required param from currentPage.params
      // and pass it as props
      return <Profile goTo={this.goTo} username={currentPage.params.username} />;
    } else {
      return <div>Something went wrong</div>
    }
  }
  }

const Index = ({ goTo }) => (
  <div>
    <h2>Index Page</h2>
    {/* CHANGE: here's how we pass additional params */}
    <a onClick={() => goTo('profile', { username: 'wolfy' })}>Profile: wolfy</a>
  </div>
);

// username is just a regular prop
const Profile = ({ goTo, username }) => (
  <div>
    <h2>Profile of user {username}</h2>
    <a onClick={() => goTo('index')}>Index</a>
  </div>
);

It's Good Enough™

The router we have is good enough to get going. To stop worrying about thousands of React libraries and start creating and experimenting.

I've made quite a few learning projects with a similar setup back when I was learning React. This is simply easier than having to dig into React Router from Day 1.

Now, when it comes to production use, it has some shortcomings:

  • It does not use or update the URL in the browser's address bar (so that if I go to page B, copy the URL, and share it with you, you will see the index page (A) instead.)
  • It can't go back/forward.
  • It can't prevent a transition from the current page conditionally.
  • ... the list goes on.

But when you’re only starting to learn React, it is a viable router that's just simple enough to get going.

Each of those can be addressed, of course... but when these begin to matter, you can as well learn React Router.

For now, though, this simplest router is Good Enough™.

See also

Think your friends would dig this article, too?

Google+
If you need a mobile app built for your business or your idea, there's a chance I could help you with that.
Leave your email here and I will get back to you shortly.