I have waxed poetic about React here at Chez Scopique, but I’m here now to tell you that it’s not all upsides. In fact, the more I have been working with it, the less enamored with it I become.

The biggest gripe I have about React is also probably one of it’s major strengths, and that is its decentralized nature. React is built around the idea of components, which are building blocks of content that we use on a web page. Consider this page you are looking at right now: At the very top of the page we have elements such as the title banner, the navigation bar, and social media icons. In this post alone we have a title, tags, banner image, author info, the post body, images, sharing icons, related posts, and comments. In the React world each one of those named items would be a component, or a collection of smaller components. Each component should, ideally, be able to operate on it’s own, or at least operate as a child entity of a larger component. In theory this means that a developer can create a component in relative isolation, and can create components that can be re-used throughout the project.

In reality, this ends up as a pile of disconnected, small elements that require either a great memory or the willingness to revisit the same code over and over in leu of proper documentation. Take the example of a button that we define as a component in order to standardize it’s look and feel.

function MyButton(props) {
         <input type="button" value={props.buttonText} onClick={props.handleClick} className={props.buttonState} />

export default MyButton;

Not so bad as far as a simple example goes. How would we go about using this?

import MyButton from './myButton';

...Other stuff...

   <MyButton ??? />

Now, it might be picking nits to say that while the signature of the standard input button is well understood by web developers everywhere, what React has done is to help obfuscate a simple task into something more difficult to work with. In order to actually use this custom button, we have to provide the following:

<MyButton buttontext="Click me!" handleClick=(e=>setMyState("Clicked!")) buttonState="button button-active" />

This sets the text of the button to “Click me!”, sets a state variable myState to “Clicked”, and sets the CSS class of the button to “button” and “button-active”. So unless I have this specific component signature documented in front of me, or I pop open the source of the component and look, how do I know what what aliases to pass into the component in order to assign values to more common attributes of the well-know button element?

This is actually just a precursor to a larger issue I am running into, and that’s keeping all wires uncrossed the larger the project becomes. I was following an excellent example of how to work with React and Firebase. I have written about this before because the article is pretty fantastic. There, the author has the reader creating a components folder in the project, and then has the reader creating about a dozen sub-folders, each of which is responsible for holding the source to a single component or single concern.

I’m having issues actually understanding when and where to use these components, as they all seem to be focused on doing one thing and one thing only — basically the whole point of React. Creating components and plugging them in where they are needed is OK, but since everything is a component in React I’m not sure where the actual page is meant to be defined. Pages in React are — wait for it — just large components which get plugged into the central App component based on routing, which is rendered inside the index page, which is why React Apps are known as SPA: Single Page Applications.

Things are getting even worse when concepts like Context are introduced. A Context is a React construct that consists of a pair of components: a Provider and a Consumer. The Provider is assigned a value which really shines when it’s assigned something large and active, like a class instance or a populated data object. A Consumer is then wrapped around another component in order to pass the value of the Provider to that component.

<MyContext.Provider value={new CustomClass()}>

//Some sub-component somewhere deep in <AllOtherComponents/>
   {custom => <WrappedComponent {...props} customClass={custom} />}

The Provider is assigning an instance of CustomClass to value, and in some other sub-component that is rendered, ultimately, between the Provider tags we call the Consumer. Consumer exposes the value of value as a functional argument which we can use. In this pseudocode our WrappedComponent is receiving the instance of CustomClass defined on the Provider.

Is this correct in regard to WrappedComponent? We don’t know unless we know exactly what WrappedComponent expects, and we expect something somehow. Simply writing a component expecting a prop like CustomClass implies that we need this component to be wrapped in a Context Consumer, which to me is kind of dumb because in that case, we might as well use the React internal useContext hook instead (which is an option I haven’t fully explored, but which comes with it’s own gotchas as far as I can tell). Know that the chances of us needing to wrap many components in the Consumer Context is very high — basically any time we need access to whatever the Context is intended to provide. Writing a Consumer (or Provider) as a higher-order component allows us to make it generic enough to use when we know our child component will need it, and what we wrap in the Consumer might be different every time. A better example would be a Provider which defines an object carrying info on the currently authenticated user. Consumers, then, could be an account modification form component, a logged in/logged out navbar widget, a navigation bar component which displays different nav options based on authentication state, or a post comment component that needs to get access to a user’s name and profile picture. Each one of those examples would be a different component made up of smaller components, yet each one would expect that user data object that wrapping it in a context Consumer would provide. We’d just “have to know” that out of all components in the app, these are the ones that need to be wrapped in the Consumer context and need to receive the data payload assigned to what we hope is a standard attribute name (so we aren’t passing the payload as “userData” on one, and “userInfo” on another).

Don’t even get me started on components which need access to several different Context payloads.

It’s this freewheeling, disconnected and ephemeral connection between components that’s throwing me off. In any app that accepts user sign-up, sign-in, and differentiation of services bases on authentication and authorization, there’s a lot of places where we need to check data. If every element in a React app is a discreet component, where, when, and how many of these cascading quantumly entangled pairs do we need to place? It’s considered bad form to pass data from a parent through several different children, as that messes up reloading components in response to state changes, and of course brings its own share of tracing headaches, so Context is the best of a bad bunch, really.

Yes, someone, somewhere would read all of this and say that if I’m running into these kinds of issues then either my app isn’t structured correctly or I don’t understand any of this well enough. Both are absolutely true. As a result I’ve stepped back from my prototype since I’ve gotten it to a point where many key principals have examples that work, and have written articles at Notion.so deconstructing the code and logic behind them. My goal is to get a better grasp on how to think about working with decoupled components which seems to be my primary hang up. If I can identify a pattern to follow then I can feel more confident that I can deploy the right component when I need to, and that I’m not simply just higher-order component wrappers around every single sub-component just in case I might need access to some data or some object.


Owner and author.

Leave a Reply

Your email address will not be published. Required fields are marked *