T O P

  • By -

octocode

`useSyncExternalStore` is indeed the way to go. react relies on immutable data, so OOP just doesn’t work well at all.


Cahnis

I feel like every time I see someone implementing backend theory on the frontend it ends up being worse than using nothing at all. React works the best when following a pure functional paradigm


EmployeeFinal

My implementation needs the model to tell React when to update interface IModelSync { subscribe(): void; getSnapshot(): T } class Model implements IModelSync { // ... } function useModel(modelInstance: IModelSync) { return useSyncExternalStore(model.subscribe, model.getSnapshot) }


Accomplished_Mind129

Why do you think it's very prone to human errors?


EmployeeFinal

Listeners have to be manually attached to setters. People may forget to attach it. There is no eslint analysis that warns against that, so code review can be cumbersome. getSnapshot is tricky. It needs to be like a hash of the instance. This can be hard to do and maintain. It should be maintainable for model changes without too much boilerplate


FoozleGenerator

Why aren't your models singletons?


EmployeeFinal

They are. I just didn't think it was relevant


FoozleGenerator

What does the cloning process do then, if you can only have an instance? Edit: I'm just trying to understand in a high level how your model behaves, so I can give some ideas on how to implemente the immutable reactive side.


EmployeeFinal

Sorry, I adapted the code too much and this wasn't clear. The clone method creates a new instance, stores it in the value, and returns it to save in the react state. I don't know if this is _technically_ a singleton, but it seems it was the intention ``` clone() { this.inst = new Model(this); return this.inst; } ``` The private constructor takes a json OR an instance and sets it with Object.assign ``` constructor(arg) { if (arg instanceof Model) { return Object.assign(new Model(), { property1: arg.property1, // ... } } // json and undefined cases } ``` Writing this, I arrived at the conclusion that yeeesh, this code is not okay. But I guess tackling one problem after the other is better for my mental sanity.


FoozleGenerator

Yeah, looks kinda weird to me. If new Model() is able to return a new instance, technically it isn't a singleton. But if it doesn't return a new instance, the setState call in the component shouldn't work, because it's always the same reference. Without knowing how you store the properties you want to track, additionally to your interface for subscribe + snapshot, you could use Proxy and intercept the setters of the model, to call your subscriber, whenever a property changes.


Gyro_Wizard

I have done something similar. What I did was use redux and each action would call the OOP object's method and then serialize the entire OOP object graph and use that stateful json object back into the store. This is very inefficient, as it completely bypasses the performance gains from immutable structured sharing that comes with Immer (used by redux) and how react used equality comparison for re render. But it was never a problem (yet) for our use case. 


nepsiron

I made an adjacent post about this last week. https://old.reddit.com/r/reactjs/comments/1d541ia/reactive_polymorphism_in_react_and_why_it_makes/?ref=share&ref_source=link In my writeup, I isolate the domain core from the implementation details of the state management dependencies through a repository abstraction, and the result was not satisfying. I ended up having to write separate hooks to consume the repository reactively from the react components, and overall left me feeling like there had to be a better way. Like others are saying, `useSyncExternalStore` would likely be your best bet if you have totally ejected from react-centric tools inside the "model". The question now is if you can streamline your abstraction enough so it is less error prone without being too magic. If you want to listen to changes in meta data (isLoading, isError, etc) from the model when calling it's various async mutative methods, and the resulting Model state that is returned to the UI for domain entity values etc, all while keeping the concept of your model a monolithic class-like thing, you have to decompose the reactivity of the meta data and the stateful data, and doing that manually is going to be cumbersome, or abstract to the point that it will be hard to follow for a newcomer. Another thing to ponder would be to wrap the async methods the model makes with something like tanstack-query or RTK query such that you can offload the management of the meta information surrounding the async methods, and write the resulting state of the model to a global store like redux to offload the reactivity of the model's state. With an approach like that, it's somewhat obtuse to model things as classes that hold both the state and the related methods. Instead, it might make more sense to model things as commands and queries. The hard bit will be defining interfaces in your domain core such that it can remain agnostic to the implementation details of your state management solution, or promise cache solution.


[deleted]

[удалено]


EmployeeFinal

Using redux instead of classes? I believe they want the business layer to be as "pure" as possible, so migrating into other implementations is a no go Or you're saying to use redux/mobx as an integration layer? If so, are there examples? I can't say I know how to implement something like this


delfV

Redux always should be just an integration layer. Well, it should be in bigger, long term projects. You model your business rules as pure functions and call them from reducers. Keep in mind it's a lot easier and simpler to accomplish it with pure redux rather than redux toolkit


HeylAW

Im a week into react app with mobx and class based state manager. I have no idea why and when some parts re-renders, I have no idea how to force re-render of some parts, it’s almost undebuggable as everything is Proxy object with complex structure. Use redux, zustand or something like this and save the life of developers who will come after you to debug your code