We are taking a slight detour in our Building with React & Flux series to show how straight forward it is to write Single Page Applications (SPA) with React and Reflux for Salesforce. If you are just hopping into this series then definitely check out the Building with React & Flux: Getting Started to get up to speed on React and Flux.
See the video at the bottom for a complete demo or grab the code from github.
The application isn't a game-changer and aims to demonstrate the basics of React and Reflux on Force.com. It consists of the following:
- The 'home' page shows a simple table of banners from the Banner__c custom object.
- An HTML form to add a new banner that is of course inserted into the Banner__c custom object.
- A page that displays a banner and toggles its display status. Of course this updates the actual record in the custom object.
Intro to Reflux
Before we get started let's take a look at RefluxJS, one of the many Flux implementations. In general, I found Flux difficult to grok and apparently I'm not alone. I read the FB docs, waded through various blog posts (Flux For Stupid People and Getting to Know Flux) and even watched all of the egghead.io React and Flux videos but I honestly didn't like it. Luckily I stumbled across React.js architecture - Flux VS Reflux and it spoke to me. While Flux seemed architecturally complex and bloated with boilerplate code, Reflux seemed streamlined and succinct. I like its simplicity and use of mixins to add functionality. It drastically simplified the process of listening to changes in stores, made working with actions easier and less verbose and got rid of the Dispatcher entirely!! If you are interested in a comparison of the two then take some time to check out this blog post for a complete rundown.
We are going to the existing code from the previous post and modify it to run inside a Visualforce page. We'll use some of the same tools as last time but with a few tweaks. You won't be able to run the application locally but we will still minify our assets using gulp before upload them as static resources in Salesforce. First, clone the following repo to grab the code:
Then install all of the dependencies for the project defined in package.json:
The only real config we need to do is to create the following Banner__c custom object and expose a tab for it (if you'd like).
gulp watch so run that at the command like:
You should now see a couple of folders appear in the
/dist directory. Zip up these
styles folders and create a Static Resource in Salesforce named
Since Visualforce is hosting our SPA, Banners.page is the only page that we'll need. It's mostly bootstrap markup with navigation, a title and a div
(id="app") where React will render our application. Notice the script and style tags that reference the
banners static resource we just uploaded.
Perhaps the most important part of the Visualforce page are the apex tags at the top where we add the Remote Objects that the application will use. We define our remoteObjectModel and specify that it will proxy for the
The app.js file is almost identical to the previous version. It sets up our application, defines the routes and renders the app. For our application we'll be using react-router which provides... wait for it... wait for it... routing for our application. The
routes variable defines the view hierarchy for our application. We then declare a view hierarchy with nested
s and provide them with a React element to handle the route when it's active.
The React component simply renders the
component that, in turn, renders the currently active child route for the application. The last section,
Router.run, is somewhat magical. When the user selects a route, the
run callback receives
Handler, that has all of its appropriate information wrapped up in it. If our app would be managing some type of state, we could pass this state down the view hierarchy. For our use case, we simply use the standard boilerplate and render our application into the
The heart of the application is the Reflux store which holds all of the model, business logic and interactions with the Force.com platform. Here is the complete code. The store holds a private
data object which the users interact with through various calls from components (e.g., getBanners(), getBanner(id), etc.). The store loads this initial state as an empty array of banners.
init function we are using the
Banner remote object to query the Banner__c custom object for up to 5 records. In the callback we create a banner object with simpler properties and push each one to the state's array of banners. Then we trigger the DOM update to display the records in the HTML table. We also register some actions (
addBanner) that the store listens for and binds them to individual function that handle the appropriate logic.
addBanner function is called whenever the store hears the
addBanner action (i.e., the user submitted the add banner form). We use the remote object again and create a new record in the Banner__c custom object using the details from the banner object passed to the function. If the record was inserted correctly, it pushes the new banner object to the array of banners in the state and updates the DOM to display the new row in the HTML table. Magic!
toggle function is called whenever the store hears a
toggleStatus action. This function fetches the appropriate banner in the state array by its ID and then toggles its
active property. The remote object is then used to update the record in the custom object. The trigger method passes the change notification to any listeners to update the DOM.
The action.js, since it uses Reflux, is much smaller and simpler than the standard Flux Dispatcher. It simply defines the actions that our app will broadcast. Not much to see here. Simplicity is good!
This view component is responsible for displaying our table of banner data. When initialized, the
getInitialState method is called and loads the banner data from the store into the state. When the component renders, it first creates a variable of
rows that is used to display the actual data in the table rows.
Note: there is a slight bug I ran across in the Reflux store when calling code asynchronously which seems to loose the reference to its state. It works fine when called synchronously, but doesn't render properly otherwise. The code I used ensures that the component listens to changes in the store and updates its state appropriately as a workaround.
The view.js file is pretty interesting and has a lot going on. First, the class uses an array of mixins to add functionality to the component. When the component initially mounts, is uses Reflux's
ListenerMixin to listen for changes in the BannerStore and act accordingly. The
getInitialState method grabs the ID of the banner being viewed and calls the BannerStores'
getBanner method and adds it to the state. When the component renders, it displays this state data on the page.
There is also a 'Toggle Active' button that, when clicked, broadcasts
actions.toggleStatus and passes the ID of the banner. The BannerStore is responsible for toggling the Yes/No status of the banner and then notifies any listeners that there has been a change. This view component listens for any change to the BannerStore and then calls
toggleStatus to change the state and update the DOM.
Our final component displays a form for entering a new banner. IMHO it takes more work that I think it should. I tried to implement a couple of libraries such as react-froms and react-bootstrap but had much better luck rolling my own for this simple application.
When the component initializes,
getInitialState sets up the state with default error and banner objects. The errors object be will used to notify the user that a field is required when submitting while the banner object will default in some data to the form fields.
When the component renders it calls
renderTextInput for each of the three form fields. This adds the appropriate HTML to the DOM to make the field look pretty and sets up any error notifications when the form is submitted. The value of the form field is bound to the banner in the state and fires the
handleChange event whenever the user changes the text (i.e., typing). The
handleChange function updates the state which re-renders the DOM node for the form field.
When the user clicks the submit button, the form's
onSubmit handler calls the
handleSubmit function which check to make sure all fields are filled out. If a required field is blank, it adds this to the state's error object which display the field in a red box. If everything is filled out correctly, it calls the BannerStore's
addBanner method with the new banner data, resets the component's state and display the home page which show the newly added banner in the table.
So there you have it, a minimal SPA written in React and Reflux on Force.com that you can use as a starter for your own project!