Getting started with GlueStick

We’re excited to announce that GlueStick, launched in 2015, has now reached v1.0! In case you missed our initial announcement, GlueStick is a command line interface (CLI) that helps you rapidly develop universal React applications. GlueStick handles asset package management, server-side rendering, and comes with a fully-functional testing environment and generators to help you move quickly. For more information on why and how we built GlueStick, see our original post here. Also, check it out on GitHub.

This tutorial will explain GlueStick and some of its awesome features. This tool was designed by TrueCar’s very own Rails team to improve productivity, and perhaps bring a little joy to the process of building front-end web applications.

Currently the test suite runs through Chrome, but we will be adding support for more browsers in the future. So, the assumption, for the remainder of this guide, is that you will be using Chrome.

Installation

We need to install two different components to get started.

First you need to install a version of Node. We recommend anything from Node 6+ to take advantage of the improved debugging tools for server side rendering.

Then to install the GlueStick tools you need, run the following command: sudo npm install gluestick-cli -g

Creating a new application

GlueStick attempts to handle as much of the non-application specific code as it can for you. However, there is still some boilerplate code and a common folder structure that you will need in order to build a GlueStick app. To make this simple, GlueStick provides the “new” command.

Time to create a new project: to create a new project, run the following command: gluestick new ExampleApp

This is will create a folder named ExampleApp in the folder on which you just ran the command. GlueStick sets up the folder structure and installs all of the node_modules that you will need for the base application. Here is an example of what you can expect to see in the newly created ExampleApp folder:

    - package.json
    node_modules/
    src/
        apps/main/
            -Index.js
            -routes.js
            actions/
            assets/
            components/
                - Home.js
                - MasterLayout.js
            containers/
                - HomeApp.js
            reducers/

Running your application

Now that you have a new project started, running the application in the browser is next. Change directories into the ExampleApp folder cd ExampleApp and run the start command: gluestick start

This will kick off a few things. It is going to run your code through webpack dev server as well as run our universal web server. The webpack dev server serves our file assets to the web browser and enables hot module replacement (explained more below). The universal web server is the part that delivers the initial html, generated from front-end code to the web browser, before the file assets are hooked up. The universal web server piece will also let us do server-side rendering of your React application.

Not only does the start command kick off our servers, but it also starts up our test runner in its own dedicated Chrome instance so you can have tests continuously running while you work on your app. Now, open your browser to http://localhost:8888/ and you should be greeted with the “Welcome To Gluestick” startup page.

Hot Module Replacement

One of our favorite features of GlueStick is hot module replacement, an awesome feature that allows for updates to show without refreshing the browser. To start, open src/apps/main/components/Home.js in your favorite editor. Replace the Home class that you see with the following:

export default class Home extends Component {  
    render () {  
        return (  
            <h1>Hello World</h1>  
        );  
    }  
}

Make sure the web browser is visible and then save this file. The page with update to Hello World right before your very eyes…without even having to refresh the browser.

Styling

There are many powerful frameworks out there for styling your React components. We’re going to demo for you one of our favorites: styled-components.

Follow these four steps to use styled-components in your app.

  1. Add styled-components to your ExampleApp/package.json file
  "dependencies": {   
      babel-core": "6.22.1",   
      ...   
      // ADD HERE
      "styled-components": "^1.4.4"   
  },
  1. Install the styled-components package: npm install (from the root folder)
  2. Import styled-components into Home.js and create a style:
  import style from "styled-components";

  const Title = style.h1`
    font-size: 4em;
    color: blue;
  `;
  1. Use this new style in your render method
  render () {
    return (
      <Title>Hello World</Title>
    );
  }

Immediately after you save the file, “Hello World” will turn blue—no refresh needed. The ability to quickly iterate over styles without refreshing the browser is a huge time saver and results in a boost in productivity. Thanks to the array of open source tools we use under the hood with GlueStick, this kind of flow was relatively easy to build and is very helpful.

Generating a Container

