Hi, I'm trying to find a good way to trigger a side effect when a route is matched. In my case, the side effect is loading some data. Currently, I'm solving this by wrapping the rendered Component with a HOC (withExtraDataLoading) that loads the data in a React.useEffect call.
<Route exact path={"/app/lists/:ownerUsername"}>
{withExtraDataLoading(() => (
<CtrProvider>
<MainPage />
</CtrProvider>
))}
</Route>
but my code would look much better like this
<Route exact path={"/app/lists/:ownerUsername"} onEnter={loadExtraData}>
<CtrProvider>
<MainPage />
</CtrProvider>
</Route>
This latter version is better because I can get rid of the HOC, I don't need the React.useEffect call (that was inside the HOC), and finally, I can drop the anonymous function. Also, conceptually this version is better because the side effect has nothing todo with rendering, so preferably I would not mix these concerns.
I apologize if there is already an elegant way to do this, I tried hard to find one.
just a side comment here: HOC is quite a bit of bloat just for a hook; you could create a custom hook and use that in CtrProvider which just implements useEffect. Much less overhead as a general pattern vs HOC with explicit hook (not to mention this was one of the reason for hooks). One approach could be useRouteMatch (in a custom hook if you want it re-used) with a result and check that it matches in an effect. If so, load the data. But IMHO it seems it would be cleaner if an associated component/view loads it's necessary data. Otherwise, it seems a bit interesting only to load it when that component's path is rendered either way (and this can be accomplished with a re-usable hook as well), since components/view tend to be the impetus and the container for binding any data you will load that a user might interact with.
Yeah, you should be putting that kind of logic inside of your route component.
Since you're not passing any props, you don't need an HOC. You can just wrap it in a regular component that runs the effect.
Some points in response:
in my opinion, it's problematic to assume that side effects (of routing) can always be performed in render functions. I mean, yes, there is probably always a way to use render for that, but it seems like an invitation to mix up concerns.
in my example, CtrProvider is designed to be agnostic of data loading, so for the sake of the argument, please accept that CtrProvider is not the place where I'd want to put this.
the React Suspense documentation also suggests that it's an option to decide the loading of data in the router: "This poses a question of how do we know what to fetch before rendering the next screen. There are several ways to solve this (for example, by integrating data fetching closer with your routing solution)." EDIT: note that they are mentioning this as an option to avoid loading data in the Component.
(my main point) since the Routing code is so central to the application, I would like to make my Router code as expressive as possible. Ideally, I would read the Router code, and directly see what happens if a particular route is rendered. This means that I would prefer not to hide the "extra data loading" step in the render function of some component, but rather show it immediately in the Route. The current HOC is serving that purpose, but it's clunky.
@mnieber What you're saying makes sense, with one exception: correct me I'm wrong (to any dev maintainers), but it seems that as the hooks are fundamentally linked to react-router via a shared context, so we do have that connection if we use useRouteMatch 馃.
This does not have to exist within CtrProvider specifically as you can create a re-usable hook e.g. useCtrData -- personally, as an example, I tend to have a useAutoDataFetcher hook that I use in my apps which initiates redux actions (created via redux-wings), and this essentially can take a param for what action to run when component is loaded automatically. Things are then connected very loosely either way, and if done using hooks it is guaranteed that hings are not too hardcoded to components, especially class-based member functions which will likely not be the recommendation forever (for perf reasons, compiler optimization, multithreading, etc), vs a general pattern such as onEnter which could be interpreted as to say YMMV with your general software architecture -- it seems recently react-router is also trying to shift it's APIs towards hooks and introducing many conflicting paradigms may lead to confusion as it's so widely adopted with peoples YOLO attitudes to code base on their day jobs /at teams.
If you generalize your data loading into a hook that is consistent with your app, you don't need to type useEffect and add that bloat -- one of the cool things about hooks is re-usability. Obviously this approach isn't the correct way or something like that, just thought one could probably use even less boilerplate with a re-usable auto data fetching hook that takes a simple param and calls useEffect/etc than without.
[also I'm commenting more for curiosity/discussion on software patterns than to resolve this... not actually a maintainer and don't have a concrete op either way 馃槄]
Hi @rob2d, yes, we can now use hooks for side effects, but we should compare this to the traditional approach of using callback functions.
For example, html elements have onFocus in case the programmer wants to create a side-effect of receiving focus. Now imagine that it becomes possible to do this in a different way: when the contents of the div are rendered, we can detect that we just received focus, and produce a side effect there. I think this would obscure the code, it would not be an improvement :-). This is especially true when the side-effect has nothing to do with rendering. In that case, it means an unnecessary mixing of concerns.
I think we are wanting to know how to move beyond the Fetch-On-Render data retrieving pattern to the Suspense leveraging Render-as-you-Fetch. That would require that data fetching be supported separately as the rendering and that they occur in parallel.
Most helpful comment
Some points in response:
in my opinion, it's problematic to assume that side effects (of routing) can always be performed in render functions. I mean, yes, there is probably always a way to use
renderfor that, but it seems like an invitation to mix up concerns.in my example,
CtrProvideris designed to be agnostic of data loading, so for the sake of the argument, please accept thatCtrProvideris not the place where I'd want to put this.the React Suspense documentation also suggests that it's an option to decide the loading of data in the router: "This poses a question of how do we know what to fetch before rendering the next screen. There are several ways to solve this (for example, by integrating data fetching closer with your routing solution)." EDIT: note that they are mentioning this as an option to avoid loading data in the Component.
(my main point) since the Routing code is so central to the application, I would like to make my Router code as expressive as possible. Ideally, I would read the Router code, and directly see what happens if a particular route is rendered. This means that I would prefer not to hide the "extra data loading" step in the render function of some component, but rather show it immediately in the
Route. The current HOC is serving that purpose, but it's clunky.