Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lifecycles #23

Closed
titoBouzout opened this issue Jul 21, 2020 · 9 comments
Closed

Lifecycles #23

titoBouzout opened this issue Jul 21, 2020 · 9 comments
Labels
question Further information is requested

Comments

@titoBouzout
Copy link
Contributor

I was looking at the project I need to modify to use mobx-jsx and of around 300 classes the only thing I can see could complicate me is the lifecycle componentWillUnmount which seems to me I cant avoid it. I will give you examples:

A component that sets a MutationObserver to redraw a canvas when something is resized to then turn off the observer on componentWillUnmount
A component that use sockets, like in socket.on(......) and on componentWillUnmount socket.off(...)

There's seem to be a concept of cleanup via mobx, but I dont see how that will work per component, on the counter++ example I added two counters and removed one later on, the setTimeout kept running on the unmounted component. Also with mobx you can create a global state that isn't attached to any component which is very handy. On Solid you do something similar to cleanup but I guess is tuned to the component? Maybe is per file? I would prefer a componentWillUnmount lifecycle as in some situations I have many components on the same file and the intention will look more clear. Thanks in advance!

For completeness, I read on #11 that componentDidMount could be simulated by using Promise.resolve().then(() => /*... */); on the constructor or render function(maybe better on the render function).

@ryansolid
Copy link
Owner

Yeah this is one I find I'm always explaining because it is non-obvious until it is. cleanup should be what you are looking for. In Solid I call it onCleanup but it's the same thing. Essentially I leverage the reactive context to do all disposal.

If you picture your app basically a bunch of nested autoruns that track all the disposal methods of their child autorun instances, that upon being re-evaluated or cleaned up themselves they clean up their children you are basically there. If an autorun creates child autoruns any re-execution of the parent is going to create a new one, so the old one needs to be released. We are basically handling this the same way a MobX autorun clears all dependencies before each run to track them all over again.

Now if you are wondering about Components, it's the realization that if there is nothing conditional in selecting their rendering then they will live the whole life of the app. So they are tied to the topmost autorun. However, if there are conditions, then that will be it's own nested autorun scope that will only re-evaluate when the condition changes and handle it's own nested disposal. So basically you can register a cleanup method anywhere and it will run when the time comes based on the nested reactive tree. Components don't need explicit will unmount methods since registering cleanup will run when the parent cleanups or re-runs itself (perhaps changing a tab in the UI).

In so the best pattern for global state is Context like React since the Provider puts creation under the reactive tree.. MobX-JSX has context unlike React has no tie to the render mechanism in itself so there is no performance hit etc.. See it here: https://codesandbox.io/s/mobx-counter-context-wlu1x

So the mentality here is Components are ephemeral. They serve as factory functions that maintain state by the closures created in MobX's reactive computations. So lifecycles don't make sense. In the purest sense all bindings are side effects of these computations. Those side effects consist also of nested computations which are all managed as part of each computations own lifecycle. There are no lifecycles outside of that. But since it isn't tied to a components the reactive primitives are fluid between global and local state. It's just the same pattern all the way down.

Does that help?

@titoBouzout
Copy link
Contributor Author

Thanks for so detailed explanation. I sort of understand the concepts you're talking about but Im having a very hard time to put them in practice. I understand a components render function sort of becomes "many independent computations that will react to change on their closures" like some sort of virtual subcomponents.

Taking the repo examples as a use case, I make an App that has Counter1 and Counter2, Counter2 is removed from the tree after some seconds but the setTimeout on Counter2 keeps running https://codesandbox.io/s/mobx-counterclasses-2tfn8 and I cant figure out a way to remove it. Do you mean I need all the Context/Store boilerplate to get the disposal working? Thanks again

@ryansolid
Copy link
Owner

ryansolid commented Jul 22, 2020

Yeah something is broken here. cleanup is never getting called. Let me look into it and fix it up. Sorry I don't have good tests for this library specifically nor use it all that much so anything that doesn't come up in a benchmark possibly could get missed. Especially Class Components. MobX is my only library I built support for because of people liking decorators etc.

@ryansolid
Copy link
Owner

Ok Fixed in v0.11.10. Ended up being just poorly written code which was sharing a reference. Sorry about the confusion, I probably broke that beginning of June and didn't notice until now.

@titoBouzout
Copy link
Contributor Author

titoBouzout commented Jul 22, 2020

Thanks a lot! I have updated the example and wrote a class that implements componentDidMount and componentWillUnmount
https://codesandbox.io/s/mobx-counterclasses-2tfn8?file=/index.js
I believe this will be enough for me although Im not sure if this will work consistently(if did is really did and will is really will) but that's just a matter of tweaking it.

import { render, cleanup, Component } from 'mobx-jsx'

class Component2 extends Component {
	constructor(props) {
		super(props)
		if (this.componentDidMount) {
			console.log('adding componentDidMount')
			Promise.resolve().then(() => this.componentDidMount())
		}
		if (this.componentWillUnmount) {
			console.log('adding componentWillUnmount')
			cleanup(() => this.componentWillUnmount())
		}
	}
}

class App extends Component2 { componentDidMount(){ }  componentWillUnmount(){ } } 

@ryansolid
Copy link
Owner

ryansolid commented Jul 22, 2020

