React Without useEffect
Tags: webdev, react, frontend, javascript
Published: 2023-04-13 05:38:47

---
title: React Without useEffect
published: true
description: "useEffect is the hardest part of React - and the most error-prone. What if we could build entire React apps without this hook? It's possible - but you need a framework."
tags:
- webdev
- react
- frontend
- javascript
cover_image: https://marmelab.com/static/3dc26a773d3076ba29b70f263fb11274/a3285/large.webp
canonical_url: https://marmelab.com/blog/2023/02/01/react-without-useEffect.html
---

`useEffect` is the hardest part of React - and the most error-prone. Getting a complete understanding of `useEffect` [takes a 49 minutes read](https://overreacted.io/a-complete-guide-to-useeffect/). I've already explained [my concerns about `useEffect`](https://marmelab.com/blog/2022/09/20/react-i-love-you.html#the-butterfly-use-effect) in another article: In my opinion, `useEffect` is the biggest pain point of React.

But what if we could build entire React apps without `useEffect`?

## A Complete React App Without `useEffect`

Check out [this Help Desk application](https://marmelab.com/react-admin-helpdesk/). It's a fully-functional React app that does all the things you'd expect, from ticket search to multi-agent collaboration.

{% embed https://vimeo.com/817162293 %}

It does data fetching, syncs the list filters with the URL, stores user preferences in localStorage, shows real-time notifications, manages content locks, updates data when another user does a mutation, and more. **All without `useEffect`** ([check the source](https://github.com/marmelab/react-admin-helpdesk)). And this is true React - not Solid.js or Svelte. So how does it work?

Of course, there is a trick: this application uses a React framework - in that case, [react-admin](https://marmelab.com/react-admin/). The primary function of React frameworks is to make React easier to use for specific use cases. For React-admin, it's for admin and B2B single-page apps.

My point is: **if you want to build a React app, you should use a framework**. Let's see why.

## Declarative Data Fetching

Take the customer column on the messages list. It has to fetch the customer name for each ticket. How would you write it in pure React?

![customer column](https://marmelab.com/static/c5232d51699351ed79c04c0105aaf0d4/95f20/customer-column.webp)

Here is a basic React implementation:

```jsx
const CustomerNameField = ({ record }) => {
    const [customer, setCustomer] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    useEffect(() => {
        setLoading(true);
        fetch(`${API_URL}/customers/${record.customer_id}`)
            .then(response => {
                setCustomer(response.data);
                setLoading(false);
            })
            .catch(error => {
                setError(error);
                setLoading(false);
            });
    }, [record.customer_id, setCustomer, setLoading, setError]);
    if (loading) {
        return <Loading />;
    }
    if (error) {
        return <Error />;
    }
    if (!customer) {
        return null;
    }
    return <span>{customer.name}</span>;
};
```

If you [look at the code of the HelpDesk app](https://github.com/marmelab/react-admin-helpdesk/blob/321c732babecbbc9964ad08f03dff0b14d923c29/src/tickets/TicketList.tsx#L59), you will see that the actual `CustomerNameField` component is actually much simpler:

```jsx
const CustomerNameField = () => (
    <ReferenceField reference="customers" source="customer_id" />
);
```

This JSX code says "display the related customer whose id is in the `customer_id` field". It's a **declarative** syntax, which means that you ask the program **what** you want to display, and not **how** to fetch and render the data.

Under the hood, [react-admin's `<ReferenceField>` component](https://marmelab.com/react-admin/ReferenceField.html) does use `useEffect` for data fetching, but that's an implementation detail that you don't need to worry about.

And `<ReferenceField>` actually does way more than the initial code. When used in a data table, it aggregates and deduplicates the calls for all the customers in the table. It then sends a single request to the API, with a `WHERE IN` clause.

## Declarative Data Sync

Now, take the lock system. When another user locks a ticket, the current user sees the ticket as locked, and cannot post a reply.

![Ticket locked](https://marmelab.com/static/ad7aeb14df0e4150dd765af8afb077b3/95f20/locked-by.webp)

To implement that in pure react, the `<TicketActivity>` component has to fetch the lock on mount, subscribe to the lock changes, update the lock state when the lock changes, and unsubscribe when the component is unmounted:

```jsx
const TicketActivity = ({ record }) => {
    const [lock, setLock] = useState(null);

    useEffect(() => {
        setLoading(true);
        fetch(`${API_URL}/locks/tickets/${record.id}`).then(response => {
            setLock(response.data);
        });
    }, [record.id, setLock, setLoading, setError]);

    useEffect(() => {
        const subscription = subscribeTo(
            `locks/tickets/${record.id}`,
            event => {
                if (event.data.type === 'locked') {
                    setLock(event.data.payload);
                }
                if (event.data.type === 'unlocked') {
                    setLock();
                }
            },
        );
        return () => subscription.unsubscribe();
    }, [record.id, setLock]);

    if (!lock) return null;
    return (
        <Typography variant="body2">
            Locked by
            <ReferenceField
                record={lock}
                source="identity"
                reference="agents"
            />
        </Typography>
    );
};
```

That's two `useEffect` calls, and a lot of code - I've even left out the loading/error states. Now look at [the `<TicketActivity>` implementation in the HelpDesk app](https://github.com/marmelab/react-admin-helpdesk/blob/321c732babecbbc9964ad08f03dff0b14d923c29/src/tickets/ActivityDetail.tsx#L10). It looks like this:

```jsx
const TicketActivity = () => {
    const { data: lock } = useGetLockLive('tickets');
    if (!lock) return null;
    return (
        <Typography variant="body2">
            Locked by
            <ReferenceField
                record={lock}
                source="identity"
                reference="agents"
            />
        </Typography>
    );
};
```

[The `useGetLockLive` hook](https://marmelab.com/react-admin/useGetLockLive.html) says: "I want to get the lock for the current ticket, and I want it to be refreshed in real-time when the lock changes in the backend". Again, it's declarative: _how_ this is done is not important, or rather, it's left to react-admin.

This time, react-admin replaces calls to `useEffect` not by a component, but by another custom hook. Yet this hook has no dependency array, and the developer doesn't have to worry about rerenders, cleanup on unmount, and all that plumbing. The custom hook hides the complexity behind a task-oriented API.

The Help Desk app contains tons of components that would normally require many `useEffect` calls, but that use higher-level, business-oriented APIs. One exception: [the `useAddReadToTicket` hook](https://github.com/marmelab/react-admin-helpdesk/blob/ddf84b648d502b2cc8370b93094360050d6c61ee/src/tickets/useAddReaderToTicket.ts) uses `useAsyncEffect` to create a new record on mount. This hook is a `useEffect` wrapper that allows asynchronous code (and [is explained in a previous post](/blog/2023/01/11/use-async-effect-react.html)). But that's it.

## But That Doesn't Work In My Case

You may say: "That declarative syntax works for simple cases, but I often have to use `useEffect` and I see no way around it". Well, we've built many React apps (including an [e-commerce admin](https://github.com/marmelab/react-admin/tree/master/examples/demo) and a [CRM](https://github.com/marmelab/react-admin/tree/master/examples/crm)) that don't call `useEffect` even once. So yes, it's possible.

{% embed https://vimeo.com/817165270 %}

There is a price to pay, of course: You have to learn The Framework Way™ to handle relationships, search & filtering, roles & permissions, Pub/Sub, user preferences, etc. instead of doing it your way. Before becoming more efficient, you'll have to develop at a slower pace.

And sometimes, the framework gets in the way and doesn't let you do exactly what you want. In those cases, you'll fall back to pure React, and you'll probably have to use `useEffect` directly. I do too, sometimes, and the pain it brings makes me wish react-admin had a declarative API for that use case. Using a framework greatly reduces the amount of imperative code you have to write, but it doesn't eliminate it.

## Conclusion

React is supposed to [favor declarative code over imperative code](https://alexsidorenko.com/blog/react-is-declarative-what-does-it-mean/). But since its API is tiny, many features require imperative boilerplate, with the help of `useEffect`. It's as if React were missing a layer to really let developers write declarative code in most use cases.

Libraries and frameworks fill that gap. [React-query](https://react-query.tanstack.com/), [react-hook-form](https://react-hook-form.com/), [react-router](https://reacttraining.com/react-router/), [MUI](https://mui.com/), and [emotion](https://emotion.sh/docs/introduction) use `useEffect` so that you don't have to. [React-admin](https://marmelab.com/react-admin/) builds up on these libraries, and exposes higher-level components and hooks to let you build complex, declarative React apps free of `useEffect`.

Like a CRUD app in 13 lines of code:

```jsx
import React from 'react';
import { Admin, Resource, ListGuesser } from 'react-admin';
import simpleRestProvider from 'ra-data-simple-rest';

const dataProvider = simpleRestProvider('https://domain.tld/api');

const App = () => (
    <Admin dataProvider={dataProvider}>
        <Resource name="users" list={ListGuesser} />
    </Admin>
);

export default App;
```

Whether you are a new React developer or a seasoned one, `useEffect` is probably your biggest productivity killer. Don't let it get in the way of your creativity. Use a React framework. And if you don't know which one to choose, [give react-admin a try](https://marmelab.com/react-admin/).