Repository: Rezact/Rezact (ts)

Issue:
BUG: JSON.stringify an object with reactive properties does not work as expected
<img width="511" alt="image" src="https://github.com/Rezact/Rezact/assets/2927894/baac5f32-6f25-49ae-8fd1-5c3ded6bc2a2">


Pull request description:
40 bug jsonstringify an object with reactive properties does not work as expected
fix #40 

Patch:
diff --git a/src/components/HomePage.tsx b/src/components/HomePage.tsx
index d4dd181..b93830f 100644
--- a/src/components/HomePage.tsx
+++ b/src/components/HomePage.tsx
@@ -10,7 +10,7 @@ function Button() {
   // let $obj = { $firstName: "", $lastName: "" };
   let xTime = "time";
   let xTimes = "times";
-  // $: console.log($count, $arr.toJson());
+  // $: console.log($count, $arr);
   // $: {
   //   if ($count === 2) console.log("Count is 2");
   //   if ($arr.length === 7) {
diff --git a/src/components/HomePageStatePersistsAcrossRouteChanges.tsx b/src/components/HomePageStatePersistsAcrossRouteChanges.tsx
index 76b4c13..c71ef35 100644
--- a/src/components/HomePageStatePersistsAcrossRouteChanges.tsx
+++ b/src/components/HomePageStatePersistsAcrossRouteChanges.tsx
@@ -14,7 +14,7 @@ function Button() {
   // let $obj = { $firstName: "", $lastName: "" };
   let xTime = "time";
   let xTimes = "times";
-  // $: console.log($count, $arr.toJson());
+  // $: console.log($count, $arr);
   // $: {
   //   if ($count === 2) console.log("Count is 2");
   //   if ($arr.length === 7) {
diff --git a/src/examples/Forms/ControlledForms.tsx b/src/examples/Forms/ControlledForms.tsx
index a57a127..58c0839 100644
--- a/src/examples/Forms/ControlledForms.tsx
+++ b/src/examples/Forms/ControlledForms.tsx
@@ -66,7 +66,7 @@ export function Page() {
         children: [$child1, $child2, $child3],
         checkbox: { scales: $scales, horns: $horns },
         radio: { drone: $drone },
-        lineItems: $lineItems.toJson(),
+        lineItems: $lineItems,
         selectItem: $selectItem,
       },
       null,
diff --git a/src/lib/rezact/mapState.ts b/src/lib/rezact/mapState.ts
index faa8bcb..f56f448 100644
--- a/src/lib/rezact/mapState.ts
+++ b/src/lib/rezact/mapState.ts
@@ -198,9 +198,11 @@ export class MapSignal<T> extends Signal<T> {
     setTimeout(() => this.updateList(this.func), 0);
   }
 
-  toJson() {
+  toJSON() {
     return this.value.map((thisVal) => {
-      if (thisVal.toJson) return thisVal.toJson();
+      if (typeof thisVal === "object" && !thisVal.state && !thisVal.get)
+        return thisVal;
+      if (thisVal.toJSON) return thisVal.toJSON();
       thisVal.get ? thisVal.get() : thisVal;
     });
   }
diff --git a/src/lib/rezact/signals.ts b/src/lib/rezact/signals.ts
index 848c2c8..b50a4ca 100644
--- a/src/lib/rezact/signals.ts
+++ b/src/lib/rezact/signals.ts
@@ -164,13 +164,13 @@ export class Signal<T> {
     return;
   }
 
-  toJson() {
-    if (this.value.state) return this.value.toJson();
-    if (typeof this.value === "object") {
+  toJSON() {
+    if (this.value.state) return this.value.toJSON();
+    if (typeof this.value === "object" && !isArray(this.value)) {
       const newObj = {};
       Object.keys(this.value).map((key) => {
         const val = this.value[key];
-        if (val.state) return (newObj[key] = val.toJson());
+        if (val.state) return (newObj[key] = val.toJSON());
         newObj[key] = val;
       });
       return newObj;


Test patch:
diff --git a/src/examples/StoreTest/App.jsx b/src/examples/StoreTest/App.jsx
index b257f4b..23c8410 100644
--- a/src/examples/StoreTest/App.jsx
+++ b/src/examples/StoreTest/App.jsx
@@ -3,6 +3,7 @@ import { StoreButton } from "./StoreButton";
 import { store } from "./store";
 
 export default function App() {
+  const jsonString = JSON.stringify(store, null, 2);
   return (
     <>
       <StoreButton />
@@ -10,6 +11,8 @@ export default function App() {
       <button onClick={() => store.$storeCount++}>Also Inc</button>
       <hr />
       <TestNested />
+      <hr />
+      <textarea>{jsonString}</textarea>
     </>
   );
 }
diff --git a/src/examples/StoreTest/__snapshots__/stores.test.ts.snap b/src/examples/StoreTest/__snapshots__/stores.test.ts.snap
new file mode 100644
index 0000000..0ffdfb8
--- /dev/null
+++ b/src/examples/StoreTest/__snapshots__/stores.test.ts.snap
@@ -0,0 +1,28 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Reactive Computations > stringifies using JSON stringify properly 1`] = `
+"{
+  \\"$count\\": 0,
+  \\"$storeCount\\": 0,
+  \\"$firstName\\": \\"John\\",
+  \\"$lastName\\": \\"Doe\\",
+  \\"$email\\": \\"test@example.com\\",
+  \\"$todos\\": [
+    {
+      \\"$id\\": 1,
+      \\"$title\\": \\"Learn Vue 3\\",
+      \\"$completed\\": true
+    },
+    {
+      \\"$id\\": 2,
+      \\"$title\\": \\"Learn TypeScript\\",
+      \\"$completed\\": false
+    },
+    {
+      \\"$id\\": 3,
+      \\"$title\\": \\"Learn React\\",
+      \\"$completed\\": false
+    }
+  ]
+}"
+`;
diff --git a/src/examples/StoreTest/store.ts b/src/examples/StoreTest/store.ts
index 7e28cb9..8d50a4e 100644
--- a/src/examples/StoreTest/store.ts
+++ b/src/examples/StoreTest/store.ts
@@ -1,4 +1,24 @@
 export const store = {
   $count: 0,
   $storeCount: 0,
+  $firstName: "John",
+  $lastName: "Doe",
+  $email: "test@example.com",
+  $todos: [
+    {
+      $id: 1,
+      $title: "Learn Vue 3",
+      $completed: true,
+    },
+    {
+      $id: 2,
+      $title: "Learn TypeScript",
+      $completed: false,
+    },
+    {
+      $id: 3,
+      $title: "Learn React",
+      $completed: false,
+    },
+  ],
 };
diff --git a/src/examples/StoreTest/stores.test.ts b/src/examples/StoreTest/stores.test.ts
index fab7641..5ed8a2a 100644
--- a/src/examples/StoreTest/stores.test.ts
+++ b/src/examples/StoreTest/stores.test.ts
@@ -71,4 +71,9 @@ describe("Reactive Computations", () => {
     await buttonDiv2.click();
     await waitFor(() => expect(p.textContent).toBe("2"));
   });
+
+  it("stringifies using JSON stringify properly", async () => {
+    const textArea = document.querySelector("textarea");
+    expect(textArea.value).toMatchSnapshot();
+  });
 });