Can AI convert React to RxJS?
Tags: react, rxjs, javascript, openai
Published: 2022-12-03 08:59:46

You may have seen those new OpenAI conversations floating around from ChatGPT. They're impressive, right?

I was intrigued so I started shooting it some prompts, and I was genuinely surprised by how capable it is. It understood just about everything I threw at it, and it was even able to transform OOP code into Functional code and vice versa.

I'll share a "conversation" I had with the bot below. The gist is, I gave it some React state management in a custom hook and prompted the AI to modify it to use RxJS instead.

### Initial Prompt 

I asked: "What does this code do?"

```js
import { useState, useEffect, useCallback } from 'react'

function useAPI() {
    const [data, setData] = useState(null)
    const [loading, setLoading] = useState(false)

    const load = useCallback(() => {
        setLoading(true)
        fetch('https://jsonplaceholder.typicode.com/todos/1')
            .then(response => response.json())
            .then((result) => {
                setData(result)
                setLoading(false)
            })
    }, [setData])

    function edit(key, value) {
        setData({
            ...data,
            [key]: value 
        })
    }

    useEffect(() => {
        load()
    }, [load])

    return {
        data,
        edit,
        loading
    }
}
```

> "This code defines a custom `**useAPI**` hook that allows a React component to fetch data from an API and store it in component state. The hook returns an object with two properties: `data` and `loading`. `data` is the data that was fetched from the API and `loading` is a boolean that indicates whether the data is currently being fetched. The hook provides a `load` method that can be used to initiate the data fetch, and an `edit` method that can be used to update the data in component state."

<small>(quick note - I forgot to include 'edit' in the return value in the prompt I gave the bot, but I modified its response to be include it. The description the bot gave is slightly inaccurate, my fault entirely!)</small>

### Second Prompt - data fetching

Next, I asked: "Can you rewrite it to use RxJS for data fetching and state management?"

> "Yes, here is one way you could rewrite the code using RxJS for data fetching and state management"

```js
import { useState, useEffect } from 'react'
import { from } from 'rxjs'
import { map } from 'rxjs/operators'

function useAPI() {
    const [data, setData] = useState(null)
    const [loading, setLoading] = useState(false)

    const load = () => {
        setLoading(true)
        from(fetch('https://jsonplaceholder.typicode.com/todos/1'))
            .pipe(
                map(response => response.json()),
                map(result => {
                    setData(result)
                    setLoading(false)
                })
            )
            .subscribe()
    }

    function edit(key, value) {
        setData({
            ...data,
            [key]: value 
        })
    }

    useEffect(() => {
        load()
    }, [])

    return {
        data,
        edit,
        loading
    }
}
```

> "In this version of the code, we use the from operator from RxJS to convert the fetch call into an observable. We then use the map operator to transform the data that is emitted by the observable, first by converting the response to JSON and then by storing the result in component state and updating the loading state. Finally, we use the subscribe method to initiate the data fetch."

To the bot's credit, it's close. A few issues: 
1. In the pipe, `setData(result)` is setting the data to an unresolved promise. This is a simple fix - make the function async and use `setData(await result)`.
2. You can't just plop an observable straight into React and expect it to be reactive. [See here](https://dev.to/crowdozer/rxjs-and-react-basics-5gig) for a rundown on how to get React to "react" to an observable 😅 It's not a problem yet, but it's about to be!
3. It ignored when I asked it to use RxJS for state management. It only did the data fetching! lol

But alas, it's not perfect, and I asked a lot out of it. Let's ignore those issues and see where the bot goes from here.

### Third prompt - state management

I asked: "can you rewrite it to use rxjs instead of useState?"

> "Yes, here is one way you could rewrite the code to use RxJS instead of useState for state management"

```js
import { useEffect } from 'react'
import { from, BehaviorSubject } from 'rxjs'
import { map } from 'rxjs/operators'

function useAPI() {
    const data$ = new BehaviorSubject(null)
    const loading$ = new BehaviorSubject(false)

    const load = () => {
        loading$.next(true)
        from(fetch('https://jsonplaceholder.typicode.com/todos/1'))
            .pipe(
                map(response => response.json()),
                map(result => {
                    data$.next(result)
                    loading$.next(false)
                })
            )
            .subscribe()
    }

    function edit(key, value) {
        data$.next({
            ...data$.value,
            [key]: value
        })
    }

    useEffect(() => {
        load()
    }, [])

    return {
        data: data$.value,
        loading: loading$.value,
        edit
    }
}
```

