React Javascript

Recently at Videofruit, we had a fun internal exercise of building an app in single workday. That app ended up being an Email Service Provider picker where you answer a series of questions and we recommend an email service provider to meet your needs. In this post, we’ll breakdown a simplified version of that app focusing in on how we built a routing mechanism that is simple but effective and uses no external dependencies.

The ESP picker app essentially consists of 4 pages: the landing page, a question page, an email collection page, and the recommendation page. We’ve implemented each of these pages as their own component and will now demonstrate how we built a sub-system to decide which component to render and then renders it.

Surprise, surprise. In typical React fasion, we’ve implemented this component selection logic wthin yet another component. This PagePicker component exists near the root of the component hierarchy and has its own state which holds the current component to render. PagePicker also defines a set of callbacks to pass into each page in order to update that state and then render new components.

import React, { Component } from "react";
import LandingPage from "./LandingPage";
import QuestionPage from "./QuestionPage";
import RecommendationPage from "./RecommendationPage";

import questions from "./questions.js";

class PagePicker extends Component {
  constructor(props) {
    super(props);

    this.state - {
      currentPage: "LandingPage",
      currentQuestion: 0
    };

    thisstartQuestions = this.startQuestions.bind(this);
    this.goToRecommendationPage = this.goToRecommendationPage.bind(this);
  }

  startQuestions() {
    this.setState({ currentPage: "QuestionPage", currentQuestion: 0 });
  }

  nextQuestion() {
    this.setState(prevState => {
      let nextQuestion= prevState.currentQuestion + 1;
      if (nextQuestion >= questions.length) {
        return { currentPage: "RecommendationPage" };
      } else {
        return { currentQuestion: nextQuestion };
      }
    });
  }

  calculateRecommendation() {
    // Your logic here
    return "super-awesome-recommendation";
  }

  render() {
    switch (this.state.currentPage) {
      case "LandingPage":
        return (
          <LandingPage 
            startQuestions={this.startQuestions}
          />
        );
        break;
      case "QuestionPage":
        return (
          <QuestionPage
            question={questions[this.state.currentQuestion]}
            nextQuestion={this.nextQuestion}
          />
        );
        break;
      case "RecommendationPage":
        return (
          <RecommendationPage
            recommendation={this.calculateRecommendation()}
          />
        );
        break;
    }
  }
}

As you can see, this approach is pretty straight forward and, most importantly, it requires no third-party dependencies. As with any implementation, there are caveats and trade-offs. Firstly, this approach does not work with an app where you expect the number of pages to grow large. The switch statement can easily get unweildy and hard to maintain.

If your app has pages which need to communicate to eachother directly, this approach could cause problems. Our app is very linear and each page is very separate in both state and function. We take advantage of that in this technique.

Overall, we’re pleased with the declarative layout of this approach and that the current page being shown is clearly defined in the state. Any of the transition callbacks can contain more complex logic which would be fuctionally encapsulated and easily testable. We hope you find some value in this technique too.

Happy routing!

– Chris

Image

Christopher R Marshall

@codegoalie

Enjoys programming web applications; especially in Ruby and Go. Also enjoys playing ice hockey as a goalie and playing the guitar.

Categories