-
Notifications
You must be signed in to change notification settings - Fork 37
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
What is the proposed pattern to implement "item details bundle"? #50
Comments
hey @abuinitski I have, in fact done this before, just haven't shared a helper for it. I think for that type of thing frequently the right level of abstraction is actually as set of state mutation helpers, that you just use in your reducer in your bundle that you hand-write. for example, I frequently have a set of helpers like:
Which do what createAsyncResourceBundle does internally. Then you key your reducer state by id reducer: () => {
if (type === STARTED) {
const existing = state[payload] || getInitialState()
return {...state, state[payload]: getStartingState(existing)
}
if (type === FINISHED) {
const existing = state[payload.id]
return { ...state, state[payload.id]: setFinishedState(existing)}
}
...
return state
} P.S. glad you're enjoying it! I've been pretty happy with it on several complex projects myself. |
Thanks @abuinitski for asking this question and @HenrikJoreteg for answering. I've been dying to see an answer to this question! To follow up how would one go about using createSelectors with the above solution and still take advantage of memoization at the hash item level? It's possible to not bind those selectors and just import them but I was hoping I wasn't missing something simple. |
@SahidMiller might not answer your question if you're not using React, but I'm thinking that in general two approaches are theoretically possible:
export default function ProductDetails({ productId }) {
const { product, productIsLoading, lastProductError } = useAsyncResourcesItem('products', productId, { eagerFetch: true} )
return <span>{product.name}</span>
} I think I'll write something like this now since I need it. It's react-specific so can't be a part of the base package – will publish as part of abuinitski/redux-bundler-hook This example doesn't seem pretty since it somehow ties state with the fact that a component is mounted, so I'm in doubtful about |
@SahidMiller @abuinitski the general question seems to be how do I pass in an ID for a specific item while still getting all the memoization / caching, etc. Generally the specific ID of the thing you're rendering is already "selected" somehow. Most commonly it's in the URL itself. So you have something like So, the easiest way is to make a selector that takes this into account, I usually do something like this: create a route in using Then you can have selectors that reference it, like this: selectActiveWidgetId: createSelector(
'selectRouteParams',
params => params.widgetId
),
selectActiveWidgetRaw: createSelector(
'selectWidgetsRaw', // whatever selector that selects the entire reducer with individual widgets keyed by ID
'selectActiveWidgetId',
(widgetsReducerState, activeWidgetId) => {
if (!activeWidgetId) {
return null
}
return widgetsReducerState[activeWidgetId] || null
}
),
reactShouldFetchWidget: createSelector(
'selectActiveWidgetId',
'selectActiveWidgetRaw',
'selectPathname',
'selectAppTime',
(widgetId, activeWidgetRaw, pathname, appTime) => {
// optionally specify this should only happen on certain pages
if (!pathname.startsWith('/widgets/')) {
return null
}
if (shouldUpdate(activeWidgetRaw, {now: appTime, staleAge: ms.minutes(5) })) {
return { actionCreator: 'doFetchWidget', args: [widgetId] }
}
}
) This way you can have a single widget reducer that stores state by widget ID like this along with the metadata about last fetches, etc. {
'47': {
data: { ... }
lastFetch: SOME_TIMESTAMP
...
}
} Hope this helps... |
@HenrikJoreteg actually that would really cover 95% of the cases. My 5% is unfortunately a bit different – I not only have multiple "items", but also multiple "current item group"-s 🤣 |
Thought on a moment that it still can be covered by this approach – but on another thought – not completely since views would still have to select data relevant to them on each render. |
@abuinitski views just "selectActiveWidgets" too. The same general approach works fine as long as you're tracking which widgets should be showing at anytime in some fashion, right? What am I missing? |
@HenrikJoreteg sorry – you are absolutely right. Why my brain ejected this from a list of valid approaches is because each leaf component will re-render each time when any item somehow changes, instead of only being re-rendered when it's own item changes. This should not be a "real" problem, but it just somehow triggers me. I have implemented a bundle like this and would want to publish it as a PR for It uses "expired", "stale" and "outdated". I assume "expired" does not bring any active effect to the bundle itself besides "isExpired" selector to become true. Others are connected in a following way:
This all seems a bit too complicated to me. I think it might be boiled down to just two items:
So in most cases any loaded data will softly refresh itself without any visible effects to the UI, while "expiry" is a harder case – like Instagram that would show you yesterday's results while loading fresh content, but if you open it without using for a month – it will start up with a clean sheet and a loading indicator. What do you think? |
@abuinitski hey!
This actually isn't a problem at all whenever any of the input functions to the In terms of the PR. I think I'd rather keep is an an external module, not because i don't think it's a good idea but because it feels a bit tangential. I'm actually contemplating doing the same with Thank you, though! I'd say just publish it as a separate npm module. |
Yep, and that's what I meant originally - I would not want them to re-render. The pattern itself assumes that leaf components would actually want just one of all the items – and really don't care about the others. Still, it doesn't change the outcome – bundle design you described earlier is clear, would work flawlessly, and rendering stuff is really a framework integration specifics that should not concern bundler at all. I'll publish the package, thanks! |
And voilà https://www.npmjs.com/package/redux-bundler-async-resources |
FYI guys, I have extended redux-bundler-async-resources with a lot of new stuff, and re-implemented Additionally, I just finally finished a hooks package for that so both can be transparently used with react's suspense and error boundaries. Just in case someone finds it useful. |
Background:
Main app page has a list of items that is on lower level managed by Async Resource Bundle, and on a higher by a custom bundle that controls lifecycle of this list – when it's fetched, invalidated etc.
Details page needs to also download item details, and we want to cache / expire / retry etc on individual item level – so we can navigate to one item, wait for it to download, then go to another item, and when going back to item 1 – to reuse cached version of it. Therefore we need a similar factory to
createAsyncResourceBundle
but which will be able to manage a collection of individual items (each item to be referenced by it's item identifier), managing their cache lifecycle independently.Is there a better/recommended way to do this? Would a PR with such implementation be welcome?
P.S. never had a chance to thank you for redux-bundler – it is an ultimate perfect solution and a genius approach to Redux and state management. Pattern of all my frontend engineering experience life :)
The text was updated successfully, but these errors were encountered: