https://spindas.dreamwidth.org/4207.html
Account name: [ ] Password [ ] [Log in]
(OpenID?) (Forgot it?) [ ] Remember Me
You're viewing [personal profile] spindas's journal
Create a Dreamwidth Account Learn More
[ ] [Interest ] [Go]
Reload page in style: site light
spinda's dreamwidth
Who needs a backend? ChatGPT as the universal Redux reducer
Who needs a backend? ChatGPT as the universal Redux reducer
Dec. 28th, 2022 13:08
spindas: (Default)
[personal profile] spindas
A React/Redux app is a one-way data flow cycle. The user interacts
with the UI, which produces Redux actions, which get dispatched to
reducer functions, which update the app's state object, which gets
passed back to React to re-render the UI.
A conversation with ChatGPT is also a one-way cycle. A human sends a
prompt (action), which gets passed to the language model (reducer),
which updates the conversation (state) with its response.
Can we use the latter to emulate the former? Spoilers: yes, we
totally can! ChatGPT will happily act as your universal reducer, so
you can prototype your app's frontend without needing to mock up a
backend or business logic right away. And after interacting with the
frontend for a bit to teach the AI how the app works, you can even
ask it to implement the reducer function for you.
Here's a quick demo of a basic counter app with my
react-redux-chatgpt plugin doing the reducing (app on the left,
behind-the-scenes ChatGPT conversation in the console on the right):
Watch on YouTube
And here's ChatGPT powering the Redux TodoMVC example, unmodified
except to delete the reducers and replace them with ChatGPT:
Watch on YouTube
Check out the source code on GitHub! Or keep reading for an in-depth
explainer.
Basic Example: Counter
Let's start with the counter app:
[counter-screenshot]
import { Provider, useSelector, useDispatch } from 'react-redux';
const initState = { count: 0 };
const App = () => {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
counter: {count}
);
};
Our initial state is just { count: 0 } - can't get more basic than
that! Clicking the button will dispatch the action { type:
'increment' }, and we expect our reducer to update the state to {
count: 1 }, { count: 2 }, { count: 3 }, etc.
Let's pop over to ChatGPT and tell it what we're doing:
ME: I'm a React/Redux app. I have a single data store.
Here's the initial state of my store, in JSON form: {"count":0}
AI: Great! It looks like you have a simple initial state for your
Redux store, with a single property called "count" that has a
value of 0.
In Redux, the store is the central place where the application's
state is stored. It is created by passing a reducer function to
the createStore function. The reducer function is responsible for
updating the state based on actions that are dispatched to the
store.
Alright, cool. ChatGPT knows what Redux is. Can it process actions
for us?
ME: My Redux reducers received an action, here encoded in JSON
form: {"type":"increment"}
What should the new state of my store be after this action?
AI: If your Redux reducer receives an action with a type of
'increment', it should update the state of the store by
incrementing the value of the count property by 1.
When the reducer function receives an action with a type of
'increment', it will return a new state object with the count
property incremented by 1. The rest of the state will be
unchanged.
For example, if the initial state of the store is { count: 0 },
and the reducer function receives an action with a type of
'increment', the new state of the store will be { count: 1 }.
It's important to note that the reducer function should be a pure
function, meaning that it should not have any side effects and
should always return the same output for a given set of inputs.
This makes it easier to test and reason about the behavior of
your application.
The answer's in there, but it's buried in a bunch of explanation that
isn't machine-readable (...er, present AI company excluded). This is a
common pattern with ChatGPT. Let's try that again and see if we can
make the AI get straight to the point.
ME: My Redux reducers received an action, here encoded in JSON
form: {"type":"increment"}
What should the new state of my store be after this action?
Provide your answer in JSON form. Reply with only the answer in
JSON form and include no other commentary.
AI: {"count":1}
Perfect.
Now, we could start a fresh conversation every time we receive a new
action, repeating our current state for the AI every time. But beyond
costing more tokens when OpenAI finally starts charging for this
magic, taking advantage of the ongoing conversation model that
ChatGPT is built around helps keep its responses internally
consistent. With the a little prompt engineering, we can get the AI
to pull the current state from the conversation log, and guide its
responses based on the previous actions and states it's observed.
ME: I'm a React/Redux app. I have a single data store.
Here's the initial state of my store, in JSON form: {"count":0}
My Redux reducers received an action, here encoded in JSON form:
{"type":"increment"}
What should the new state of my store be after this action?
Provide your answer in JSON form. Reply with only the answer in
JSON form and include no other commentary.
AI: {"count":1}
ME: My Redux reducers received an action, here encoded in JSON
form: {"type":"increment"}
Given the state of the store in your previous response, what
should the new state of my store be after this action? Provide
your answer in JSON form. Reply with only the answer in JSON form
and include no other commentary.
AI: {"count":2}
ME: My Redux reducers received an action, here encoded in JSON
form: {"type":"increment"}
Given the state of the store in your previous response, what
should the new state of my store be after this action? Provide
your answer in JSON form. Reply with only the answer in JSON form
and include no other commentary.
AI: {"count":3}
Now that we've got a cycle going, let's experiment. Can ChatGPT
synthesize actions for us?
ME: The user has requested that I do the following: add 3 to the
count
What action(s) should I dispatch to my Redux store in order to
complete this request? Provide your answer in the form of a JSON
array of actions. Reply with only the answer in JSON array form
and include no other commentary.
AI: {"type":"increment","payload":3}
Okay, it's made up a payload parameter for the increment action.
Creative, but maybe not what we want. Can we get it to stick to using
only what we've demonstrated so far?
ME: The user has requested that I do the following: add 3 to the
count
Given the state of the store in your previous response and the
types of actions I have received so far, what action(s) should I
dispatch to my Redux store in order to complete this request?
Provide your answer in the form of a JSON array of actions. Reply
with only the answer in JSON array form and include no other
commentary.
AI: [{"type":"increment"},{"type":"increment"},
{"type":"increment"}]
ME: My Redux reducers received some actions, here encoded in JSON
form: [{"type":"increment"},{"type":"increment"},
{"type":"increment"}]
Given the state of the store in your previous response, what
should the new state of my store be after this action? Provide
your answer in JSON form. Reply with only the answer in JSON form
and include no other commentary.
AI: {"count":6}
Sweet. It can even handle reducing a sequence of actions with a
single prompt. That'll save on round-trips to OpenAI.
We can cut out the AI entirely once we've demonstrated to it how the
flow should go. It's happy to synthesize a reducer for us:
ME: Give me JavaScript code that implements my reducer function.
It should be able to handle all of the actions I told you I
received, producing the states you told me would result from
applying them.
AI: Sure, here is a JavaScript code that implements a reducer
function that can handle the increment action:
const reducer = (state = {count: 0}, action) => {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
default:
return state;
}
};
This reducer function will initialize the state to {count: 0} if
no state is provided. When it receives an increment action, it
will return a new state object with the count increased by 1. If
it receives any other action type, it will simply return the
current state.
You can add additional actions by adding new cases to the switch
statement. For example, if you wanted to handle a decrement
action, you could add a case for it like this:
case 'decrement':
return {count: state.count - 1};
I hope this helps! Let me know if you have any questions.
---------------------------------------------------------------------
There's no official API for ChatGPT yet, so I'm using the unofficial
chatgpt NPM package to wire up the app in the demo video. OpenAI has
their Cloudflare protection cranked up high; to get around that, it
runs a full Chrome instance (!) in the background to shuttle messages
back and forth between the little REST API server I wrote and
ChatGPT's internal API.
The logic for guiding ChatGPT through a conversation like the above
is packed into my client-side react-redux-chatgpt package. It
constructs the prompts, keeps track of conversation threads, and can
even automatically retry prompts in the rare case ChatGPT spits out
malformed data. There's also a few stock components bundled in for
implementing the "thinking..." screen and action bar in the demo, and
methods like synthesizeReducers and synthesizeActions for generating
code. Hooking it up to our counter app is as simple as:
import { createRoot } from 'react-dom/client';
import { AiButtons, ChatGptBackend, ThinkingScreen } from 'react-redux-chatgpt';
const backend = new ChatGptBackend(initState);
const root = createRoot(document.getElementById('root'));
root.render(
);
Advanced Example: TodoMVC
So ChatGPT can handle a counter app. Big whoop. How about something
more substantial?
[todomvc-screenshot]
TodoMVC is a standard benchmark for web frameworks: the same app,
with the same interface, implemented for React, Mithril, Vue, Elm,
Angular... The task is to implement a todo list, as the name suggests,
with the ability to add todo items, mark them completed, filter based
on completion status, and all clear completed items with a click.
I've downloaded the React/Redux version and deleted the reducers/
directory. Bye bye, business logic! The main JS module looks like
this out of the box:
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './components/App'
import reducer from './reducers'
import 'todomvc-app-css/index.css'
const store = createStore(reducer)
render(
,
document.getElementById('root')
)
And this is what it looks like after tossing the reducers and
dropping in react-redux-chatgpt:
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider, useSelector } from 'react-redux'
import { AiButtons, ChatGptBackend, ThinkingScreen } from 'react-redux-chatgpt';
import App from './components/App'
import 'todomvc-app-css/index.css'
const initState = {
todos: [{ text: 'Use Redux', completed: false, id: 0 }],
visibilityFilter: 'show_all'
}
const backend = new ChatGptBackend(initState, { description: 'todo list' })
render(
,
document.getElementById('root')
)
That's the full extent of the changes I had to make to get this to
work. Note I've dropped the AI a hint about what the app is for with
the description: 'todo list' option passed to ChatGptBackend.
Let's take a peek at the conversation that happens under the hood as
we interact with the app. Clicking the circle next to a todo item
does this:
ME: I'm a React/Redux app. I have a single data store.
A description of the app is: todo list
Here's the initial state of my store, in JSON form: {"todos":
[{"text":"Use
Redux","completed":false,"id":0}],"visibilityFilter":"show_all"}
My Redux reducers received an action, here encoded in JSON form:
{"type":"COMPLETE_TODO","id":0}
What should the new state of my store be after this action?
Provide your answer in JSON form. Reply with only the answer in
JSON form and include no other commentary.
AI: {"todos":[{"text":"Use
Redux","completed":true,"id":0}],"visibilityFilter":"show_all"}
It set the completed property to true on todo 0. Good job, bot!
Here's the UI after the re-render:
[todomvc-screenshot-mark-complete]
Punch "Read more books" into the text field and hit return, and this
happens:
ME: My Redux reducers received an action, here encoded in JSON
form: {"type":"ADD_TODO","text":"Read more books"}
Given the state of the store in your previous response, what
should the new state of my store be after this action? Provide
your answer in JSON form. Reply with only the answer in JSON form
and include no other commentary.
AI: {"todos":[{"text":"Use Redux","completed":true,"id":0},
{"text":"Read more
books","completed":false,"id":1}],"visibilityFilter":"show_all"}
It even figured out that todo items need unique ids! And the new item
starts with "completed": false, while the previous one is left at
"completed": true.
[todomvc-screenshot-add-todo]
We can use the filter links:
ME: My Redux reducers received an action, here encoded in JSON
form: {"type":"SET_VISIBILITY_FILTER","filter":"show_completed"}
Given the state of the store in your previous response, what
should the new state of my store be after this action? Provide
your answer in JSON form. Reply with only the answer in JSON form
and include no other commentary.
AI: {"todos":[{"text":"Use Redux","completed":true,"id":0},
{"text":"Read more
books","completed":false,"id":1}],"visibilityFilter":"show_completed"}
[todomvc-screenshot-filter-completed]
And we can "clear completed":
ME: My Redux reducers received an action, here encoded in JSON
form: {"type":"CLEAR_COMPLETED"}
Given the state of the store in your previous response, what
should the new state of my store be after this action? Provide
your answer in JSON form. Reply with only the answer in JSON form
and include no other commentary.
AI: {"todos":[{"text":"Read more
books","completed":false,"id":1}],"visibilityFilter":"show_all"}
[todomvc-screenshot-clear-completed]
We can go freeform and tell ChatGPT to dream up actions with natural
language, then execute them all at once:
const actions = await backend.synthesizeActions("add todo items for reading each of Tokein's books")
// [
// {type: 'ADD_TODO', text: 'The Hobbit'},
// {type: 'ADD_TODO', text: 'The Lord of the Rings'},
// {type: 'ADD_TODO', text: 'The Silmarillion'},
// {type: 'ADD_TODO', text: 'The Children of Hurin'},
// {type: 'ADD_TODO', text: 'The Fall of Gondolin'},
// {type: 'ADD_TODO', text: 'The Adventures of Tom Bombadil'}
// ]
backend.store.dispatch(actions)
[todomvc-screenshot-tolkein-books]
And because we're dealing with an all-knowing AI, we can make up our
own actions that go beyond the standard TodoMVC set:
backend.store.dispatch({type: 'COMPLETE_ALL_TODOS'})
[todomvc-screenshot-complete-all]
After all that, ChatGPT can spit out a reducer function that handles
the whole app, no sweat:
await backend.synthesizeReducers()
Here is an example of how you could implement the reducer
function in JavaScript to handle the actions you described:
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
todos: [
...state.todos,
{
text: action.text,
completed: false,
id: state.todos.length
}
],
visibilityFilter: state.visibilityFilter
};
case 'COMPLETE_TODO':
return {
todos: state.todos.map(todo =>
todo.id === action.id ? { ...todo, completed: true } : todo
),
visibilityFilter: state.visibilityFilter
};
case 'SET_VISIBILITY_FILTER':
return {
todos: state.todos,
visibilityFilter: action.filter
};
case 'CLEAR_COMPLETED':
return {
todos: state.todos.filter(todo => !todo.completed),
visibilityFilter: state.visibilityFilter
};
case 'COMPLETE_ALL_TODOS':
return {
todos: state.todos.map(todo => ({ ...todo, completed: true })),
visibilityFilter: state.visibilityFilter
};
default:
return state;
}
};
This reducer function takes in the current state of the store and
an action, and returns the new state of the store based on the
action type. It handles all of the actions you described and
produces the states that you specified.
Note that this is just one way to implement the reducer function,
and there are many other ways you could do it as well. The
important thing is to make sure that the reducer function
correctly handles all of the actions that your application can
dispatch, and produces the correct new state for the store.
[chatgpt-ending]
The future of coding is here, and it's chatting with us.
P.S. Wondering if ChatGPT can generate the frontend code, too? The
answer is yes - sort of! Sometimes it forgets to include an input
field for the todo text.
* Current Mood: # artistic
* Current Location: Sammamish, WA
* Current Music: la pumpkin - BLOODY MEMOIR vol. II
Tags:
* artificial intelligence,
* code synthesis,
* javascript,
* machine learning,
* react,
* redux,
* web
* Previous Entry
* Add Memory
* Share This Entry
* Next Entry
---------------------------------------------------------------------
* Reply
---------------------------------------------------------------------
Profile
* Recent Entries
* Archive
* Reading
* Tags
* Memories
* Profile
Links
* Homepage
* GitHub
December 2022
S M T W T F S
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Most Popular Tags
* 3d - 1 use
* arch - 2 uses
* artificial intelligence - 1 use
* aur - 2 uses
* code - 1 use
* code synthesis - 1 use
* comics - 1 use
* dk2 - 1 use
* dreamwidth - 1 use
* firewatch - 1 use
* fun - 2 uses
* game - 1 use
* games - 1 use
* groot - 1 use
* gsoc - 1 use
* guardians of the galaxy - 1 use
* hacks - 2 uses
* haskell - 1 use
* internships - 1 use
* javascript - 1 use
* jobs - 1 use
* linux - 2 uses
* liquidhaskell - 1 use
* machine learning - 1 use
* marvel - 1 use
* nemo - 1 use
* nodejs - 1 use
* oculus - 1 use
* ogre - 1 use
* ogrevr - 1 use
* package - 2 uses
* programming - 1 use
* react - 1 use
* reading - 1 use
* redux - 1 use
* resource - 1 use
* rift - 1 use
* rocket raccoon - 1 use
* snippets - 1 use
* status-update - 1 use
* theme - 1 use
* tutorial - 1 use
* tweaks - 1 use
* userscripts - 1 use
* web - 3 uses
Page generated Dec. 28th, 2022 23:00
Powered by Dreamwidth Studios
Top of page