Maintaining state variables in React

3 min read 1083

Managing activity between pages can be quite easy in React if you know how to keep things in good order. The go-to way of passing props down and back up every time there’s a change makes sense, but can easily get messy.

And slow.

By breaking out your components into two simple categories and separating each page’s responsibility, you can take out the need to pass down so many props (and keep many of them up in the address bar).

Page components vs. block components

Let’s start by breaking out an application into blocks of HTML that relate to functions of the application itself. If you’ve ever used any CMS, you’re likely already familiar with the concept of managing “pages” separate from “blocks” of content.

Example:

An application has a blog post object. There is a page specific to that single blog post, but there are also individual representations of that blog in short-form. Maybe there’s a blog main page with a list of 10 per page, maybe there’s a “newest posts” section of the homepage, maybe there is an author page with every post they have authored.

The blog page is all about the content of the individual blog post, but the blog block is something that can be used wherever we want, regardless of context. To separate out your functionality into pages in React without sacrificing the ability to pass information between, it’s important that your app is structured with many page components that can use any number of block components.

More on this in a moment.

Tying pages to URLs

There are a few ways to do this and none of them come out-of-the-box with React. There are many great options of how to do it, but my favorite is react-router. Because I’m doing this example for the web, we’ll use react-router-dom, but there are options for React Native as well. Here are the basics of how that works.

  • One component (usually called App) is the top-level that owns the router and manages the history object* as part of its state and props.
  • Multiple (page) components or render functions choose what to put on the page based on the URL in the address bar currently.
  • The rest of your functionality is put into the pages on an as-needed basis.
  • This is the important bit.

Did you know that the DOM already has an object that contains all of the properties of the pieces of the URL? Do me a favor, go into your console in this browser tab, type in window.history and check that out.

Pretty cool, right? What’s great about it is that it manages where you’re at and where you’ve been using… (drumroll) state! Check out the docs when you have a minute because there are really cool things you can do with the history object.

The way that most routing works is by tying your top-level component to the history and managing its state with your browser history. It also contains a lot of cool abilities to break out URL segments, and params.



OK, so seriously, what do I do with that?

Here’s where this gets cool. By passing the history object into props on page components, you maintain state variables (even if they change at the top level) down into each page and even between them. Leverage that with the ability to put other block components wherever you want, and you have a tidy way to manage whatever information is relevant for the page of the app.

Step 1: Pages

Let’s go back to our blog example. Without a router, you would need to create a separate file with a separate state between the homepage and the blog post page, but with a router, you can pass params into the URL, even using that to dynamically set URLs.

Check it out:

import React, { Component } from "react"
import { BrowserRouter as Router, Route } from "react-router-dom"

component App extends Component {
  render () {
    return (
      <div className="app">
        <Router>
          <Route path="/" exact component={HomePage} />
          <Route path="/blog" exact component={BlogPage} />
          <Route path="/blog/:id" exact component={BlogPostPage} />
        </Router>
      </div>
    )
  }
}

With three lines, you have set up three separate pages, all that share content of the blog posts and can render the same component without having to pass a load of props. You’ll even notice, that I’ve included URL parameter for the blog post ID called id.

Step 2: Mapping the history

By taking the dynamic piece of the URL(the blog post’s ID) and moving it into a parameter, we can avoid the need for the application to have any knowledge of the blog database whatsoever.

This has huge processing savings implications. If there’s a CRUD interface inside that /blog URL, BlogPostPage can manage all of that. Even better yet, you can rely on a reducer through Redux to manage all of the local stores so that App is only responsible for making sure the right page shows.

This is what the start of BlogPostPage probably looks like:

import React, { Component } from "react"

component BlogPostPage extends Component {
  state = {
    postId: this.props.match.params.id
  }
  
  componentDidMount () {
    // database call to get other attributes for state
  }
  
  render () { ... }
}

Step 3: The fun stuff

By default react-router includes the ability to pick up on parameters, hashes, or anything else you may want to check in the URL. Every one of these is immediately available for the page component:

Parameters (for named variables): this.props.match.params

Hash (great for anchor links): this.props.location.hash

Query params (for search or other queries): this.props.location.search

All URL segments (if you need even more granular control over the path): this.props.location.pathname

This even works if you nest paths within each other:

return (
  <div className="app">
    <Router>
      <Route path="/" exact component={HomePage} />
      <Route path="/blog" exact component={BlogPage} />
      <Route path="/blog/:id" exact component={BlogPostPage} />
      <Route path="/user" exact component={UserProfile} />
      <Route path="/user/settings" exact component={UserSettings} />
    </Router>
  </div>
)

Conclusion

After some refactoring, you can think of each page of your application as separate mini-apps only responsible for their own features and break out features repeated between pages into block components that get used on each page. That keeps your state and props few and responsibilities small.

Get setup with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now

Leave a Reply