GlueStick includes several generators to help boost productivity even further. Given its role in the application development, you will start with the Container generator. GlueStick apps use Redux for managing application-state. When using Redux, components that are directly connected to Redux are referred to as “containers.” These will generally be components that we connect to a route in our router.

Now that the essentials are in place, we’re going to walk you through the creation of an actual application. For this guide, you will build a todo list application. (After all, we ARE trying to boost productivity.) Your first step will be generating a container for your todo list. Executing gluestick generate container Todos will generate the following file:

src/apps/main/containers/Todos.js

  import React, { Component } from "react";
  import { bindActionCreators } from "redux";
  import { connect } from "react-redux";
  import Helmet from "react-helmet";

  export class Todos extends Component {
    static gsBeforeRoute (/* {dispatch}, renderProps, query, serverProps */) {}

    render () {
      return (
        
Todos
); } } export default connect( (/* state */) => ({/** _INSERT_STATE_ **/}), (dispatch) => bindActionCreators({/** _INSERT_ACTION_CREATORS_ **/}, dispatch) )(Todos);

As you can see, this is a simple component that is already hooked up to the Redux store for you. Notice the static method, gsBeforeRoute, gives you a hook whereby you can asynchronously fetch data before your container is rendered. Your server-side request won’t respond until all of the data is filled in so that you don’t end up returning to your app’s loading state for everything. To match this container to a route, we will need to add an entry to the routes file. If you have used React Router, then the routes file will look familiar to you, since we built this into GlueStick already. We have already hooked up the router for you so that you don’t have to mess with it and can just focus on defining your routes.

The routes file is located at src/apps/main/routes.js and it looks like this:

  import React from "react";
  import { Route, IndexRoute } from "react-router";
  import { ROUTE_NAME_404_NOT_FOUND } from "gluestick-shared";

  import MasterLayout from "./components/MasterLayout";
  import HomeApp from "./containers/HomeApp";
  import NoMatchApp from "./containers/NoMatchApp";

  export default function routes (/*store: Object, httpClient: Object*/) {
    return (
      <Route name="app" component={MasterLayout} path="/">
        <IndexRoute name="home" component={HomeApp} />
        <Route name={ROUTE_NAME_404_NOT_FOUND} path="*" component={NoMatchApp} />
      </Route>
    );
  }

Matching your newly created Todos container with a route is simple. First, import your container, then add a nested route with the correct path and component.

  import React from "react";
  import { Route, IndexRoute } from "react-router";
  import { ROUTE_NAME_404_NOT_FOUND } from "gluestick-shared";

  import MasterLayout from "./components/MasterLayout";
  import HomeApp from "./containers/HomeApp";
  import NoMatchApp from "./containers/NoMatchApp";
  // IMPORT HERE
  import Todos from "./containers/Todos.js"

  export default function routes (/*store: Object, httpClient: Object*/) {
    return (
      <Route name="app" component={MasterLayout} path="/">
        <IndexRoute name="home" component={HomeApp} />
        // ROUTE HERE
        <Route name="todos" component={Todos} path="/todos" />
        <Route name={ROUTE_NAME_404_NOT_FOUND} path="*" component={NoMatchApp} />
      </Route>
    );
  }

Save this file then direct your browser to http://localhost:8888/todos and your new container should show up.

Generating a Component

React applications are made up of lots of components. They almost all start with the same few lines of code, so we made a generator to speed things up. We want to encourage developers to write unit tests for their components so we didn’t stop there. Whenever you use the generator to create a new component, it will also create a test file for that component, along with a very basic test to verify that it is rendering without any issues. To generate a new component, simply enter: gluestick generate component TodoList

This will generate two files: src/apps/main/components/TodoList.js

  import React, { Component } from "react";

  export default class TodoList extends Component {
    render () {
      return (
        
TodoList
); } }

