This content originally appeared on Level Up Coding - Medium and was authored by Pierre G.
Your React App Isn’t Great — Fix It With Controllers.
Leverage the Power of Controllers and Make Your React Apps Invincible.
As your colleagues struggled with debugging overly coupled code, I shared some tips to help you create React apps they’ll love (read this through if you haven’t yet). I hope you’re not too overwhelmed with congratulations and romantic messages — great code, after all, can evoke strong emotions.
Technical Discovery
Let’s explore controllers a bit further through a classic example of a frontend application — you guessed it: the To-Do List 📝. Keep it simple.

So far, nothing extraordinary. How about we break down the UI into view components?

Here we are. Now that we have a clear breakdown of the UI, we need to define controllers and their responsibilities — basically, what interfaces will be exposed to the view components (and other controllers).

- ErrorController [stateful]: Handles the error state — a simple string visible by end users. Most controllers depend on it.
- CreateItem [stateful]: Handles changes made by end users through the form. It also makes API calls to create a new item—that's why it depends on the API client.
- ListItems [stateful]: Fetches the items of the current user and stores them in a React state. It exposes a refresh() function that the CreateItem controller uses to update items after a successful creation.
- DeleteItem [stateless]: Handles clicks on delete buttons.
On top of these controllers:
- A simple sanitizer that allows for removing extra characters, like spaces at the start and end of input strings. To ease unit testing, it is isolated in a dedicated module.
- An API client exposing asynchronous functions to create, read, and delete To-Do items.
Note: If you plan to create a real Todo for real users (and aim for lofty ambitions with it), you should also add a loader. To keep the example concise, I’ve skipped it, but the principle remains the same.
You could also add validators with feedback messages while the user is typing, along with all sorts of lovely features to improve the UX.
This approach strongly decouples the UI frame from the controlling logic. You’ll be able to update views by extracting DOM nodes and inserting new components as much as you want, without even thinking about how to access data and handle user actions. Consequently, connecting views with data becomes an easy game 💪.
Additionally, testing the controlling logic separately from the UI presents significant benefits by reducing the complexity of unit tests.
Implementation
Ok, we now have a clear overview of how to implement our app: it’s time to code.
Let’s dive into the CreateItem controller. How will it look?
import React, { createContext, useContext, useState, ReactNode } from 'react'
import { useErrorController } from './errorController'
import { todoClient } from '../clients/todo'
import { sanitizeFormInput } from '../sanitizers/strings'
interface CreateItemType {
onChange: (itemValue: string) => void
createItem: () => Promise<void>
}
interface CreateItemProviderProps {
children: ReactNode
}
const CreateItem = createContext<CreateItemType | null>(null)
export const CreateItemProvider: React.FC<CreateItemProviderProps> = ({
children,
}) => {
const { displayError } = useErrorController()
const [itemValue, setItemValue] = useState<string>('')
const createItem = async () => {
try {
const sanitizedValue = sanitizeFormInput(itemValue)
await todoClient.createItem(sanitizedValue)
} catch (err) {
displayError(err)
}
}
const onChange = (value: string) {
setItemValue(value)
}
const value: CreateItemType = {
createItem,
onChange
}
return (
<CreateItem.Provider value={value}>
{children}
</CreateItem.Provider>
)
}
export const useCreateItem = (): CreateItemType => {
const context = useContext(CreateItem)
if (!context) {
throw new Error('useCreateItem must be used within a CreateItemProvider')
}
return context
}
The file createItem.tsx contains:
- A React context provider, CreateItemProvider which embeds the controlling logic and exposes the controller interface, i.e., two functions: onChange(), which updates the state of the form input, and createItem(), which triggers API calls.
- A React hook,useCreateItem(), eases access to the context. It can be consumed as follows within view components (or other controllers):
const Form = () => {
const { onChange, createItem } = useCreateItem()
// View component logic
}
How to connect view components with controllers?
For a given view component to consume a given controller, the view component must be a child of the controller provider.
To clarify dependencies and ease data access, I recommend instantiating all controller providers within the same file at the top of the page. This way, controllers can be accessed by any view component.
Ultimately, if you face performance issues, you can adjust the approach and refine the hierarchy.
const TodoApp = () => {
// Mount controllers providers to make them accessible from
// any view component.
// Page is the root of view components.
return (
<ErrorController>
<ListItems>
<CreateItem>
<DeleteItem>
<Page />
</DeleteItem>
</CreateItem>
</ListItems>
</ErrorController>
)
}
Important note: when calling providers ensure the order matches the dependencies between controllers.
And that’s it! This is a way to build robust controllers with React native tools. I hope you enjoyed this article. Let me know if you’d like to dive deeper into certain aspects.
Thanks for reading!
Your React App Isn’t Great — Fix It With Controllers. was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Pierre G.

Pierre G. | Sciencx (2025-01-19T20:28:25+00:00) Your React App Isn’t Great — Fix It With Controllers.. Retrieved from https://www.scien.cx/2025/01/19/your-react-app-isnt-great-fix-it-with-controllers/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.