React On Rails

To allow Rails to serve our React applications, we use this library:

https://github.com/shakacode/react_on_rails

The doc is quite complete, and the “Getting started” section shows you how this library is used and how it handles React components:

State properties

In a Rails view, you can simply call an already defined component like this:

As you can see, this Rails view will serve the HelloWorld component, and it will give it some properties (in this example, the JSON { authenticity_token: “XYZ” }).

To access those props everywhere in your React application, without having to pass them from parent components to children components, we also use the react-redux library.

In our example, this is how the HelloWorld component could look:

As you can see, the HelloWorld component receives 2 properties: props & railsContext.

We then use both to create the state appStore(Object.assign(railsContext, props)) and assign it to the store property of the Provider.

Later, in any other children components that HelloWorld uses (in our example, Header or HelloWorldPage), we can access the state like this:

Where useSelector is also imported from react-redux.

So, if you see this line in our codebase:

That means that the preferred locale is given by Rails as a property, so it’s the role of Rails to provide the correct preferred locale.


File Structure

In Squarehub, we don’t use Rails to serve simple components, we use it to serve whole entire React apps (they still qualify as components, but let’s see how they differ).

See how we handle the RecruiterApp:

Let’s breakdown the 3 attributes we give to the react_component function:

  • “RecruiterApp” ⇒ a String representing the name of the React component it should serve

  • props: {} ⇒ the properties we want to pass onto our React component

We’ve already covered the props, and the prerender: false is a default for all React applications in the Squarehub repo, while in the Sander repo both are used. This topic will be covered more in-depth in the Sander section.

So let’s focus on the “RecruiterApp” part and dive more in the the structure.

RecruiterApp

RecruiterApp is the React component that will encapsulate all other components & services in a single place. Let’s see it’s implementation in our codebase:

As we saw above, it receives props & railsContext from the back-end. First thing it renders is the <Provider> in which we register the state with react-redux.

Then, we have the <SnackbarProvider> which is another library we use to display small snackbar messages. By registering it here, we make it available in all children components as well (kinda similar to to the redux store).

Following this comes the <SquarehubTheme>, which is an in-house component that links our theme.js file to the MaterialUI theme. Again, by registering it here, it becomes available in all children components.

Finally, after all those “providers” comes the second part: the container.

RecruiterContainer

RecruiterContainer is mainly used as router for the RecruiterApp. There is also some other providers initialization, that we’ll cover here.

Let’s have a look at a simplified version of our RecruiterContainer:

  • Imports:

    • React + used libraries (redux, react-router-dom for the routing & react-intl for the preferred locale)

    • Translations given to react-intl

  • Attributes:

    • currentRecruiter & i18nlocale initialized via the redux state

    • dispatch to trigger an update (used in updateCurrentRecruiter)

And with that we’ve got everything covered for the file structure! Now that you have your routing system, you can simply create new components and assign them to new routes. Those big components can then themselves call smaller components and so on.


Routing

Application routing

All routes are first handled by Rails. When you navigate to this route /fr/recruiter/job_offer_creation, Rails will look up for a match in the routes.rb file.

If you search for “job_offer_creation” in this file, you won’t find anything though, that’s because every recruiter routes matches this line:

In this case, /fr/recruiter/job_offer_creation matches so Rails will transfer the request to the RecruiterController on the index function.

This function is quite empty:

And it contains no return statement. A controller function will always implicitly render its linked view if there is no explicit return. So, in this case, Rails will serve the index-recruiter.html.erb view. Let’s have a look at a simplified version of this view:

Wait, we’ve already seen that! Indeed, in this Rails view we explicitly state that it should render the RecruiterApp component.

So now, we continue our flow: from RecruiterApp, we go to RecruiterContainer which handles the internal react routing. The URL will be tested against our available routes there, and in this example will match:

And we’ll finally land in the JobOfferCreation component, which is where the URL was supposed to bring us.

API Routing

Next to the Application Routing, we also use routes that interacts with our backend as an API.

Let’s continue our above example. We’ve now successfully landed in our <JobOfferCreation>.

Now, imagine you want to fetch the credit linked to the current recruiter (let’s suppose we don’t have that information in the redux state).

To achieve that, you might want to define a new route: recruiter/get-credit.

You’ll need to define it in the routes.rb file:

This states that the new route will point to the CreditController onto the get_current_credit function. Now, let’s imagine the implementation of this function:

In this controller, there is an explicit return: render json: { credit.as_json }. It means that it returns some data in JSON (in this case, the serialization of the credit object).

That’s the main difference with application routing: this one will always render views while API routing will render data.


Oof! That was a heavy one, but we’ve got most of our architecture covered. Let’s take a break now, or jump directly in the next section:

Last updated