and src/apps/main/components/_tests_/TodoList.test.js

  import React from "react";
  import { shallow } from "enzyme";

  import TodoList from "apps/main/components/TodoList";

  describe("apps/main/components/TodoList", () => {
    it("renders without an issue", () => {
      const subject = <TodoList />;
      const wrapper = shallow(subject);
      expect(wrapper).toBeDefined();
    });
  });

As you continue to develop to your application, our GlueStick test suite will automatically re-run your tests. The results from these tests will show up in your terminal as they are made available. Also, you will receive push notifications each time the tests are run to show you whether or not they passed. In order to ensure as complete a testing environment as possible, our test suite is made up of Karma, Mocha, Sinon, and Chai.

Generating a Reducer

GlueStick applications use Redux to manage application-state. Put simply, Redux lets you manage your application-state as a single object that is propagated throughout your app. To change the state, take your current state object and pass it off to reducers that determine what the new state should look like based on a given action. GlueStick hooks up all that boilerplate code for you so that you can get right down to the business of creating the app. To generate a reducer, enter: gluestick generate reducer todos

This will create a new file src/apps/main/reducers/todos.js and modify the existing reducers index file src/apps/main/reducers/index.js. GlueStick looks to src/apps/main/reducers/index.js to know which reducers our Redux store should incorporate.

src/apps/main/reducers/todos.js

  type State = {
    // State handle by your reducer goes here
  }

  const INITIAL_STATE: State = {};

  export default (state: State = INITIAL_STATE, action: { type: string, payload?: any }) => {
    switch (action.type) {
      default:
        return state;
    }
  };

src/apps/main/reducers/index.js

  import todos from "./todos";

  export default {
    todos,
  };

Next, you’ll need to modify your reducer to manage an array, so you can add todo list items to the piece of the state of which this reducer is in charge. For now, just change the initial state from an empty object to an array with two items in it ["First Item", "Second Item"];. Update /src/apps/main/reducers/todos.js as follows

  const INITIAL_STATE: State = ["Thing1", "Thing2"];

  export default (state: State = INITIAL_STATE, action: { type: string, payload?: any }) => {
    switch (action.type) {
      default:
        return state;
    }
  };

Hooking up your data

Now that you have a reducer that is responsible for handling the todos part of your application-state, you can expose that state to the rest of your application through the container. In order to do so, update your todo container to the following:

src/apps/main/containers/Todos.js

  import React, { Component } from "react";
  import { bindActionCreators } from "redux";
  import { connect } from "react-redux";
  import Helmet from "react-helmet";

  // ******* IMPORT `TodoList` HERE *******
  import TodoList from "../components/TodoList";

  export class Todos extends Component {
    static gsBeforeRoute (/* {dispatch}, renderProps, query, serverProps */) {}

    render () {
      return (
        // ******* PASS `todos` to `TodoList` *******
        <TodoList todos={this.props.todos} />
      );
    }
  }

  export default connect(
    // ******* EXPOSE REDUX STATE CONTAINER *******
    (state) => ({todos: state.todos}),
    (dispatch) => bindActionCreators({/** _INSERT_ACTION_CREATORS_ **/}, dispatch)
  )(Todos);

Next, you’ll need to get your TodoList component showing the items on your todo list. Update your TodoList component:

/src/apps/main/components/TodoList.js

import React, { Component } from "react";

    export default class TodoList extends Component {
    render () {
      const todos = this.renderTodos();
      return (
        
{todos}
); } renderTodos() { if (!this.props.todos) return; return this.props.todos.map((todo, index) => { return
{todo}
; }); } } The renderTodos method will return an array of elements containing the todo list text. You’ll assign this array to a variable and expose it in your render function. You will need to refresh the browser for this update to work because our reducer was originally set up with an initial state that wasn’t an array. Hot module loading will not replace an existing state. Once you refresh the browser, you should now see both of your todo list items on the page. Turn off JavaScript Now…to server-side rendering. Try turning off javascript in your web browser and refreshing the page. You’ll notice that you get the exact same result! That is because we render the same page on the server before it is sent to the user. This makes it so that your React applications can be fully search engine optimized. Now turn JavaScript back on and refresh the browser so you can continue building your app. Generate a component for adding todos Now that you set up the rendering of the todo list items, it is time to allow users to add new items to the list. Generate a new component named AddTodo. gluestick generate component AddTodo Once that is created, edit your TodoList component to include it above your list. src/apps/main/components/TodoList.js
  import React, { Component } from "react";

  // ******* IMPORT THE `AddTodo` COMPONENT *******
  import AddTodo from "./AddTodo";

  export default class TodoList extends Component {
    render () {
      const todos = this.renderTodos();
      return (
        
{/******* ADD THE `AddTodo` COMPONENT *******/} {todos}
); } renderTodos() { if (!this.props.todos) return; return this.props.todos.map((todo, index) => { return
{todo}
}); } }

