barelyhuman / mute

lab repo for playing around with mutable state syntax and methodologies
MIT License
1 stars 0 forks source link

Pass state outside of component scope #1

Open barelyhuman opened 2 years ago

barelyhuman commented 2 years ago

Continuation of https://github.com/barelyhuman/babel-plugin-mutable-react-state/issues/5

barelyhuman commented 2 years ago

@orenelbaum, PR for the same, you can use a locally linked version to test the syntax, do check the test version of the code for the basic usage

orenelbaum commented 2 years ago

From the test it looks like you only added the $mut function.

barelyhuman commented 2 years ago

Yeah, for now the changes is that it doesn’t change the variable name , it would change $a to [a,setA] for the initial declaration

the $mut function allows passing in additional val, setter pairs that might not be from useState, the test just uses it as an example

reads are from $a[0] and write are on $a[1] instead of a and setA

I need additional tests to see if the passing down of these works as expected

barelyhuman commented 2 years ago

Diffs , yes there's no change other than the $mut function on the written part, basically just changed the output to match with your target syntax. also the $mut is already typed so that solves the smallest problem for typescript that was mentioned.

moving ahead would be the handling of destructuring of props and usage of reactive values that come as parameters

Written code diff

import * as React from "react";
import {$mut} from "mute";

const createState=(x)=>[x,setterForX]

function Component() {
    let $a = 1;
-   let $b = createState(1) //couldn't even do this cause the plugin would create `useState(createState(1))`
+   let $b = $mut(createState(1));

    const onPress = () => {
          $a+=1
          $b+=1
    };

    return <>
        <p>{$a}</p>
        <p>{$b}</p>
    </>
}

Compiled code diff

import * as React from "react";

function Component() {
-   const [a, setA] = React.useState(1);
+   const $a = React.useState(1);
-   const [b, setB] = React.useState(createState(1));
+   const $b = createState(1);

  const onPress = () => {
-    setA(a + 1);
+    $a[1]($a[0] + 1);
-    setB(b + 1);
     $b[1]($b[0] + 1);
  };

  return <>
-    <p>{a}</p>
+    <p>{$a[0]}</p>
-    <p>{b}</p>
+    <p>{$b[0]}</p>
  </>
}
orenelbaum commented 2 years ago

It's really awesome that you are working on this. In general it's awesome that you're working on this plugin, but it's not the first plugin to introduce Svelte-like syntax to JSX. But usually with those Svelte like syntaxes, including Svelte (I think), you either can't pass those mutable state variables around or it's more verbose so it misses the point a little bit. I feel like those solutions encourage you to create all of your actions in the same component where you created the state and pass those actions around instead. The only solution I've seen where I feel like it makes sense to pass mutable state variables around is Marko, that's why I think that it's awesome that you're open to exploring how to make it work.

barelyhuman commented 2 years ago

Kinda made it work , not sure if it's close to the syntax you were expecting but it's the latest commit in the PR and the test case is named Reactive props passed around components and the snapshot of the same can be seen for the compiled output, i'm still trying to break it down but theoretically seems to be compiling fine

Links: Snapshot: https://github.com/barelyhuman/mute/blob/variation/array-setters/test/react/snapshots/babel.ts.md#reactive-props-passed-around-components Code: https://github.com/barelyhuman/mute/blob/8ae362ee895e222756863d1755eae18e383b1c1d/test/react/babel.ts#L44

orenelbaum commented 2 years ago

That's awesome. It seems pretty much like what I was thinking about in the receiving end. Are you also planning to implement what I suggested in the component passing the state? e.g.

<Component $count={$a} $count2={b} $count3={$c}/>

instead of

<Component $count={$mut($a)} $count2={$mut($b)} $count3={$mut($c)}/>

I'm still not 100% sure about this but I couldn't think about any significant disadvantages yet except for the fact that I'm not sure what a.$b = $c will do. I was thinking that it will compile to a.$b[1]($c[0]), but it would be a little awkward if I'm trying to just add $c to an existing object so I can pass it around. So maybe in this case we would want to do a.$b = mut($c) which is also a bit awkward IMO and ideally I'd want people to have the ability to skip the $mut function as much as possible. But if a.$b = $c compiles to a.$b = $c it's also a little bit awkward because it means that the $ prefix will behave differently in variables than it does in properties.

barelyhuman commented 2 years ago

