ARTICLES & RESOURCES

Container Components are Dead

Long Live Containers?

by Taivara, Technology Innovation

Container Components

For a long time, we’ve made use of the Container Component pattern within our React apps. It’s been a grand old pattern and we’ve gotten a ton of mileage out of it.

If you’re not already familiar with the Container Component pattern, I highly recommend reading this article by Dan Abramov.  Mr. Abramov provides greater depth around the pattern than we’ll get into here — and even offers hints around why we no longer need to rely on the pattern quite so heavily.

In any case, here is a quick summary of the pattern (paraphrased from our linked article). Presentational components are concerned with how things look (i.e. UI logic). Container components are concerned with how things work (i.e. Business Logic).

Breaking your features up into these two types of components can help with:

  • Better separation of concerns.
  • Better component reusability.
  • Updates to look-and-feel without changes to logic.

Sounds fantastic! What’s the problem??

Well, in the wild, we’ve found that it can be difficult to draw a neat line between “business logic” and “UI logic”. Sometimes you just get a big, fat, fuzzy, smudgy line that makes the distinction feel awfully arbitrary. More on this later.

Also, React Hooks!!

React Hooks

Paraphrasing from the official React docs, hooks are functions that allow you to encapsulate and reuse stateful logic. Really though, while the “stateful” piece is incredibly powerful, it’s also completely optional. At the most basic level, you can just think of hooks as containers for the various logical concerns of a given component. 

Consider the benefits of extracting our component logic into hooks, as opposed to using a container component.

  • Even better separation of concerns. We’re now free to group our logic in whichever manner makes the most sense. If the distinction between business/UI logic is material in your case, great! If not, also great! At the end of day, your hooks don’t care about how you choose to categorize the logic they hold.
  • Equivalent component reusability. Let’s be clear here, presentational components are not going anywhere. You are still free (encouraged, in fact!) to extract reusable presentational components wherever it makes sense.
  • Equivalent updates to look-and-feel without changes to logic. The logic is just housed in hooks, instead of the container component.

An Example

Let’s make this all more concrete with code!

We’ll build out a simple form that saves names to some backend. It leverages internationalization and allows the user to toggle between “light mode” and “dark mode”.

Here’s the simplified pseudo-code for our feature, using the Container pattern.

const SimpleFormContainer = () => {

 // Create our form schema. Includes validation details.

 const schema = createSchema({ name: required });


 // Create callback to save name on our server.

 const saveName = useNameSaver();

 const onSubmit = useCallback(values => saveName(values), [saveName])


 return (

 <SimpleFormView schema={schema} onSubmit={onSubmit} />

 )

};


const SimpleFormView = (props) => {

 const { schema, onSubmit} = props;


 const theme = useTheme(); // Theme is toggleable elsewhere.

 const t = useI18n();      // Folks 'round the world use our magnificent form.


 return (

 <Form onSubmit={onSubmit} theme={theme}>

 <Field name={schema.firstName}>

 {({ name, value, error }) => (

 <>

 <Label>{t('firstNameLabel')}</Label>

 <Field value={value} />

 { error ? <FieldError>{error}</FieldError> : null }

 </>

 )}

 </Field>


 <Button type="submit">{t('submitButton')}</Button>

 </Form>

 );

};

Now. We notice that creating our schema and onSubmit callback are really concerned with the same thing: making our form work. Let’s go ahead and pull that form logic into its own concern via custom hook.

Here is what our feature looks like now.

const SimpleFormContainer = () => {

 const { schema, onSubmit } = useSimpleFormLogic();


 return (

 <SimpleFormView schema={schema} onSubmit={onSubmit} />

 )

};


const SimpleFormView = (props) => {

 const { schema, onSubmit} = props;


 const theme = useTheme(); // Theme is toggleable elsewhere.

 const t = useI18n();      // Folks 'round the world use our magnificent form.


 return (

 <Form onSubmit={onSubmit} theme={theme}>

 <Field name={schema.firstName}>

 {({ name, value, error }) => (

 <>

 <Label>{t('firstNameLabel')}</Label>

 <Field value={value} />

 { error ? <FieldError>{error}</FieldError> : null }

 </>

 )}

 </Field>


 <Button type="submit"}>{t('submitButton')}</Button>

 </Form>

 );

};

For me, this little refactor has the effect of making it perfectly obvious that, as a whole, our SimpleForm feature has three concerns:

  1. Form logic
  2. Theming logic
  3. Internationalization logic.

We’ve decided that our Form Logic is “business logic” and belongs in our container component; Theming & Internationalization logic are classified as “UI Logic” and placed in our presentational component. Fine — but why don’t we indulge and bikeshed a little :p ?

What if, under the hood, our i18n hook is pulling from an external source to inform content decisions? What if that relationship becomes more complex and begins to include other business entities or application configuration? Is the logic still purely presentational? Honestly, I don’t know.

Regardless of where we land and draw our fat, fuzzy, smudgy line, our `SimpleFormContainer` component does not seem to offer any benefits other than, well, an imperfect delineation between business and presentational logic. At this point, we are left with dogmatic adherence to a programming pattern without purpose.

Ew. Let’s get rid of it!

const SimpleFormFeature = () => {

 const { schema, onSubmit } = useSimpleFormLogic();

 const theme = useTheme(); // Theme is toggeable elsewhere.

 const t = useI18n();      // Folks 'round the world use our magnificent form.


 return (

 <Form onSubmit={onSubmit} theme={theme}>

 <Field name={schema.firstName}>

 {({ name, value, error }) => (

 <>

 <Label>{t('firstNameLabel')}</Label>

 <Field value={value} />

 { error ? <FieldError>{error}</FieldError> : null }

 </>

 )}

 </Field>


 <Button type="submit"}>{t('submitButton')}</Button>

 </Form>

 );

};

Ta-da! We now have a single “Feature” component whose various logical concerns (whether purely business/presentational, or otherwise) are sensibly encapsulated via custom hooks.

Conclusion

Hooks provide all of the benefits of containers, but with better encapsulation/abstraction and no arbitrary division between business logic and UI logic. Therefore:

Container components are dead! Long live containers (as custom hooks)!

Want to learn more about Software Development? Check out our article on Managing a Remote Software Development Team for more information on Development and Design, one of the 8-Key Elements of Digital Product Co-Creation.

Want to be more informed on Software Development?

Let us help you design and develop more digital products.

Share This