4 ms·
I think I get what you mean (lmk if I don't). But there's a reason why libraries like redux can exist: React is _not_ a framework and doesn't impose many restri
by thatswrong0 5y ago
I think I get what you mean (lmk if I don't). But there's a reason why libraries like redux can exist: React is _not_ a framework and doesn't impose many restrictions on how you choose to structure your application.
My current company, for example, enforces a pretty strict architecture where core application state lives "outside" of React [0]. What page we're on in our SPA, for example, is determined by state in our page store. We don't explicitly rely on component mounts to fetch data - instead, when we switch to a certain page via some action (like OpenUserPage), and _that_ fetches the data needed for that page. When we navigate away from the page, we call CloseUserPage, which unloads that data.
We try to push all business logic into discrete use case based actions. Like you mention, we use local component state strictly for view state (e.g. is this menu open?). We try to use hooks like useInterval sparingly (I think we only use it for things like updating relative timestamps every minute). We do use setInterval in business logic though, where it's simpler to reason about, for stuff like polling.
In an ideal world, there'd be very little need for complex hooks, where most components are strictly function components that only re-render when state that they're subscribed to in the store changes. But the real world is messier, and making a beautiful SPA with animations and whatnot often requires tricky view state logic with hooks. And of course, you have to be careful to not re-render components unnecessarily in performance critical situations where you're rendering a lot of stuff.
But I think the sort of high level separation you're describing can and does exist, and IMO striving for that separation makes it much easier to understand how data flows through the app, and obviates the need of digging into component lifecycles to manage the state of the app.
[0] At the end of the day, it does live in a top level context, but we don't interact with that context directly from child components.
- wruza 5y agoBut afaiu, with state living outside of react, components have to be updated by hand? Or at least have state to be duplicated in them. I mean, if a control/table gets a mutating event, you have to update both outside state and internal setState({input}) as well, otherwise input wouldn’t appear. And when outside state changes by itself, there should be a way to forceUpdate() with props or maybe a direct setState(). Am I right? At least MobX does exactly that behind its fancy object properties. I mean, I’m not saying react is restrictive, it’s just tedious and doesn’t even try to help if you claim ownership over your state. you have to be careful to not re-render components unnecessarily in performance critical situations where you're rendering a lot of stuff Interestingly mithril’s author, the library that I use in place of react, suggested that for humongous doms it’s better to split it into sub-apps. In short, you make a shallow component that mounts another entire “app” into its div on its mount, so that updates do not propagate downwards (the main app simply doesn’t know about subapps). Otoh, events propagate as expected due to regular dom. Personally I find mithril’s approach more outside-state compatible, if you don’t do funny tricks with event bubbling. If you’re not familiar with it, it’s the same hyperscript/vdom, but re-render()s everything after any event related to dom it controls, unless you tell it not to in a handler. That is fully out-of-your-way approach compatible with anything. Animations, jquery components, you name it. I don’t get why people choose react, which seems to be the opposite of that. What do you think I’m missing here? And thanks for your time and the expanded reply! Edit: frankly, I wouldn’t even bother with react and would just use what fits, but its presence is overwhelming in a sense of both community and job options. If there is a way to use it without abstraction pain, I’d like to understand it.
- thatswrong0 5y ago> components have to be updated by hand Most state libraries provide a mechanism by which you can "connect" components to parts of the state you care about, so that they automatically re-render when that part of the state changes. New props come in, and the components can decide what to do with them. > Or at least have state to be duplicated in them. I mean, if a control/table gets a mutating event, you have to update both outside state and internal setState({input}) as well, otherwise input wouldn’t appear. And when outside state changes by itself, there should be a way to forceUpdate() with props or maybe a direct setState(). Am I right? So this is where things get murky, regardless of what library you're using. In the application I work on, our stores generally reflect the source of truth in the backend, and it's real-time so the stores can update literally whenever. This puts two-way data binding you see in other libraries out of the picture. So most of our forms use local state, because we only want the data store to be updated with the final result the end user actually saves. Interaction wise, it wouldn't make sense to submit it anyways until the user blurs it or explicitly saves it (plus it would be unnecessarily expensive to constantly be saving to the backend every keystroke). And by keeping the state local, if an update comes in from the backend on the entity we're editing in the form, we can notify you that "Hey, someone else updated this!" while still keeping our form state, and letting us make the decision as to whether we want to overwrite it. But we could also choose to just override the form state if we so choose.. we could just look at the "connect"ed state we care about, and if it changes, overwrite the local state in the form. That's generally not desirable though from an interaction perspective. > In short, you make a shallow component that mounts another entire “app” into its div on its mount, so that updates do not propagate downwards We do the same thing in our application. Although the top level component technically re-renders when our data store changes (because it's stored in a top level context component), we just have memoized components beneath that take 0 props, which therefore don't re-render even when the parent re-renders. And we re-use this same pattern through the application, creating separated subtrees that only re-render when data they care about changes. So I think of our application like this: either user interaction occurs (like a form save), or the backend updates. This changes our data store (which tries to reflect the state on our backend). This update causes the components that care about that specific piece of information to re-render. Their children may choose to re-render or not. And then we wait for more interaction / backend changes. This is where the notion of "unidirectional data flow" comes from - it's basically just a tight one-way loop that drives the presentation of our application. Forms do muck this up a bit with their local state, but that's something I think _every_ application has to think about handling I will admit I only learned frontend development / React properly at my current company (previous used a messy Backbone.js app), so there might be simpler / better ways at this point, but I find it very easy to reason about our application as a result of this pretty explicit data flow + our architectural constraints.