React asynchronous render with built-in callbacks (a proposal)
Tags: react, frontend, discuss, opensource
Published: 2023-07-01 15:17:40

The most consistent way to deal with side effects is to define them in event handlers. This is how you keep the logic consistent and you can define state changes correctly without going into weird re-renders.

The main problem now in react is due to use useEffect for getting asynchronous resources. useEffect is known for triggering infinite loops in react components and make renders inefficient. This is because we're dealing with a side effect during render and the current implementation is messing with react internal render logic. 

_useEffect_ was build for handling subscriptions. When you subscribe to a external store, you expect events to be dispatch with certain order and not to have an infinite loop. However, react didn't provide a way to solve the fetch of external resources when component mounts and so developers start using this hook as a solution to go.

So the issue we want to solve here is how to get a resource in a component asynchronously without useEffect. 

Let's start with an easier situation. How do we get resources for the current component during an event handler. Let's take a click event.

In this case you would define the fetch **in the click handler** so you could deal with all state changes (loading, error, success) during all the asynchronous events that a fetch goes through.

```jsx
function App () {
	const [status, setStatus] = useState('idle')
	return (
	<div>
		{status}
		<Button
			onClick={() => {
				fetch()
					.then(() => setStatus('ready'))
					.catch(() => setStatus('error'))
			}}
		/>
	</div>
}
```

Notice how simple it gets in this case just because we have an event handler that start the sequence. Here we're declaratively saying how states in the component changes after a click happens.

However, we don't have an event handler to solve this when the component mounts.

So, say we have a component that shows info about a movie.
```jsx
function Movie ({id}) {
	const [status, setStatus] = useState('idle')
	const [movie, setMove] = useState(null)
	useEffect(() => {
		// we're using effect this as event handler for mounting
		fetchMovie(id)
			.then((movie) => {
				setStatus('ready')
				setMovie(movie)
			})
			.catch(() => setStatus('error'))
		// this can become fragile when the dependencies are messy
	}, [id])
	return (
	<div>
		{/* we have to deal with inconsistent states (unless we suspend) */}
		{movie.title}
	</div>
}
```

Notice that, all those issues won't be there if we implement the same functionality but after a button is clicked.

If we dig a bit on the API. The actual problem is that `useEffect` has to be executed **every time** the component renders, even if there's no DOM update. That's why the dependency array helps react to tell if the callback inside needs to be executed.

In my opinion this is an *unstable equilibrium*. Meaning, solves the problem but it'll break easily. Adding the dependencies array is a patch. As a developer, I'm afraid of adding expensive calculations in components because I know it's a potential performance issue in the future. 

How do we solve the resource loading then? Well, in the end, we're abstracting a DOM that represents our application. Browser's DOM is not perfect but it have many handy functionalities that we use instinctively like event bubbling and event handlers that we take for granted for **all** DOM elements. However, we don't use any of them when we develop react components.

The event handler we're missing here is **onMount**. That's it. "On mount, get the resource and when the resource is ready, use it for this component". That's the user story here so what if provide **onMount** event handlers for all Components.

Here's a possible implementation:

```jsx

const MovieTitle = (props)=> {
	const movie = props.onMount()
	return <div>{movie.title}</div>
}

const Movie = ({id}) => {
	return (
		<MovieTitle
			fallback={<DisplayMovieLoading />}
			onMount={()=> {
				// the component will be ready after the promise is resolved
				return fetchMovie(id)
					.then(setMovie)
			}}
		/>
	)
}
```

Notice, there're no more side effects in any component. Now you may ask, how you deal with id changes? Same way as we do know, using a `key` prop with the id would run `onMount` again.

What do you think about this approach? Would make your apps simpler?