Yeah I think that is right. The microtask resolution will be after mount. And the cleanup runs at the beginning of re-evaluation so the elements aren't removed until the replacement elements are inserted so they are still attached to the DOM at that point.

The only place this falls apart is if something upstream decides not to insert its children after deciding to create them. props.children evaluation is lazy so its unusual situation but theoretical possible. The biggest example I can think of for this is Suspense where we might create a subtree but not have attached it to the actual DOM tree. I haven't made any implementations like that in this library so far, but worth mentioning.

For the most part I think even componentDidMount is not needed as most things just need refs to elements etc and not all need to be attached to the DOM but there are definitely a few cases for it. Unlike React render only runs once so you can put pretty much all your initialization code in there. I have to admit my class component approach is a bit of mock abstraction. Reallly the function components make the most sense here but I know people aren't used to using MobX this way.

@ryansolid ryansolid added the question Further information is requested label Sep 7, 2020
@ryansolid
Copy link
Owner

I thought I'd update this to tell you where I took this with Solid. I added features like Suspense and concurrent mode and it was problematic that we'd never know when things are mounted. My old set timeout falls apart hard. So I did figure out how to do this in an interesting way that is DOM oblivious but understands when reactive contexts have settled for the first time.

The challenge is it puts me a crossroads because if I'm using hook like primitives I'd need to introduce things beyond current MobX API to represent them. It basically becomes a framework on its own.. like if I made an effect hook that runs after render and you use autorun for things that are immediate etc.. It'd take a lot of meddling to go down that path. So that might just be the limit of these offshoot libraries.

If I wanted to bring them up to parity I'd just be aping Solid's APIs and they'd stop being what they are more or less which is basically the end of this experiment. I could look at making Solid's reactive core itself pluggable but I don't have full control over every reactive system to work the way I want. It would be cool cause you could use existing MobX stuff and it would be seamless but I'd almost be better to provide a light wrapper over Solid with MobX's API if people wanted to port the code.. People have done that with Vue and it would work pretty well for the 90% case. It's not unlike what you have been doing to port lifecycles over to MobX JSX> I suppose I could even have MST work with Solid MobX facade.

I don't really want to do that necessarily as I have great love for MobX and all other reactive libraries. But at a certain point the reactivity has to be customized to fit the problem space. There are certain things I can just do with my own reactive library I can't with others due to how they work. Or maybe I could if I could export some internals etc.. But it's considerable effort.

@titoBouzout
Copy link
Contributor Author

Hey many thanks for the time on the detailed follow up and your considerations. The following is just me on my own context.

I opened this issue originally because I needed a way to turn off "events handlers" (sockets/canvas/dom). The cleanup approach works ok and I translated it to a componentWillUnmount method just because I use classes in most situations.

Nonetheless, I grew up tired of all the confusing and closely related lifecycles provided by React, it led me from time to time to some bugs that were just annoying. I do not feel the need for other lifecycles than mount and unmount, I think everything else could be handled by just leveraging a state, in any case mobx already provides ways to intercept and observe.

In a closely related note I do not see the point for things like Suspend, Portals and friends, again you can handle these sorts of things leveraging a state and knowing what you are doing. If you have a bug near these sorts of utility Components you will not only have to understand your own code but you will also have to think of the internals of how suspend and friends works, so I avoid them.

I understand is fascinating to provide solutions for UI architecture, I feel like most solutions are overdoing and handling edge cases. This obviously may be good for companies but for mere mortals like me I end with an incredible amount of websites all of them using slightly different versions and concepts of closely related technologies, its overwhelming. I love mobx because I do not have to think how stuff works, I can concentrate into what Im actually building, I can just do @observable hidden = false; this.hidden = true; () => <div class={this.hidden ? 'hidden' : null}, so finding mobx-jsx was a blessing because I can avoid react madness and at the same time what you are doing here avoiding DOM diffing is just incredible great.

That being said I understand the need for detailed and extended solutions, solids.js looks great, I just feel I dont need the features. I tried to look at it but the syntax handling observables turned me off, if solid had provided the syntax I snipped on this post and classes I probably had been using solid by now, because everything is handled on the same library and because seems to be your main area of focus . Unless Im missing something my problem space is covered by what already is provided, split in two libraries but such is life, the simplicity seduces me. I wished mobx provided a renderer but it seems that them are also shifting paradigms and introducing more and more concepts, there must be reasons of course. Im done, Im just very thankful for what you are providing.

@ryansolid
Copy link
Owner

That's great. And I think that's sufficient then.

I love reactive primitives because I can model almost anything but some problems are difficult even for me so it's nice sometimes to give a syntax to make it just automatically handle it for you. But I'm also very particular about my approach with Solid otherwise I might just be using MobX in my core so I understand why it doesn't align. I've put more time into Solid specifically to worry about more complicated things like lifecycle (which a reactive library doesn't really have) and trying to tell a consistent story but it does complicate everything (in theory to allow certain things to be easier).

I'm definitely not primarily focused on the localized problem (like looking at the component file in front of me) when working through my decisions. You can tell a library like Svelte is but it has limits elsewgere. MobX JSX keeps things simple enough with enough room to branch out that I'm glad it exists. At one point this was really all my ambition for Solid really. It's just growing beyond that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants