Final Countdown

Posted by Paul Ashour on September 16, 2019

The part where I make a final project

Conception

The idea here was really to make something simple to make sure that I had time to get all the underlying mechanics correct. So I literally set up some Rails models called “adjectives”, “nouns”, and “concepts”. The former two had a “name” string and the latter was just a join model for a has_many through relation with a method to display the combined string. I meant it to be a practice, but it still ended up taking all my time.

The finished project (available now at [https://legacy-chronicler.herokuapp.com/checklist]!) simply has a couple of tabs, one of which allows you to add Adjectives and Nouns, and the other of which allows you to combine them into Concepts.

Execution

A fun thing I got to do here was figure out dynamic ‘active’ items in a list. Within the component that handles Concept concepts, the lists of Adjectives and Nouns are shown just as in the Adjective and Noun component. However, here they are selectable for the purpose of creating new Concepts. This is done visually with bootstrap; all the lists of concepts are presented as <ListGroup.Item> components, and the active styling works well as an indicator. But how to make exactly one active when clicked? I ended up storing an active_adjective and active_noun integer in the container component’s state and passed down the appropriate one to each Adjective and Noun concept component in the tab. Each of those also got a handleSelect function passed as a prop from the same container which would pass its id back up to the container state and immediately change which one was active. The only issue with that is the reusability of the component for concepts. Not all concepts needed to be looking for these extra props, so the component got a quick ternary variable:

  const options = props.handleSelect ?
    {
      action: true,
      active: props.selected === props.concept.id,
      onClick: () => props.handleSelect(props.concept.id)
    } :
    {}
	```
	
Which is rolled into the render like so:

return( <ListGroup.Item {…options}> {props.concept.name} </ListGroup.Item> ) ```

While error handling is not particularly robust here, it is useful that functional components won't complain about props they don't have. It allows me to have a very small component that still can handle multiple use cases, here depending on whether or not I pass in a `handleSelect`. No selection handler? No need for the `action` flag that makes the concept clickable. This is especially nice because it prevents all the other concept lists from appearing like clicking on them should do something.

Problems

My truly biggest pain was deploying to Heroku. An early attempt to get the project up and running there ended up completely screwing up my configuration even when serving the local development environment, and I had to start from scratch. Now that it is running there, here are some things I have learned from the process:

Package Managers

  • Nobody likes npm anymore. Yarn is the way to go - so don’t npm install the React client’s dependencies because the Heroku build process will complain when it tries to yarn install and sees your package-lock.json.
  • You should have two package.json files, one in your Rails root and another in your React root. If you add dependencies, you almost certainly want them in the React client list, so add them to that file. When you add dependencies, you should do that with yarn add, in which case you should not forget to go to the correct directory or use yarn add --cwd YOUR_REACT_CLIENT_DIRECTORY
  • The ideal setup for a Rails/Webpacker app does not involve any sort of separate node process in production, even though development is easiest if you’re running webpack-dev-server so that you can immediately see changes. Just have Heroku run Rails, and test that that works locally by running ./bin/rails server -e production
  • Rails does not want to assume that it’ll be serving your assets in production. In /config/environments/production.rb, you’ll see that there is a setting to “Disable serving static files from the /public folder by default since Apache or NGINX already handles this”. On Heroku, we don’t have that kind of cool server setup. Set config.public_file_server.enabled = true
  • Finally, Github also assumes that you have a cool server setup, and so the autogenerated Rails .gitignore file ignores the /public assets folders. This gives Heroku mixed messages when your webpacker.yml production values have the default cache_manifest:true, since the manifest.json will be gitignored out. Even if you set this to false (which is not great for performance) Heroku seems to struggle precompiling your assets. So just be nice to Heroku! Precompile your assets with RAILS_ENV=production bundle exec rake assets:precompile and remove public/assets and public/packs from your .gitignore. At time of writing, Heroku automatically runs rake assets:precompile only if it fails to find a manifest.json. Now it should see your precompiled manifest, and simply use the assets that are now in your repository.

Dumb JS mistakes

  • A reference without an action is a valid line of code, and event.preventDefault without the parentheses is just a reference that will not stop the page from refreshing when you submit your form.