Architecting React Components

The requirements for the project entailed three new pages, each including a collapsible options panel at the top of the page and some tabular data at the bottom. The data at the bottom of the page are determined by the options at the top and loading is triggered either automatically (when certain fields change) or manually (by hitting the “Refresh” button). Following the discussion in the previous sections, the functionality for each new page would be wrapped in a React app with a corresponding entry point in the webpack configuration. A JQuery-based version of the options panel had already been in use on the existing pages of the application, but we rewrote it as a React component in order to use it on the new pages. The re-written options panel (in its expanded form) can be seen in the screenshot below:

Since the options panel is used by the other pages, it was placed in the components directory (the entire layout of the React\src directory can be seen in the below screenshot:

The options panel is implemented in the optionsPanelTemplate.js file and the corresponding OptionsPanelTemplate class. The reason for the “Template” suffix in the component’s name will be soon made clear. The rest of the components in the components directory are used only by the options panel (directly or indirectly) and are not imported by any of the page components. The entry points for the three pages are implemented in the corresponding index.js file of the contentProfiles, dailyActivities and keyDates directories.

In structuring the React components I took into account the following requirements:

  • Options panel state should be kept inside the options panel component. Doing the reverse (i.e. lifting state up to the page components) would require a lot of state and event management boilerplate code to be duplicated for each page.
  • Page components should be able to access the state of the options panel, so that they know what information they need to display.

Coming from an object-oriented background, my first impulse was to host an options panel in a page component and directly access the state of the options panel from the page component. Before reaching to the server for new data, the page component would read the state from the options panel (a child component) and issue an appropriate request to the back-end. Implementing this idea would be very simple by using a ref to the options panel and accessing its state, but this approach seems to be highly discouraged by the React community. I am guessing things could become convoluted if the parent component needed to be notified about when the state of the child component changed. By lifting state up, the state and the changing of the state take place in the same component keeping everything nice and tidy, while child components are re-rendered with the new state passed as a prop to them. However, as mentioned above, lifting the state up to the page component was not an option for us, as it would require all this boilerplate code to be copied to each page. It is probably in situations like this where the need for a state management library such as Redux or Flux becomes more evident.

In any case, I ended up using the concept of render props and converted the options panel from a standalone component to a template which dynamically renders a child component passed to it as a prop. In fact, the prop contains a function that accepts the state of the parent component and returns a child component with the state of the parent passed to it as a prop (brain twisting but makes sense). Whenever the state of the options panel is modified, the child component (one of the three tabular data constructs) is scheduled for re-rendering and, thus, correctly updated with the new information. I cheated a bit, and also passed a function to be called by the child component under certain circumstances, but I think I did not significantly deviated from the spirit of React.

If you are interested in understanding how the code works check the mouse and cat example at the link in the beginning of this paragraph. The code I ended up using in the index.js of one of the three pages is the following:

ReactDOM.render(
    <OptionsPanelTemplate profilesUse render={(optionsPanelState, onLoadClick) =>
        (<ContentProfiles optionsPanelState={optionsPanelState} onLoadClick={onLoadClick} />)}
        showPanelButton={false} showEquipmentTree showStaffTree
    />,
    document.getElementById("root")
);