As soon as you save this file, you will see AddTodo show up above your todo list items. You’ll want that to be an input form, so edit the the AddTodo component.

src/apps/main/components/AddTodo.js

/* @flow */

  import React, { Component } from "react";

  export default class AddTodo extends Component {
    render () {
      return (
        
this.input = input} />
); } didSubmit = (e) => { e.preventDefault(); const newItem = this.input.value; this.input.value = ""; // @TODO: send out new item to redux } }

Your AddTodo component will render a form that, when submitted, will call your didSubmit method. You will call e.preventDefault() to prevent the form from doing a traditional form submission that would leave the page. You’ll make your input element available to your methods using the ref property (See React docs for more information on refs). When didSubmit is called, you will capture the value from the input and then clear the form. The next step will be to update your application-state to include this new todo item. Before you can do that, you need to dive into action creators.

Make an action creator

The reducer that you just created determines the new state based on an action that is passed to it. These actions are typically simple objects that include a type property and value and will include any additional data the reducer will need to perform the action. An action for creating a new todo might look like this:

    {
        type: "ADD_TODO",
        text: "Write getting started guide"
    }

Given that typing these objects out every time is inefficient, the typical flow in a Redux application uses “action creators.” These are very simple functions that return action objects. This will give us a nice place to expose the type as a constant for your reducer, as well. You don’t use a generator for action creators because, as you will see, there’s no boilerplate to generate.

Create a new file: src/apps/main/actions/todos.js

  export const ADD_TODO = "ADD_TODO";

  export function addTodo (text) {
    return {
      type: ADD_TODO,
      text: text
    }
  }

Now tell the reducer what to do with this action. Edit src/apps/main/reducers/todos.js

  // ******** IMPORT TODO ACTION **********
  import { ADD_TODO } from "../actions/todos";

  const INITIAL_STATE: State = ["Thing1", "Thing2"];

  export default (state: State = INITIAL_STATE, action: { type: string, payload?: any }) => {
    switch (action.type) {
      // ******** ADD `ADD_TODO` CASE **********
      case ADD_TODO:
        return [action.text, ...state];
      default:
        return state;
    }
  };

First, import your ADD_TODO constant at the top. Then, add a special case to a switch statement for actions of that type. Then, create a new array with the todo text and copy over the old array using the ES2015 spread operator. Note: create a new array; don’t mutate the old array. This is an important requirement of Redux as their documentation states: “For now, just remember that the reducer must be pure. Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.” (See the Redux documentation on Reducers for more information.)

Hook up your action creator

Redux sends actions to the reducers through a dispatch method that is provided for you through export default connect that GlueStick automatically sets up for you. Redux gives you the ability to bind your action creators to the dispatcher with its bindActionCreators method. GlueStick also sets most of this up for you so that you only need to pass your action creators in the place where the code says /** _INSERT_ACTION_CREATORS_ **/.

Update your Todos container so that you can pass your bound addTodo action creator to the AddTodo component.

src/apps/main/containers/Todos.js

  import React, { Component } from "react";
  import { bindActionCreators } from "redux";
  import { connect } from "react-redux";
  import Helmet from "react-helmet";

  import TodoList from "../components/TodoList";
  // ********* IMPORT `addTodo` action *********
  import { addTodo } from "../actions/todos";

  export class Todos extends Component {
    static gsBeforeRoute (/* {dispatch}, renderProps, query, serverProps */) {}

    render () {
      // ********* PASS `addTodo` ACTION CREATOR to `TodoList` *********
      return (
        <TodoList todos={this.props.todos} addTodo={this.props.addTodo} />
      );
    }
  }

  export default connect(
    (state) => ({todos: state.todos}),
    // ********* BIND `addTodo` HERE *********
    (dispatch) => bindActionCreators({addTodo}, dispatch)
  )(Todos);

Now that TodoList has been given the addTodo method, you can continue to pass it to your AddTodo form. Edit the TodoList component like the following:

src/apps/main/components/TodoList.js

  import React, { Component } from "react";

  import AddTodo from "./AddTodo";

  export default class TodoList extends Component {
    render () {
      const todos = this.renderTodos();
      return (
        
{/******* PASS `addTodo` AS A PROP *******/} {todos}
); } renderTodos() { if (!this.props.todos) return; return this.props.todos.map((todo, index) => { return
{todo}
; }); } }

The only line we changed was that we changed <AddToo /> to <AddTodo addTodo={this.props.addTodo} />.

Now, you can finish the AddTodo component by using the addTodo method. Edit the file and replace your // @TODO… comment with this.props.addTodo(newItem);

  import React, { Component } from "react";

  export default class AddTodo extends Component {
    render () {
      return (
        
this.input = input} />
); } didSubmit = (e) => { e.preventDefault(); const newItem = this.input.value; this.input.value = ""; /******* USE `addTodo` HERE *******/ this.props.addTodo(newItem); } }

