Skip to content

Commit ed24377

Browse files
committed
Update React
1 parent 8e87bee commit ed24377

17 files changed

Lines changed: 426 additions & 2 deletions

MyApp/_posts/2025-11-12_react.md

Lines changed: 175 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,179 @@ Switch to Dark Mode to see how all components looks in Dark Mode:
101101
</a>
102102
</div>
103103

104-
---
105-
106104
ServiceStack's first-class React support positions your applications at the forefront of AI-assisted development. With declarative APIs, complete type safety, and minimal boilerplate, you can leverage AI code generation with confidence while maintaining the quality and maintainability your production systems demand.
105+
106+
## TypeScript Data Models
107+
108+
As AI Models are not as adept at generating C# APIs or Migrations yet, they excel at generating TypeScript code, which our
109+
[TypeScript Data Models](https://docs.servicestack.net/autoquery/okai-models) feature can take advantage of by generating all the C# AutoQuery CRUD APIs and DB Migrations needing to support it.
110+
111+
With just a TypeScript Definition:
112+
113+
- [Bookings.d.ts](https://github.com/NetCoreTemplates/react-vite/blob/main/MyApp.ServiceModel/Bookings.d.ts)
114+
115+
We can generate all the AutoQuery CRUD APIs and DB Migrations needed to enable a CRUD UI with:
116+
117+
:::copy
118+
npx okai Bookings.d.ts
119+
:::
120+
121+
This is enough to generate a complete CRUD UI to manage Bookings
122+
in your React App with the [React AutoQueryGrid Component](https://react.servicestack.net/gallery/autoquerygrid).
123+
or with ServiceStack's built-in [Locode UI](https://docs.servicestack.net/locode/):
124+
125+
:::{.wideshot}
126+
[![](/img/posts/react/bookings-locode.webp)](https://docs.servicestack.net/locode/)
127+
:::
128+
129+
### Cheat Sheet
130+
131+
We'll quickly cover the common dev workflow for this feature.
132+
133+
To create a new Table use `init <Table>`, e.g:
134+
135+
:::copy
136+
npx okai init Transaction
137+
:::
138+
139+
This will generate an empty `MyApp.ServiceModel/<Table>.d.ts` file along with stub AutoQuery APIs and DB Migration implementations.
140+
141+
#### Regenerate AutoQuery APIs and DB Migrations
142+
143+
After modifying the TypeScript Data Model to include the desired fields, you can re-run the `okai` tool to generate the AutoQuery APIs and DB Migrations
144+
(which can be run anywhere within your Solution):
145+
146+
:::copy
147+
npx okai Transaction.d.ts
148+
:::
149+
150+
After you're happy with your Data Model you can run DB Migrations to run the DB Migration and create your RDBMS Table:
151+
152+
:::copy
153+
npm run migrate
154+
:::
155+
156+
#### Making changes after first migration
157+
158+
If you want to make further changes to your Data Model, you can re-run the `okai` tool to update the AutoQuery APIs and DB Migrations, then run the `rerun:last` npm script to drop and re-run the last migration:
159+
160+
:::copy
161+
npm run rerun:last
162+
:::
163+
164+
#### Removing a Data Model and all generated code
165+
166+
If you changed your mind and want to get rid of the RDBMS Table you can revert the last migration:
167+
168+
:::copy
169+
npm run revert:last
170+
:::
171+
172+
Which will drop the table and then you can get rid of the AutoQuery APIs, DB Migrations and TypeScript Data model with:
173+
174+
:::copy
175+
npx okai rm Transaction.d.ts
176+
:::
177+
178+
## AI-First Example
179+
180+
There are a number of options for starting with an AI generated Application, with all the Instant AI App Generators like
181+
[Google's App Studio](https://aistudio.google.com/apps) able to provide a great starting point. Although currently Professional Developers tend to use
182+
[Cursor](https://cursor.com/), [Claude Code](https://www.claude.com/product/claude-code) or
183+
[Codex](https://openai.com/codex/) as their day-to-day tools of choice.
184+
185+
### Use GitHub Copilot when creating a new Repository
186+
187+
If you're using [GitHub Copilot](https://copilot.github.com/) you can also use it to generate a new App
188+
[from the Vite React template ](https://github.com/new?template_name=react-vite&template_owner=NetCoreTemplates):
189+
190+
[![](/img/posts/react/react-new-repo.webp)](https://github.com/new?template_name=react-vite&template_owner=NetCoreTemplates)
191+
192+
For the example, I've started with a useful App that I've never created before, a Budget Planner App, using the prompt:
193+
194+
### Budget Planner Prompt
195+
196+
```
197+
- React 19, TypeScript, TailwindCSS v4
198+
- Persistence in IndexedDB/localStorage
199+
- Recharts
200+
- Vitest with React Testing Library
201+
202+
## Features
203+
Dashboard
204+
- Overview of total income, expenses, and remaining budget
205+
- Monthly summary chart (line graph)
206+
- Expense categories (pie chart)
207+
208+
Transactions
209+
- Add/Edit/Delete income or expenses
210+
- Date filtering/sorting
211+
212+
Budgets
213+
- Set monthly budget goals per category
214+
- Progress bars for spending vs. budget
215+
216+
Reports
217+
- View past months
218+
- Export
219+
```
220+
221+
The generated source code for the App was uploaded to: [github.com/mythz/budgets.apps.cafe](https://github.com/mythz/budgets.apps.cafe)
222+
223+
### Budgent Planner App
224+
225+
After a few minutes Copilot creates a PR with what we asked for, even things that we didn't specify in the prompt but could be inferred from the Project Template like **Dark Mode** support where it made use of the existing `<DarkModeToggle />`.
226+
227+
<screenshots-gallery :images="{
228+
'Dashboard': '/img/posts/react/budget/dashboard.webp',
229+
'Dashboard - Dark Mode': '/img/posts/react/budget/dashboard-dark.webp',
230+
'Transactions List - Dark Mode': '/img/posts/react/budget/transactions-list-dark.webp',
231+
'Transactions - Add Expense': '/img/posts/react/budget/transactions-expense.webp',
232+
'Transactions - Add Expense - Dark Mode': '/img/posts/react/budget/transactions-expense-dark.webp',
233+
'Transactions - Add Income - Dark Mode': '/img/posts/react/budget/transactions-income-dark.webp',
234+
'Budgets - Dark Mode': '/img/posts/react/budget/budgets-dark.webp',
235+
'Reports': '/img/posts/react/budget/reports.webp',
236+
'Reports - Dark Mode': '/img/posts/react/budget/reports-dark.webp',
237+
}"></screenshots-gallery>
238+
239+
### Prompt AI to add new Features
240+
241+
AI Assistance doesn't end after the initial implementation as AI Models and tools are more than capable to
242+
create 100% of the React UI now, including new features, fixes and other improvements. For this example I used
243+
Claude Code to Implement Category Auto-Tagging with this prompt:
244+
245+
Implement Category Auto-Tagging
246+
247+
Allow specifying tags when creating a new transaction.
248+
When users add a transaction, try to predict the tag from the Description, e.g:
249+
250+
Input: “Starbucks latte” → Suggests category: Food & Drinks
251+
Input: “Uber to work” → Suggests category: Transport
252+
253+
Implementation:
254+
255+
Maintain a small local list of common keywords + categories.
256+
Pre-fill category in the transaction form as the user types in the Description.
257+
258+
Which resulted in [this commit](https://github.com/mythz/budgets.apps.cafe/commit/e45a17b8dfd2b5983971554ced3e52ded6fa050e) which sees the feature available in the UI:
259+
260+
:::{.shadow}
261+
![](/img/posts/react/budget/expense-tags-dark.webp)
262+
:::
263+
264+
Along with different seed data, tailored for Income and Expenses:
265+
- [categoryAutoTag.ts](https://github.com/mythz/budgets.apps.cafe/blob/main/MyApp.Client/src/lib/categoryAutoTag.ts)
266+
267+
And 19 passing tests to verify a working implementation:
268+
269+
- [categoryAutoTag.test.ts](https://github.com/mythz/budgets.apps.cafe/blob/main/MyApp.Client/src/lib/categoryAutoTag.test.ts)
270+
271+
Combined with Vite's instant hot-reload, this creates a remarkably fluid development experience where
272+
we get to watch our prompts materialize into working features in real-time.
273+
274+
All this to say that this new development model exists today, and given its significant productivity gains, it's
275+
very likely to become the future of software development, especially for UIs. Since developers are no longer
276+
the primary authors of code, our UI choices swing from Developer preferences to UI technologies that AI models
277+
excel at.
278+
279+
So whilst we have a preference for Vue given it's more readable syntax and progressive enhancement capabalities, and whilst .NET ecosystem has a strong bias towards Blazor, we're even more excited for the future of React and are committed to providing the best possible support for it.

MyApp/wwwroot/css/app.css

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,9 @@
425425
.top-2\/3 {
426426
top: calc(2/3 * 100%);
427427
}
428+
.top-4 {
429+
top: calc(var(--spacing) * 4);
430+
}
428431
.top-8 {
429432
top: calc(var(--spacing) * 8);
430433
}
@@ -455,12 +458,18 @@
455458
.right-2 {
456459
right: calc(var(--spacing) * 2);
457460
}
461+
.right-4 {
462+
right: calc(var(--spacing) * 4);
463+
}
458464
.right-full {
459465
right: 100%;
460466
}
461467
.bottom-0 {
462468
bottom: calc(var(--spacing) * 0);
463469
}
470+
.bottom-4 {
471+
bottom: calc(var(--spacing) * 4);
472+
}
464473
.bottom-full {
465474
bottom: 100%;
466475
}
@@ -929,6 +938,9 @@
929938
.aspect-\[9\/10\] {
930939
aspect-ratio: 9/10;
931940
}
941+
.aspect-\[2048\/2158\] {
942+
aspect-ratio: 2048/2158;
943+
}
932944
.aspect-video {
933945
aspect-ratio: var(--aspect-video);
934946
}
@@ -1045,6 +1057,9 @@
10451057
.max-h-\[72px\] {
10461058
max-height: 72px;
10471059
}
1060+
.max-h-\[90vh\] {
1061+
max-height: 90vh;
1062+
}
10481063
.max-h-\[130px\] {
10491064
max-height: 130px;
10501065
}
@@ -1365,6 +1380,10 @@
13651380
--tw-translate-y: calc(var(--spacing) * 4);
13661381
translate: var(--tw-translate-x) var(--tw-translate-y);
13671382
}
1383+
.translate-y-full {
1384+
--tw-translate-y: 100%;
1385+
translate: var(--tw-translate-x) var(--tw-translate-y);
1386+
}
13681387
.scale-95 {
13691388
--tw-scale-x: 95%;
13701389
--tw-scale-y: 95%;
@@ -1852,6 +1871,22 @@
18521871
.bg-black {
18531872
background-color: var(--color-black);
18541873
}
1874+
.bg-black\/50 {
1875+
background-color: color-mix(in srgb, #000 50%, transparent);
1876+
@supports (color: color-mix(in lab, red, red)) {
1877+
& {
1878+
background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
1879+
}
1880+
}
1881+
}
1882+
.bg-black\/95 {
1883+
background-color: color-mix(in srgb, #000 95%, transparent);
1884+
@supports (color: color-mix(in lab, red, red)) {
1885+
& {
1886+
background-color: color-mix(in oklab, var(--color-black) 95%, transparent);
1887+
}
1888+
}
1889+
}
18551890
.bg-blue-50 {
18561891
background-color: var(--color-blue-50);
18571892
}
@@ -2100,6 +2135,16 @@
21002135
--tw-gradient-from: var(--color-white);
21012136
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
21022137
}
2138+
.via-black\/20 {
2139+
--tw-gradient-via: color-mix(in srgb, #000 20%, transparent);
2140+
@supports (color: color-mix(in lab, red, red)) {
2141+
& {
2142+
--tw-gradient-via: color-mix(in oklab, var(--color-black) 20%, transparent);
2143+
}
2144+
}
2145+
--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
2146+
--tw-gradient-stops: var(--tw-gradient-via-stops);
2147+
}
21032148
.via-slate-200 {
21042149
--tw-gradient-via: var(--color-slate-200);
21052150
--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
@@ -2210,6 +2255,9 @@
22102255
}
22112256
}
22122257
}
2258+
.object-contain {
2259+
object-fit: contain;
2260+
}
22132261
.object-cover {
22142262
object-fit: cover;
22152263
}
@@ -3122,6 +3170,24 @@
31223170
}
31233171
}
31243172
}
3173+
.group-hover\:translate-y-0 {
3174+
&:is(:where(.group):hover *) {
3175+
@media (hover: hover) {
3176+
--tw-translate-y: calc(var(--spacing) * 0);
3177+
translate: var(--tw-translate-x) var(--tw-translate-y);
3178+
}
3179+
}
3180+
}
3181+
.group-hover\:scale-110 {
3182+
&:is(:where(.group):hover *) {
3183+
@media (hover: hover) {
3184+
--tw-scale-x: 110%;
3185+
--tw-scale-y: 110%;
3186+
--tw-scale-z: 110%;
3187+
scale: var(--tw-scale-x) var(--tw-scale-y);
3188+
}
3189+
}
3190+
}
31253191
.group-hover\:rotate-12 {
31263192
&:is(:where(.group):hover *) {
31273193
@media (hover: hover) {
@@ -3668,6 +3734,18 @@
36683734
}
36693735
}
36703736
}
3737+
.hover\:bg-white\/10 {
3738+
&:hover {
3739+
@media (hover: hover) {
3740+
background-color: color-mix(in srgb, #fff 10%, transparent);
3741+
@supports (color: color-mix(in lab, red, red)) {
3742+
& {
3743+
background-color: color-mix(in oklab, var(--color-white) 10%, transparent);
3744+
}
3745+
}
3746+
}
3747+
}
3748+
}
36713749
.hover\:bg-white\/20 {
36723750
&:hover {
36733751
@media (hover: hover) {
@@ -3734,6 +3812,13 @@
37343812
}
37353813
}
37363814
}
3815+
.hover\:text-gray-300 {
3816+
&:hover {
3817+
@media (hover: hover) {
3818+
color: var(--color-gray-300);
3819+
}
3820+
}
3821+
}
37373822
.hover\:text-gray-500 {
37383823
&:hover {
37393824
@media (hover: hover) {
@@ -6202,6 +6287,16 @@
62026287
background-color: var(--color-gray-900);
62036288
}
62046289
}
6290+
.dark\:bg-gray-900\/90 {
6291+
&:where(.dark, .dark *) {
6292+
background-color: color-mix(in srgb, oklch(21% 0.034 264.665) 90%, transparent);
6293+
@supports (color: color-mix(in lab, red, red)) {
6294+
& {
6295+
background-color: color-mix(in oklab, var(--color-gray-900) 90%, transparent);
6296+
}
6297+
}
6298+
}
6299+
}
62056300
.dark\:bg-green-200 {
62066301
&:where(.dark, .dark *) {
62076302
background-color: var(--color-green-200);
49.3 KB
Loading
43.4 KB
Loading
63.3 KB
Loading
73.2 KB
Loading
36.2 KB
Loading
61.7 KB
Loading
68 KB
Loading
32 KB
Loading

0 commit comments

Comments
 (0)