> "In this version of the code, we use `BehaviorSubjects` from RxJS to manage the component state. A `BehaviorSubject` is a special type of subject that stores the current value of the stream and emits that value to new subscribers. This allows us to get and set the current `value` of the state by reading from and writing to the value property of the `BehaviorSubject`. We use the `next` method to update the state by pushing new values onto the stream.
>
> We then use the `useEffect` hook from React to trigger the data fetch when the component is mounted. Finally, we return the current values of the `data$` and `loading$` subjects from the hook so that they can be used in the component."

The bot's getting close! The aforementioned issues still exist (React not understanding Observables, data being set to a promise) but those are easy to fix manually after all of this.

### Fourth prompt - logic extraction

Let's make another big stride.

I asked: "can you extract data$, loading$, load, and edit into a separate file?"

> "Yes, you could extract the `data$`, `loading$`, `load`, and `edit` functions into a separate file like this:
>
> state.js:"

```js
import { from, BehaviorSubject } from 'rxjs'
import { map } from 'rxjs/operators'

const data$ = new BehaviorSubject(null)
const loading$ = new BehaviorSubject(false)

const load = () => {
    loading$.next(true)
    from(fetch('/api/data'))
        .pipe(
            map(response => response.json()),
            map(result => {
                data$.next(result)
                loading$.next(false)
            })
        )
        .subscribe()
}

function edit(key, value) {
    data$.next({
        ...data$.value,
        [key]: value
    })
}

export { data$, loading$, load, edit }
```

> "Then in your `useAPI` hook, you could import the `data$`, `loading$`, `load`, and `edit` functions from the `state.js` file like this:"

```js
import { useEffect } from 'react'
import { data$, loading$, load, edit } from './state'

function useAPI() {
    useEffect(() => {
        load()
    }, [])

    return {
        data: data$.value,
        loading: loading$.value,
        edit
    }
}
```

> "This allows you to separate the state management logic from the hook itself, which can make your code easier to maintain and test."

Now that's pretty cool!!!

Not only had it remembered that it had created the "state.js" file, it remembered all of the code changes it made, AND it knew how to import and use all of the files and dependencies. Incredible, right? When they said it had memory, I thought it would be some vague notion of was said before.. but this is some next level stuff.

Recall however that we still have a massive glaring issue! React won't react to an observable all by itself.

Before tackling that, I tried a few times to get the bot to fix the promise bug in the loading$ pipe, but I couldn't word it in a way it understood. To its credit, it completely rewrote that function to use async/await and a try/catch lol! But that's not what we want, we want rxjs. I settled on fixing that manually,

After that, I was trying to word my prompt such that the bot would alter useAPI to include useObservableState (from "observable-hooks") to fix the reactivity. But before I could hone in on the perfect prompt, my session expired and I got kicked out of ChatGPT! Lol the conversation was lost.

Let's fix those bugs for the bot, starting with the promise:

```js
const load = () => {
  loading$.next(true);
  from(fetch("https://jsonplaceholder.typicode.com/todos/1"))
    .pipe(
      map((response) => response.json()),
      map(async (result) => {
        data$.next(await result);
        loading$.next(false);
      })
    )
    .subscribe();
};
```

Easy enough, all we needed to do was make that map function async and await the result, since response.json() is a promise.

Next, in useAPI:
```js
import { useObservableState } from "observable-hooks";

function useAPI() {
  const data = useObservableState(data$, null);
  const loading = useObservableState(loading$, false);

  useEffect(() => {
    load();
  }, []);

  return {
    data,
    loading,
    edit
  };
}
```

In *theory*, it's now feature-complete.

The question is: Does is work? Remember, the bot did pretty much everything.

The answer is... Yes! 

It does. Here's a codesandbox where I ported over the final result, and a tiny app to visualize it: https://codesandbox.io/s/trusting-dawn-2qihsb

### Final thoughts

It was a very cool experience walking the bot through this. 

In a way, it really felt like I was being taught something by a human. At the same time, I still had to guide the bot, it was like mentoring somebody who isn't quite comfortable with the topic. I don't think our machine overlords are *quite* ready to replace us, but we can certainly learn from them. Definitely, it's more of a two-way learning experience :wink:



Thanks for reading! 👋