Asynchronous action creators

If you made it this far, you just built a todo list application using GlueStick and the array of frameworks it glues together for you! Hip Hip Hooray! You’ll notice that refreshing the web browser resets the list to the initial state. That is because you haven’t done anything to persist the data yet. You could keep things in the browser by using LocalStorage or hitting a server API in your action creators.

GlueStick gives you a couple middleware functions for your Redux store: Redux Thunk and our own Promise middleware.

Creating an action creator that needs to hit an API should be done with the Promise middleware. Here is an example of what that would look like:

  export function getTodos () {
      return {
          type: "GET_TODOS",
          promise: new Promise((resolve) => {
              someAsyncMethod((result) => {
                  resolve(result);
              });
          })
      };
  }

The promise middleware can fire off three of its own actions. If your action type is GET_TODOS, then it will first dispatch GET_TODOS_INIT. It is up to you if you want to handle this action or not. This is a good place to update the state to show a loading spinner.

GET_TODOS will only be triggerd once the promise resolves. When you call resolve from inside your promise, any value passed to the resolve method will be available on the action object under as action.value. GET_TODOS_FAILURE will be triggered if the promise fails to resolve. This gives you the chance to notify the user that something went wrong.

Prefetching data with gsBeforeRoute

Earlier, we mentioned the static gsBeforeRoute method. React Router allows you to call a method before rendering a component. In this case, all of the containers have the opportunity to let the server and client know if you need to fetch data before showing the component. To use this method, create an action creator using the Promise middleware and return the result of dispatching that action. Example:

  import React, { Component } from "react";
  import { bindActionCreators } from "redux";
  import { connect } from "react-redux";
  import Helmet from "react-helmet";

  import TodoList from "../components/TodoList";
  import { addTodo } from "../actions/todos";

  export class Todos extends Component {
    static gsBeforeRoute ({dispatch}) {
      // It is important you `return` the result and that you pass the result of
      // `getTodos()`, not the the function itself. Lastly make sure getTodos()
      // returns an action using the promise middleware so the the dispatch method
      // returns a promise.
      return dispatch(getTodos());
    }

    render () {
      return (
        <TodoList todos={this.props.todos} addTodo={this.props.addTodo} />
      );
    }
  }