solidjs / solid

A declarative, efficient, and flexible JavaScript library for building user interfaces.
https://solidjs.com
MIT License
32.05k stars 914 forks source link

Help Me Understand Documentation Holes #167

Closed ryansolid closed 2 years ago

ryansolid commented 4 years ago

I really would appreciate feedback here. It is so hard for me to see it since I've been so close to this for so long and I've developed apps in this manner for 10 years. I'm going to be the worst person to write the documentation since it is all so second nature for me. Yet, I am most likely going to be the person writing the documentation since I understand how everything works so well.

So can you please comment on anythings you found confusing about Solid as you've been trying it out, using it in your demo projects, integrating it with 3rd party libraries. And if you did find a solution can you comment on what you feel would have been the easiest way to describe it to you. Even if it takes me some time to compile all of this I think it would also highlight some early gotcha's for people trying the library.

I know I need to do a lot better here but this is a bit of a blindspot for me. Thank you.

framp commented 4 years ago

I think having a formal API generated from the codebase would be very helpful; sometimes it's hard to find which methods are available, what are they called or what types of values are accepted (I'm thinking about all the possible way to call setState). I found myself looping over the files in https://github.com/ryansolid/solid/tree/master/documentation until I found the answer

Given it's a typescript codebase, https://typedoc.org could be helpful, even if it's not the most flexible generator (it looks something like this on a project of mine: https://framp.me/frappe/docs)

My background is mainly React and derivates (from Classes to Fn/Hooks) + a fair amount of elm + very little vue. My surprises:

I probably would have described the system starting from reactivity and introducing the simpler primitive and then building up to state. I'll try to come up with some tutorial / writing on this and get back to you.

amoutonbrady commented 4 years ago

I know there's an open issue about SSR & SSG, but a more detailed documentation about it and how to set it up would be really nice. I think that's going to spark a lot of interest, especially if you manage to get it close to Marko/Svelte rendering.

ryansolid commented 4 years ago

@framp this is great stuff. The state naming is an interesting thing in the post Hooks world. See I started calling that beforehand because I was trying to emulate React Class Components state object. But now people are used to useState. That's a good point. I'm not sure I'd call signals state because that is just as confusing being functions. But it brings up a good point.

createEffect being separate is mostly for code size. It is the most used reactive primitive and is used everywhere in the views. The number of times I've wanted createDependentEffect are fewer but they do exist.

Yes I need to document create solid better.. It is just a fork of Create React App. I just pointed at CRA docs because that all applies but it's worth talking about that. For instance adding module.css as the extension I think does CSS modules out of the box(https://create-react-app.dev/docs/adding-a-css-modules-stylesheet/). The problem is there is so much in CRA that I don't know how it works. Of course I should just make that a lot clearer.

Thank you so much for this feedback.

@amoutonbrady .... hah.. you got me. This is purposefully cryptic right now since I'm making breaking changes even in patch versions. I shouldn't be but I don't want to bump the minor version to infinity while I work on this. I know how to get Marko/Svelte performance but it comes at real cost given the scenarios I want to support. I almost have to start over and build up again to get there. Basically they render a static string with no reactivity on the server this makes advanced patterns very limited. But there are different sorts of solutions once you accept that. I'm still profiling the cost of reactivity. So far seems I can get Vue like performance, but have to make a call whether that is good enough.

louisch commented 4 years ago

I can't really talk about what the experience of someone with no experience in other frontend frameworks would be, since I'm not that person, but as someone who has mostly only worked with React as well, coming to Solid when I see things that are named "Effect" or "State", I would really want to know either exactly how they differ to React effects and state, or if they are substantially different, it would be nice if they were named something else entirely.

I think the problem with the current explanation of Solid State in the README is that it isn't 100% clear exactly how effects or state work in Solid.

For example, React hooks execute every single render, or if you add state variables into the dependency array, only when those state variables get updated. In Solid's README if you read the provided example code one might be able to deduce that the effect only executes if state variables used inside are set through their setters, but it would be good if this was stated explicitly in the explanation. Also, it still leaves a few questions unanswered, like "so does the effect ever execute if state variables inside are not set?", and "if one uses a specific path inside the state object, does the effect only execute if that path is updated? Or just whenever the setter is called?". Whichever is the case, a more explicit explanation would be nice.

You also don't explain:

The two linked introductory articles are a bit better, but explain by showing some amount of the implementation, which can be insightful, especially in this case where the underlying implementation isn't hugely complicated, but can also be confusing to some people. Generally speaking as well, while talking about implementation detail can be fine if it is explained clearly why they make things work they way they do, it introduces one more thing that a new user needs to understand, and is only useful if it helps them understand how to use the library, or helps them understand why the library is good/superior.

I also understand if the introduction on State is merely meant to motivate, not explain, but in that case I would expect a more thorough explanation on both State and Effect somewhere else in the official documentation (and not an article written on another site). And also, if the goal was to motivate, personally speaking, I don't see from the provided examples how the library is all that different to React, and if I was a beginner it wouldn't really occur to me why Reactivity is even that important in the first place. It's only through digging through other parts of documentation and the two articles that any of it clicks into place.

The Reactivity.md document starts by talking about Signals, but I hesitate to actually read about them, because 1. I'm not sure whether it's actually necessary to understand them, and 2. the actual explanation for what signals actually are seems to mostly be a very heavy two paragraph section titled "Accessors Reactive Scope" which should really have more code examples to demonstrate what you mean by things like "reactive scope" and "can be nested as many levels as desired". If signals are just implementation detail then I don't really want to read about them.

ryansolid commented 4 years ago

@louisch Thank you this cuts to the heart of it. I've had difficulty explaining the reactive update model without going into implementation. Any wordy explanation sounds like vague generalizations or like I'm selling magic. This always detracts from having a simple message.

I do see a couple commonalities here:

  1. Use of terms used in React even if React doesn't own the term (Effect, Memo) is generally confusing. I did choose them intentionally for familiarity, especially State. At minimum need disclaimers like the Suspense side.
  2. Unclear the role of State vs Signal. Emphasizing state initially undermines learning core concepts.
  3. More detail on mechanism of reactive rendering. And how it builds on top of reactivity basics.
junaid1460 commented 4 years ago

@ryansolid I think you should include this example shared state between multiple components

also how to use createEffect without running into issues

ryansolid commented 4 years ago

@junaid1460 did you remove another example? I actually liked that one. Since it showed something that I took for granted. Computations re-track dependencies on every execution. If you ever hit branching logic that doesn't depend on a reactive stat the other branch will never evaluate and the branch itself won't since it isn't being tracked. I think the execution cycle of reactivity tracking could be better explained.

As for the CodeSandbox example has a lot going on so I'm unclear what precisely you are trying to demonstrate. Generally I recommend following React-like patterns even if it is unnecessary. I find that code organization easier to understand the basics like props and eventually context. Again perfect example of where I'm assuming React knowledge.

It is possible to hoist out signals and state, but you don't want to do so with effects and memos since they will never be released. For that reason I strongly suggest following the Context pattern. I have examples about how that works and docs. But maybe I need a more of.. writing your first app etc.. Or like after a writing your first Component, a next steps.

I'm thinking this mostly boils down to having good tutorials. The examples are ok, the docs definitely have holes, but as much of it is they go too deep in areas a beginner doesn't care to as of yet. I need the documentation to be complete but there should be a path for people who don't care for those details. I introduced state early to let people coming from React try easy stuff almost as a drop in.

junaid1460 commented 4 years ago

Yeah, kinda changed it. with making two components almost similar except for one line. I was trying to explain how tracking works. May be you could include a simple example in a best practice doc

Also there's some other pattern like

passing JSX element directly in props might cause duplication

like for example

function Test({array, index}:{array: any[], index: number })  {
    return array[index]
}

function Root({index}: {index) {
    // Avoid this
    <Test array={[<MyBigComponent></MyBigComponent>, <i></i>]} index={index} />
}

function Test2({array, index}:{array: any[], index: number })  {
    return array[index]()
}

function Root({index}: {index) {
    // do this
    <Test2 array={[() => <MyBigComponent></MyBigComponent>,() =>  <i></i>]} index={index} />
}

This looks very minor issue, but this might cause mem leak while using external libraries like monaco or Pickr, also too many subscriptions to state if element uses state. I think a good dev tool for chrome can notice these issues and notify. May be I'll try to build one.

ryansolid commented 4 years ago

Is the confusion that all dynamic props are lazily evaluated? And:

<MyBigComponent />
// roughly equals
MyBigComponent()

Because I don't see an issue in the first case if that is what you are going for. Can you explain why don't do this? I also notice you use a lot of destructuring which shouldn't be a problem here but could lead to missing reactive triggering. I'm thinking that something even more fundamental is missing in the docs and following that assumption we've gotten out here somewhere.

Maybe there is a bug here that I'm missing as I expect the first scenario to be fine. Maybe my heuristic for dynamic wrapping is incorrect in these component cases? I feel like I'm missing part of what is trying be achieved here. Using onCleanup should regardless of how inefficient the rendering is (redoing work etc) should still release any side effects.

junaid1460 commented 4 years ago

I have migrated a project from react to solid, I noticed this with a tab component, during change, updates were triggered in two createEffects, out of which one is visible and another was just invoked by this <MyBigComponent />.

bobaekang commented 4 years ago

Haven't tried solid yet, but read a few articles by @ryansolid & documentation pages in this repo. Solid looks like a very promising project and I really want to see it gaining more traction among developers.

With that, I'd strongly recommend the project to have its official website with documentation before/with going v1.0. If you're already planning on that, please disregard the rest of this comment. 😅

As silly as it sounds, presentation matters. A lot. A nice, clean website to go with the documentation will attract new users and help to retain current users. This is especially true for those who are not as knowledgeable/interested in the underlying technologies but still curious about what the next big thing might be or simply looking for some great off-the-shelf tools to build applications with.

A link to the project website also looks much better than to its GitHub repo when spreading the word. Unfortunately, https://github.com/ryansolid/solid just doesn't give off an impression that this is more than an experiment.

Nothing too crazy--something as simple as https://recoiljs.org/ should suffice in my opinion. You already have all the materials.

Best of luck to you!

ryansolid commented 4 years ago

@bobaekang I agree completely. It is already in the works. I hope to get it in place soon. It is probably the biggest outstanding piece for 1.0. I wouldn't mind stabilizing on SSR a bit more and a bit of cleanup around Resource APIs for Suspense. I think starter templates could be improved too.

I appreciate the feedback.

junaid1460 commented 4 years ago

@bobaekang I agree completely. It is already in the works. I hope to get it in place soon. It is probably the biggest outstanding piece for 1.0. I wouldn't mind stabilizing on SSR a bit more and a bit of cleanup around Resource APIs for Suspense. I think starter templates could be improved too.

I appreciate the feedback.

@ryansolid could you please drop a link to repo here. I'd like to take that up.

ryansolid commented 4 years ago

Right now it's a private repo. I will likely change when I have something to show.

boogerlad commented 4 years ago

Can you elaborate on reconcile in documentation/state.md? Specifically, how does using reconcile differ from just plain setState? What concrete use cases are there for reconcile? User login? Can you provide some examples of the resulting state after setState with and without reconcile?

Does store.get('users') in the example return an array of objects with at least an _id field which is to be used as the key for diffing?

martinpengellyphillips commented 4 years ago

Just started looking at Solid (having found it via interest in Svelte). I'm most familiar these days with React (and Redux), but have used a bunch of approaches/tools over the years (including good ol' VanillaJS).

Solid looks very interesting, especially as I've hit some performance bumps recently with React in a side project. I know I can work around them, but I keep having this feeling that browers and JS are incredibly powerful these days and we are actually getting in our own way with a lot of our 'solutions'. So Solid's focus on being fast resonates a lot!

Regarding docs, some general points that would help folks like me jump in are:

Right now there is a ton of information that I've read through about Solid that's got me excited about it. But I also notice that I haven't yet tried playing with it because I dread having to make a bunch of decisions (even which 'starter' to use). Reducing those decisions by providing a strong opinion would help a lot here.

I looked over the realworld and hackernews source. I think it would be amazing to document the decisions made (and accompanying rationale) when implementing those - could be the basis of the tutorial? Are they recommended patterns for Solid usage?

By the way, the general explanation of why (along with the what and how) in the current docs/articles is very useful and something often overlooked in docs. Distilling these down into a summary of the tradeoffs made could be helpful to folks evaluating potential fit for their projects.

Lastly, a tie-in with an existing service can be a boon to adoption and visibility. I saw #100 and think that getting an article on their learning section could be a good idea. They are also a good example of real-world articles that get you into their product by showing how it works with other common products (like Auth0).

s0kil commented 4 years ago

Really like the idea of having a tutorial/tour page of Solid's features.

ryansolid commented 4 years ago

@martinpengellyphillips Yeah I did try to streamline at least getting started. The challenge is everyone has their own opinion. I have Create Solid App. And truthfully the fact that I wouldn't use my own starter probably says something. In general, I've found the process on tooling has generally been I suggest something and everyone points out how they have a different use case. This is a problem with the JS ecosystem in general. There is no one size fits all here. For larger projects people have more patience for bloat. I find most early adopters would rather start pretty minimalist so something like Create Solid is way overkill. But it also isn't there for them. I pictured most people would just start there as documented.

I tried to write an article for how to build TodoMVC but I feel it fell short. The real challenge is answering "why" without getting into very specific details of the reactive system. From my perspective most libraries gloss over this a bit. They just go, this is how you do blank which I think is probably sensible from an intro perspective if why makes the explanation 4 times longer.

Personally I think the single best way to try SolidJS without any commitment is modifying one of the over a dozen examples from CodeSandbox I link off the readme. That's how I learn. That's the poor man's version of tutorials. Which really just brings us back to the tutorial/guides which keep coming back in these comments. I'd love to be able to do something like that. But it starts with getting a REPL setup. Something nice that shows code running, as well as compilation output. That is pretty much the requirement to make those tutorials effective. I've had Solid compile in the browser before using Babel Standalone but this needs to be much better. With that I think the rest of this comes. I'm not sure if there are existing editor tools that can be used? I know Svelte built theirs from scratch.

I could make an issue around creating a REPL. It's just one of many things that will take some doing. As much as I'd like to do stuff like this to promote the library. I've resigned myself to prioritize things that only I can do at this point and things that others could help with like say Routing, creating a Website, or creating a REPL I'm going to leave out there for now. If anyone wants to help would be super appreciative. Otherwise I will get there eventually.

martinpengellyphillips commented 4 years ago

@ryansolid I hear ya!

I could make an issue around creating a REPL. It's just one of many things that will take some doing.

I personally don't think you need a REPL tutorial (it's a nice to have).

What I was thinking more about was some clearer sense of "Ryan's sitting down to start a new project with Solid - what do they do?". And I think that, as the creator, you totally get to give your opinion without worrying about satisfying everyone elses 😄

And maybe you don't use starter templates and just spin things up from scratch. If so, I'd say that in the intro/tutorial because it helps establish the culture ('lean and mean', 'back to basics', 'understand your tools', ...) and it also avoids folks spinning the wheels too much on 'am I doing this as intended?'

And truthfully the fact that I wouldn't use my own starter probably says something.

I totally clocked that and it was a stumbling block for me getting started because I wanted to know why doesn't Ryan use this? Can I do what they do instead?.

Personally I think the single best way to try SolidJS without any commitment is modifying one of the over a dozen examples from CodeSandbox I link off the readme.

I think that's a good way to try bits of Solid, but for me it's not a hook to use Solid. I need to build something with it and see how the pieces fit together. And that means I need low friction on getting started on my own project. As an aside, CodeSandbox is typically slow so not a great compliment to a project that focuses so much on performance 😉. Also, some of the examples (e.g. form) seem slightly broken and there is a lot to go through up front.

BUT, to stay true to my earlier point, if you think lots of examples via sandboxes is the way to go, then just stating that clearly will help folks get in the groove.

In the meantime, I'll take a punt at converting one of my existing apps to Solid and let you know how it goes.

BenAOlson commented 4 years ago

I'm not sure if I've missed this, but one topic that may be worth covering is how to generally approach the integration of 3rd party vanillaJS libraries with Solid (e.g. libraries for animations, drag-and-drop, etc.).

This is a topic that has been in the back of my mind while thinking about (and very much wanting) to use solid for an upcoming personal project. I don't know if I've overlooked or just failed to understand something, but there could be other dumdums like me that are also unclear as to what to consider and how to approach trying to use existing vanillaJS libraries with Solid.

ryansolid commented 4 years ago

That's fair. I think I assume too much React knowledge and don't provide any guides for this as it is a bit outside of what the library does. In one sense it's really simple, which is why it gets missed I think since:

// this is an HTMLDivElement
const div = <div />

In that it is pretty easy to do Vanilla JS since you just get a reference to the element and do what you would with it. You can also get refs via the ref bindings https://github.com/ryansolid/solid/blob/master/documentation/rendering.md#refs.

If you need to cleanup when your component is destroyed you can add an onCleanup call to clean up any side effects.

Albeit I don't have great examples. This thread might be of interest: https://github.com/ryansolid/solid/issues/39 I'd be interested in more concrete examples as a source for guides.

Myrdden commented 4 years ago

As kind of the culmination of the long chain of questions I had in #215, I think this line:

While you don't see them, everytime you write an expression in the JSX,
the compiler is wrapping it in a function and passing it to a createEffect call.

in the Reactivity docs could use a bit more elaboration. Reactivity in Effect and Memo are very clear, I've been basing my understanding of reactivity off of S, so those read to me just as S computations... But it's a lot more opaque when it comes to JSX, I think (Which is an issue I think I also have with Surplus). I originally interpreted this as meaning of "an expression in [the] JSX" as "a JSX expression" as in "All JSX is compiled to a createEffect call", which lead to the assumption that anything inside the body of a Component function would therefore be reactive, but from the conversation we had and the article you linked, I understand that's not the case. I'm assuming now that "an expression in JSX" is what the curly brace interpolation thing is called, and I guess I just never picked up on that. So, does this mean that any function that's ultimately invoked within JSX bracket whatevers is reactive? Does this apply universally, like in props and such? I think it would be useful for people who are prone to making way too many assumptions, such as myself, to explicitly lay out where the boundaries of reactivity are.

EDIT: I think I might just be parroting what louisch said above.

Also, when you say all dynamic props are lazily evaluated, does this mean that something like

<Component someProp={getSignal()}/>

Is not reactive, unless I were to use someProp within the Component? Or is the rule specifically to not do this, and pass getSignal to be called within Component?

ryansolid commented 4 years ago

@Myrdden Yeah it's all good. It just means they can be better. I made a lot of changes thanks to @louisch comments. But you bring up such an obvious thing yet not obvious to me since I am in it all the time. Those curly brace things are called JSX Expressions in the spec and the AST parsed. I actually named the underly DOM Expressions after this concept but it is in no way obvious since in JSX from a JavaScript perspective everything is an expression.

To answer your first question yes. Component execution bodies themselves are untracked, but every expression which can be reactive inside a JSX Expression is reactive. The compiler uses syntax analysis to determine this. Things that call functions or access members (like array or object property access) are treated like they are reactive. Function declarations, literal values, and simple identifiers that can never be tracked are not (it's a little bit more complicated than that but that's the message).

Second question. With components the compiler instead of wrapping each prop in createEffect only wrap these in functions and mark the property as dynamic. A dynamic property creates a getter on the props object at Component creation time. The reason you want to do that is to universalize the API. You could pass the getSignal which as a function could be accessed reactively with no extra wrappers, but it would be the child's job to decide if they had a signal or a simple primitive value. That is unacceptable from my perspective. So you are better to resolve it in the binding like your example and then props.someProp will only run getSignal() when you access the property, and if you bind with a non-reactive value like with a string value props.someProp will still hold the right value. So it's universal. But this clever little trick prevents creating excessive intermediate reactive primitives, unifies signal and POJO interfaces, and removes the need for an isSignal operator or any sort of special consideration for the Child Component based on what is input. There is a chance the child ends up wrapping a non-reactive expression in downstream JSX expression and it never updates but the cost is minimal unless it is the only expression for that whole template. But short story, don't worry about that.

Still I've struggled to explain this. Lazy evaluation is necessary so that access happens inside a reactive context as desired. But I understand it isn't clear what that means since I sort of hide it behind property access.

Myrdden commented 4 years ago

Neat, that totally cleared it up for me. My takeaway is basically that the assumption can be made that anything inside of Effect, Memo, or a JSX Expression is reactive, but I think that did a good job explaining why.

One more question, how exactly does destructuring work? Is it just "never use it with reactive things"? I understand that ({someProp}) => ... will break reactivity, but will (props) => const {someProp} = props; ... do so as well?

Actually, while we're at it, it might be useful to have a list of potential pitfalls documented somewhere...

ryansolid commented 4 years ago

Honestly I think this is the by far the biggest one. It's all variations on this topic of reactive context and reactive property access and tracking. All reactive libraries fundamentally work the same but since I don't do this at a Component scope it causes a lot of confusion. Unfortunately it is the thing that makes this approach interesting from performance standpoint. Why it is able to do the 1:1 updates that no other library even Reactive ones like Svelte can do.

It's much easier to just say never destructure. But the truth is for something to be reactively tracked we need to call a function. We can hide that function behind a getter or a proxy but we need to call a function while the reactive context is executing. So nothing is wrong with destructring as long as you are in the right place. Destructuring is accessing properties on an object and assigning them to a value. A value can not be reactive. So it matters where you access that property, so a destructure tracks like any other reactive access. But if you do it at the top of your file no one is listening.

I looked at Vue 3 docs for suggestions of how to explain this as they have the same problem in their setup function. But they just point to a helper that sort of destructures into a bunch of refs(signals). But it's verbose and expensive. I guess I could write simple function wrappers with explicit keys but it stops looking like destructuring. It becomes sort of self-defeating when the goal is to keep things terse. I could do it with a proxy maybe and keep the syntax tight. It wouldn't work in a rest parameter.

Like:

const {someProp} = toSignals(props);

// now someProp is a function and reactive when called.
someProp()

The request has come up a couple times now. Any thoughts on something like that? A bit more ergonomic perhaps than p.someProp?

We do have some other helpers like splitProps that I use for common cases where you want to spread portions of the props to different children and want to retain reactivity.

Myrdden commented 4 years ago

Personally I'm happy with "never destructure reactive props or else they'll break". I usually tend to use use p.someProp anyways, I just wanted to be clear on the behaviour. That said, I suppose a helper wouldn't hurt if one is really committed to destructuring.

Myrdden commented 4 years ago

Hello, another question... Maybe this goes in it's own ticket? Not sure.

I recall you saying somewhere, I don't remember where, that <Component/> is the same as Component(). If I assume this is universally true, will I run in to any issues? For instance, if I have:

const Component = () => <p>hello</p>;

const App = () => <div><Component/></div>;
// versus
const App = () => <div>{Component()}</div>;
// or even something like
const App = () => wrapDiv(Component());

will these all function exactly the same? Or is there some other logic to <Component/>?

EDIT: Forgot that I, as a programmer, posses the ability to look at other people's code. So I'm seeing createComponent when <Component/> is compiled, and this looks to be wrapping props as dynamic, and also memoizing the component itself? <div>{Component()}</div> just ends up as () => Component(), so is all of that work necessary as a JSX concern, and that tracking happens automatically given the expression is wrapped in a tracked function, or is there actually work being done in <Component/> that isn't with {Component()}?

I'd assume the answer to this is yes, but I figured I'd be thorough and ask.

ryansolid commented 4 years ago

Yeah I might even remove createComponent in the future out of the JSX output. Props are the difference. Effectively this is what is happening if I were to do it at compile time.

// consider:
<Component prop1="static" prop2={state.dynamic} />

// becomes:
sample(() => Component({
  prop1: "static",
  get prop2() { return state.dynamic }
}))

Does that makes sense?

Generally, ignore the memo it's for a special case in which the component returns a function. But I'm intending to solve that a different way in the future to avoid unnecessary wrapping.

Myrdden commented 4 years ago

Yeah, that functionality makes sense. The question was more, if I do

<div>{Component({prop1: static, prop2: () => state.dynamic})}</div>

or something similar, the end result is exactly the same, yes? From my messing around, it certainly seems to be.

EDIT: Oh, it actually does look like the compiled result is more or less the same.

ryansolid commented 4 years ago

Props expect to work on simple property access. so prop2 should be a getter so you can do props.prop2 not props.prop2(). This universalizes it. Secondly they should be in sample to avoid the Component render body being tracked. So what I wrote above would be the near equivalent. Technically since it returns a function and looks dynamic JSX compilation as you've written will create an effect that(using my code above) is immediately sampled. So it will function exactly same but add a few bytes of extra memory.

Myrdden commented 4 years ago

Hello again, noticed mutable functionality has been split off recently. Can produce be used with pathing? I.E. is:

setState('here', 'is', 'an', 'object', produce(obj => someMutableChanges);

valid syntax or does produce have to be consumed by setState directly? From the docs it looks like reconcile can be used in a path, but doesn't show produce being used in a similar fashion.

(Not sure if this counts as a documentation hole per se, but the documentation doesn't say anything about it, so...)

ryansolid commented 4 years ago

Oh yeah it can be on the path, any of the state modifiers work the same way. I can update examples. Thanks.

Myrdden commented 4 years ago

Also, would you say that using produce is the best way to delete keys? Or is there some way to null out keys or set them to undefined using setState? Will that break things?

ryansolid commented 4 years ago

You should just set them to undefined. You are right I don't mention that anywhere. https://github.com/ryansolid/solid/blob/master/packages/solid/src/reactive/state.ts#L106

If you need to remove items from an array provide a new array without it.

In general I don't necessarily wish to promote produce. It's an option for people who like that but generally the immutable forms are better and more performant.

ai commented 4 years ago

@ryansolid can I use Solid with another state manager? For instance, this state manager has state.subscribe(changes => …) API.

Real use case: can I use GraphQL library or my Logux with Solid without rewriting core to Solid State.

ryansolid commented 4 years ago

Yes. I mention this briefly but I have a reconcile function that will data diff immutable data structures and apply fine grained updates. You still use Solids state in your views but a minimal wrapper should take the immutable updates and convert them to more granular ones.

https://github.com/ryansolid/solid/blob/master/documentation/state.md#reconcilevalue-options

oleggrishechkin commented 4 years ago

@ryansolid how i should extend props?

for example, with React i do this:

const Component = ({ className, ...rest }) => <div {...rest} className={cx(className, theme.component)} />

//in parent jsx

<Component className={cx(theme.item, { [theme.active]: active )} onClick={handleComponentClick} />

How i can do this with solid-js?

I use signals and i loose theme.component class from Component when signal value changes

const Component = (props) => <div {...props} className={cx(props.className, theme.component)} />

//in parent jsx

<Component className={cx(theme.item, { [theme.active]: !!activeSignal() )} onClick={handleComponentClick} />

*cx - is clsx package

ryansolid commented 4 years ago

@oleggrishechkin I think you are looking for the splitProps helper: https://github.com/ryansolid/solid/blob/master/documentation/api.md#splitpropsprops-keyarrays-splitprops. You are correct identifying this needs special consideration. Hmm.. now that you mention it I only put examples in the release notes.

EDIT: Actually it's documented nowhere. Thanks. For quick reference:

const Component = (props) => {
  const [local, others] = splitProps(props, ["className"])
  <div {...others} className={cx(local.className, theme.component)} />
}
oleggrishechkin commented 4 years ago

@ryansolid Thanks you for quick answer! It works as i expect for first look.

So, I think you should add examples for API page with simple usage like you comment above

oleggrishechkin commented 4 years ago

@ryansolid How can i combine refs?

I provide ref to Component this way:

let ref;

...

<Component ref={ref} />

// I also want provide `Component` ref to another component and set `Component` `scrollTop`  directly from `OtherComponent`

<OtherComponent componentRef={ref} />

In Component I want set scrollTop (for example)

const Component = (props) => {
    let ref;
    setTImeout(() => {
        ref.scrollTop = 100
    });

    return <div ref={ref} />
}

But, how can i use both - ref from props and local ref?


React approach -

const ref = useRef(null);

...

<Component innerRef={ref} />
<OtherComponent componentRef={ref} />

...

const Component = ({ innerRef ) => {
    const divRef = useRef(null);
    const ref = innerRef || divRef;

    useEffect(() => {
        ref.current.scrollTop = 100
    }, []);

    return <div ref={ref} />
}

P.S. strange behavior

I need 3 setTimeout calls for change scrollTop...

timeoutId = window.setTimeout(() => {
        timeoutId = window.setTimeout(() => {
            timeoutId = window.setTimeout(() => {
                const scrollTop = scroll[props.name];

                if (scrollTop) {
                    ref.scrollTop = scrollTop;
                }
            });
        });
    });
ryansolid commented 4 years ago

Thanks for editting your issue I was so confused at first about what was going on. Part of what you are facing is part of my motivation behind #225 .

That being said if you have 2 components one which gets a ref and one that is passed a ref like:

let ref;
// get the ref
<Component ref={ref} />

// sends the ref
<OtherComponent componentRef={ref} />

In the first one that gets the ref you want to be able to set scrolltop as well? This might be the easiest. If defined components should get a props.ref that is function. I tried to explain this in the docs but not sure it's clear. https://github.com/ryansolid/solid/blob/master/documentation/rendering.md#refs

const Component = (props) => {
    let ref;
    setTimeout(() => {
        ref.scrollTop = 100
    });

    return <div ref={(el) => (ref = el) && props.ref && props.ref(el)} />
}

As for the setTimeout thing I'm not sure how 3 setTimeouts would do anything special. It is possible without setTimeout that the ref hasn't been connected to the DOM yet, but by the time the synchronous execution is done it should be connected. So I'm not quite sure how that would make any sort of difference. Is it some arbitrary time.. Like if you setTimeout 10ms does it also work?

oleggrishechkin commented 4 years ago

@ryansolid Thanks for your anwser,

Your solution works I tried this before write previous comment: ref={(el) => ref = el && props.ref(el)} when props.ref not provided It causing 'infinite loop', page not answered (I think it should throw an error like "undefined is not a function"). It's a real problem, errors not thrown. ErrorBoundary not catch this errors too.

3 setTimeouts it's my mistake, sorry. I forgot that data load asynchronously

lucaseverett commented 3 years ago

@ryansolid After reviewing the docs, I wasn't certain the best/correct way to explicitly set a dependency for createEffect.

So I looked through issues and I found an issue where you discussed this. https://github.com/ryansolid/solid/issues/27

I tried both methods you shared, but only one of them works.

This works:

  createEffect(() => {
    order.type;
    // Do this when order.type changes
  });

But this doesn't:

createEffect(() => {
    // Do this when order.type changes
  }, [() => order.type]);

I actually prefer the second way as it's more similar to React/Vue Composition API, but in my testing only the first example works.

ryansolid commented 3 years ago

Oh weird I must have copied and pasted from the React example. I am trying to remember if I ever supported deps that way with createEffect. I did have a special primitive for it at one point(createDependentEffect) but it was only for one type of computation so I ended up removing it. I have createMemo, createComputed, and createEffect. The second argument in Solid's computations are the initial value which let's you perform reducing operations. Technically the deps are figured out at runtime so it adds extra code for everyone not wanting to be explicit.

I wasn't aware Vue did that. I haven't seen any examples where they use explicit dependencies. The advantage of explicit dependencies is to not track random things you don't expect. I use untrack for that. I'm not sure it makes sense to add this on all computation types etc as a 3rd argument. Or just rely on the untrack. Given my usage definitely the latter but years ago I had a different opinion.

lucaseverett commented 3 years ago

In Vue it can be accomplished with the following.

watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

https://v3.vuejs.org/guide/reactivity-computed-watchers.html#watch

ryansolid commented 3 years ago

Thanks. Yes I see. Right now my signature for the core computations are:

// derive a value
function createMemo<T>(fn: (prev?: T) => T, initialValue, comparator): () => T

// synchronize before render
function createComputed<T>(fn: (prev?: T) => T, initialValue): void

// after render
function createEffect<T>(fn: (prev?: T) => T, initialValue): void

I'm not sure if I can override these reasonably. I mean it's easy to create this watch behavior it's just extra. I mean we have all the tools here. The whole render engine is built with the same APIs the end user uses. I could make it an modifier I suppose:

function on(deps, fn) {
  const isArray = Array.isArray(deps);
  return (prev) => {
    let value;
    if (isArray) {
      value = [];
      for (let i = 0; i < deps.length; i++) value.push(deps[i]());
    } else value = deps();
    return untrack(() => fn(value, prev));
  }
}

// And then we could use it like:
createEffect(on(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
), initialCount);

Seems reasonable to me. I have a whole library of similar modifiers in solid-rx and it would work with any of the build in computation types. You could use it with createMemo or even so more specific ones like createDeferred. It also doesn't push extra code on people who don't use the feature (we really aggressively tree shake around here). We could even change up the signature a bit to use rest parameters so you wouldn't need to pass an array ever and the last function would be the reaction. We could also do the same with the arguments making the last the previous value.

Does that seem interesting or useful to anyone?

aminya commented 3 years ago

My suggestions for the docs:

ryansolid commented 3 years ago

@aminya Thank you. A lot of good suggestions. We are working on the website/docs site. But i especially appreciate the detailed suggestions regarding giving examples with API, and component converting guide.

We've recently been talking about how to better showcase the community projects. There are a couple ideas floating around but are pretty ambitious. Initially it might be nice to move links from the project resources docs to a dedicated repo.

thatcort commented 3 years ago

Many of these have been mentioned above, but I'll repeat again as a way of showing interest. Also, I'm just getting started with the library, so they could be entirely wrong:

davedbase commented 3 years ago

This is really good feedback @thatcort. @ryansolid we're in a place with the website to apply these details and requests. The infrastructure is generally in place, however we need to dedicate time to re-structuring/sprucing up the markdown files.

Does anyone have suggestions about how the sections of the documentation should be laid out? It might be a good first step to deciding what needs to be written or re-organised.