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

Document the stores registry pattern #299

Closed
vbuch opened this issue Jun 23, 2022 · 12 comments
Closed

Document the stores registry pattern #299

vbuch opened this issue Jun 23, 2022 · 12 comments
Labels
documentation An issue requires changes in the documentation only enhancement New feature or request wontfix This will not be worked on

Comments

@vbuch
Copy link

vbuch commented Jun 23, 2022

Hi @avkonst and contributors,
Really hate when people start their silly questions with "first of all thank you for the great lib", but... it's true, so

First of all thank you for the great lib!

This is not a bug report but rather a (silly?) question. "Is it supposed to work this way?"

I have a Map-like state that is initialized as an empty object {}.
Keys are then added to it. state['new-key-1'].set('value');
Whenever that set above is done, all usages of state get rerendered.
I have created a simple repro here https://codepen.io/vbuch/pen/poaXYgZ?editors=0011 where you can observe the above.

Accessor1 is getting rerendered/called on every hit to the "Add more" button.
Accessor2 is not.

I would imagine that the access that Accessor1 does won't be tracked as usage hence won't force a rerender, but... I may be missing something.

Thank you in advance!

@avkonst
Copy link
Owner

avkonst commented Jun 23, 2022 via email

@vbuch
Copy link
Author

vbuch commented Jun 23, 2022

Thank you for the quick reply! It is indeed a hard one, I understand. Here is a usecase:

  1. I have items that are coming occasionally (ref: just as if the "Add more" button is clicked.
  2. I only want to show a selected one (ref: like 'SOMETHING THAT IS NOT EVEN IN THE REPO') in a component. (the rest may be shown in other instances of the same component or in other components).
  3. The items get occasional updates

I have thought of a possible approach that will not force rerenders but that requires duplicating the selected item in a separate state/member of state. That becomes tricky with the third point above.
I do think that the approach of having a repository that is the single source of truth would be optimal (ref: the way we've set up the whole example) but as it is not possible, do you have any suggestion on how we can work around that?

I've experimented with an array, but then I need to access repo.keys whenever I want to find an item, therefore I will again force render because I'm accessing keys.

If I go the state = {repo: {}, selected: {}} and then add to both, I have to make sure I keep both instances repo[id] and selected in sync and that is again... tricky.

I'm out of ideas, so will be greatful if you have some.

Edit: I think I figured out a solution. I am asking hookstate to be a repository, while that's not what it is. I would rather have a repo (think JS Map) that holds many State objects. Then a new item will come in that repo as map.set('new-item', createState(item)) and we're back to simple basics. Map does what it's supposed to do and hookstate does what it does great.

I'm leaving the above part of my reply for history and resoning.

Thank you again!

Edit 2: This appears to be harder than I imagined even this way.

Edit 3: Here is a repro of the above mentioned approach: https://codepen.io/vbuch/pen/JjpQzeK?editors=0011

@avkonst avkonst added enhancement New feature or request hookstate-4 Marks issues which are solved starting from version 4. labels Jul 1, 2022
@avkonst
Copy link
Owner

avkonst commented Jul 22, 2022

Thanks, very nice. yes, I used the repository of states in my projects. This approach works.

@avkonst
Copy link
Owner

avkonst commented Jul 22, 2022

Keep it open to update the docs how to create a repository of states.

@avkonst avkonst added the documentation An issue requires changes in the documentation only label Jul 22, 2022
@avkonst
Copy link
Owner

avkonst commented Jul 22, 2022

Also reminder from the above: "track if enumeration of keys or values of an object happened during render and rerender only in this case"

@avkonst avkonst changed the title Adding new keys to state forces rerender to all uses of nested properties Document the stores registry pattern (WAS: Adding new keys to state forces rerender to all uses of nested properties) Jul 23, 2022
@avkonst
Copy link
Owner

avkonst commented Jul 23, 2022

Also, somewhat duplicate of #51

@vbuch
Copy link
Author

vbuch commented Jul 25, 2022

I think another term was already used somewhere in the docs for that. Let me fetch it. Wasn't "registry", nor "repository", neither "dictionary".

Edit: Found it. This is referred to as "catalog of dynamically created and destroyed global states" in createState's lines.

@avkonst avkonst added hookstate-5 Features postponed for v5 and removed hookstate-4 Marks issues which are solved starting from version 4. labels Dec 19, 2022
@avkonst avkonst changed the title Document the stores registry pattern (WAS: Adding new keys to state forces rerender to all uses of nested properties) Document the stores registry pattern Dec 19, 2022
@avkonst avkonst added wontfix This will not be worked on and removed hookstate-5 Features postponed for v5 labels Dec 19, 2022
@avkonst avkonst closed this as completed Dec 19, 2022
@speigg
Copy link
Contributor

speigg commented Feb 14, 2023

Hi, thanks for the question. Setting new property to a value is equivalent
to adding a property to an object. And so it means the entire object is
changed, so all the nested properties which are used trigger rerender...

@avkonst I think I'm also running into this issue. Data structure is like this:

const stateMap = hookstate({} as {
 [id:number]: Array<Item>|null
})

when I do stateMap[id].merge(items) for a particular id, it causes other nested states to invalidate for other ids, which causes useEffect components to rerender and useEffect hooks to re-run for unrelated items.

I don't know that a non-tracked registry pattern works for me, since I need to be able to react to the registry itself having items added and removed.

@vbuch
Copy link
Author

vbuch commented Feb 15, 2023

@speigg , the way I went around that is:
I have const repo = new Map([itemId, hookstate(Item)]) and const items = hookstate([itemId]). So items will allow me hooking to the event of adding items and the repo will be the stores registry (as named in the title of the issue) with all the items' data.

@avkonst
Copy link
Owner

avkonst commented Feb 15, 2023

@speigg what do you mean by "it causes other nested states to invalidate for other ids" ?
changing one property by merging items to it's array value would make the whole array affected and cause rerender where this array is used. It should not affect other things which do not use the property with the same id

@speigg
Copy link
Contributor

speigg commented Feb 17, 2023

@vbuch yes, I'm landing on a very similar pattern. something like:

const existenceMap = hookstate({} as {[id:string]:true})
const stateMap = new Map<id, State<any>>()

This way, each item in the stateMap is tracked (via hookstate) in isolation, without affecting the others, and I can also reactively keep track of when they are added or removed via the existenceMap. It's a bit more bookkeeping, but I've got it all abstracted away behind other operations so no big deal.

It should not affect other things which do not use the property with the same id

@avkonst It's a relief to know that's the intention

changing one property by merging items to it's array value would make the whole array affected and cause rerender where this array is used. It should not affect other things which do not use the property with the same id

Hard to tell exactly what the cause is, but I am generally seeing undesired/unnecessary component invalidations and state proxies being updated. I might have to create an isolated test case to zero in on the exact mechanism that is causing this behavior, but the only other thing would be items being removed/added from a top level hookstate record, i.e., stateMap[id].set(items) where it was previously undefined, or stateMap[id].set(none). Would these operations cause nested items for other ids to invalidate?

@vbuch
Copy link
Author

vbuch commented Feb 17, 2023

@speigg adding a new key to const stateMap = hookstate({key1: 'duh'}) would consider the whole object changed. So stateMap.key2.set('blah') will force components using stateMap.key1.value/get() to re-render.

There used to be a table that showed updating what causes a render in where. I cannot seem to find that in the docs @avkonst

Edit: Here it is in version 3. Did a lot of this change?
https://github.com/avkonst/hookstate/blob/version-3/docs/index/docs/21-performance-intro.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation An issue requires changes in the documentation only enhancement New feature or request wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

3 participants