So, I need some kind of pointer/notation to know that the variable is already reactive and it's not to be messed with, that's where $mut comes in, so while I can make

<Component $count={$a} $count2={b} $count3={$c}/>

this work, it'd be a hard added condition for react and not really a generalized solution since there can be cases where this wouldn't work. a.$b = $c for this to work though, I still have to add handling that if the $mut is assigned to something then that something is to be considered a reactive variable, which isn't done yet.

But overall point, getting rid of $mut would require us to make assumptions in the plugin which might not sit well in all cases and we'll have to add conditionals for each case, if you check the Identifier transform code right now it already hard check's it's parents a lot to make sure the wrong identifier isn't replaced.

Would just add extra work for us everytime a new spec is added to JS and we haven't handled it 😂

https://github.com/barelyhuman/mute/blob/8ae362ee895e222756863d1755eae18e383b1c1d/src/react/babel.ts#L75-L118

orenelbaum commented 2 years ago

it'd be a hard added condition for react and not really a generalized solution since there can be cases where this wouldn't work. What do you mean exactly?

I don't understand all of what you just said but I'm not suggesting completely getting rid of $mut, only supporting this syntax const a = { $b: $c } instead const a = { $b: mut($c) } which is kinda similar to what you already implemented with destructuring just the opposite direction and <Component $a={$b}/> instead <Component $a={mut($b)}/> which is pretty much the same thing just in JSX instead of JS. It can be a lot more concise if you pass mutable state around a lot, and it's not really overriding anything else you would want those syntaxes to mean. Right now it would only make sense to use this syntax if you're storing a value-setter pair as the value of a state (like useState([val, setter])), which is a weird thing that you probably won't ever need to do.

barelyhuman commented 2 years ago

<Component $a={$b}/> instead <Component $a={mut($b)}/>

doable just for the JSX props, yes

const a = { $b: $c } instead const a = { $b: mut($c) }

this I need to see how I'll be doing it, theoretically it's just adding it to the reactive variables array and also checking object props for reactivity instead of just variables.

Shouldn't be hard, but to cover all cases, I need apt test cases as well

orenelbaum commented 2 years ago

this I need to see how I'll be doing it, theoretically it's just adding it to the reactive variables array and also checking object props for reactivity instead of just variables.

It doesn't necessary mean that a.$b will be a reactive variable like $b. I mean technically you can make it so that a.$b acts like a normal variable and doesn't get compiled, at least until you destructure it or pass it to $mut. I'm still not 100% sure if it makes sense to make properties act like reactive variables as well. The only problem I see with it so far is that it adds complexity, but I feel like it's more consistent and will probably make the plugin feel more natural to use.

this I need to see how I'll be doing it, theoretically it's just adding it to the reactive variables array and also checking object props for reactivity instead of just variables.

I don't think that you have to track reactive properties, you can just assume that every prefixed property is reactive. In fact I think that you have to do that cause objects can be passed from other scopes.

barelyhuman commented 2 years ago

track reactive properties

not really tracking, the current code maintains a set of vars that are to be reactive, they are specifically for simple identifiers and not for object props, I will just have to add it for the obj props as well , or they won't be replaced when compiled

so if I don't do it

a.$b = 1 
// will not compile to 
a.$b[1](1) 

it specifically needs to the original variable right now, so in terms of the what I've written, I do have to add it to the array of properties I need to replace/compile

orenelbaum commented 2 years ago

I mean technically I don't see why you can't just transform them as you go, same for normal mutable state variables. For properties you can do it per reference if you assume all prefixed properties are reactive. For variables you can do it per reference. I might be missing something cause I haven't really looked into the implementation. But either way this seems like an implementation detail so I don't know if it matters too much.

Shouldn't be hard, but to cover all cases, I need apt test cases as well

If you're talking about edge cases, I haven't really thought about them yet.

barelyhuman commented 2 years ago

Yeah it's an implementation thing, I was thinking out loud with it.

If you're talking about edge cases, I haven't really thought about them yet.

that's okay, it's not like the library is going to have 1m downloads tomorrow, we can find them as we go

orenelbaum commented 2 years ago

I just released babel-plugin-reactivars-solid which implements those ideas. I figured it would be relevant to mention it here.

barelyhuman commented 2 years ago

@orenelbaum that looks nice, I got done with the JSX support that day, all that's left is the internal object props being detected as reactive, and we should have a featureset worth moving to the original plugin