DEV Community

crowdozer
crowdozer

Posted on • Updated on

Can AI convert React to RxJS?

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?"

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
    }
}
Enter fullscreen mode Exit fullscreen mode

"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."

(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!)

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"

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
    }
}
Enter fullscreen mode Exit fullscreen mode

"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 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"

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
    }
}
Enter fullscreen mode Exit fullscreen mode

"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:"

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 }
Enter fullscreen mode Exit fullscreen mode

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

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

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

    return {
        data: data$.value,
        loading: loading$.value,
        edit
    }
}
Enter fullscreen mode Exit fullscreen mode

"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:

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();
};
Enter fullscreen mode Exit fullscreen mode

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:

import { useObservableState } from "observable-hooks";

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

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

  return {
    data,
    loading,
    edit
  };
}
Enter fullscreen mode Exit fullscreen mode

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 😉

Thanks for reading! 👋

Latest comments (1)

Collapse
 
mprzodala profile image
Mariusz

I'm curious what happens when you ask to rewrite the code using a not popular library :)