yornaath / batshit

A batch manager that will deduplicate and batch requests for a certain data type made within a window. Useful to batch requests made from multiple react components that uses react-query
https://batshit-example.vercel.app/
MIT License
121 stars 4 forks source link

keyResolver returns undefined #8

Closed tbowmo closed 5 months ago

tbowmo commented 1 year ago

If the keyresolver can't find a match for the key, it returns undefined, resulting in reactQuery throwing up.

Perhaps it should coalesce to null instead?

tbowmo commented 1 year ago

an example

you request ids [1, 5, 7] and backend replies with [{id: 1,...}, {id: 7,...}]

then keyResolver, which uses find, returns undefined for id 5, as it's not able to find it in the backend response. This breaks tanstack/query, which doesn't allow undefined.

Took me a while to find a bug with the above scenario..

yornaath commented 1 year ago

Actually that can easily be handled with a custom resolver:

const batcher = create({
  fetcher: async (ids: number[]) => {
    return fetchUsersByIds(ids);
  },
  resolver: (items, id) => items.find((item) => item.id === id) ?? null,
});
tbowmo commented 1 year ago

Yeah, I know it can be done with a custom resolver, but I also think that the one included in the library should not cause errors when used.

tbowmo commented 1 year ago

You could also just use

const batcher = create({
  fetcher: async (ids: number[]) => {
    return fetchUsersByIds(ids);
  },
  resolver: keyResolver('id') ?? null,
});

The issue is that default behavior can result in undesired side-effects when used with the libraries mentioned in the examples

yornaath commented 1 year ago

No your code isnt correct. keyResolver('id') ?? null will always resolve to the left hand assignment since its a function and not null or undefined. And even though this library is a good fit to use with react-query, its not its responsibility to return something specific that fits its opinionated behaviour.

The responsibility of the resolver is to map the discrete id passed to the individual fetch calls to the batch of returned items. The example I provided of a custom resolver where you need to return null instead of undefined when a id is missing in the returned batch works as intended. Here are some tests to verify:

test("handle undefined responses", async () => {
  const batcher = create({
    fetcher: async (ids: number[]) => {
      return mock.usersByIds(ids);
    },
    resolver: (items, id) => items.find((item) => item.id === id) ?? null,
  });

  const all = await Promise.all([batcher.fetch(2), batcher.fetch(100)]);

  expect(all).toEqual([{ id: 2, name: "Alice" }, null]); // PASSES
});
test("handle undefined responses", async () => {
  const batcher = create({
    fetcher: async (ids: number[]) => {
      return mock.usersByIds(ids);
    },
    resolver: keyResolver('id') ?? null,
  });

  const all = await Promise.all([batcher.fetch(2), batcher.fetch(100)]);

  expect(all).toEqual([{ id: 2, name: "Alice" }, null]); // FAILS
});

Where id 2 is user alice and id 100 does not exist.

yornaath commented 1 year ago

Yeah, I know it can be done with a custom resolver, but I also think that the one included in the library should not cause errors when used.

This is not an issue with this library, that is an up stream issue when consumed with react-query. Returning undefined when they key is not defined in the batch is more in line with js standards where array.find or someObject["missingKey"] is undefined when its not in the array/object, not null.

If you need to massage the data in your upstream consumption of it so that it fits your use that is your responsibillity and can easily be done with a custom resolver or fetch

tbowmo commented 1 year ago

Then could you at least write a note alongside your example in the readme file, about this potential pitfall in relation to tanstack query, now that you got an example for that one?

tbowmo commented 1 year ago

We ended up creating our own keyResolver, that takes a fallback argument, that it returns if the key cannot be found.

yornaath commented 12 months ago

We ended up creating our own keyResolver, that takes a fallback argument, that it returns if the key cannot be found.

Yes this is the correct approach for your use case. Why did you reopen this issue?

tbowmo commented 12 months ago

I still think that the documentation could be better in this regard, so it was an error that I closed the issue

yornaath commented 5 months ago

Just made index and key resolvers return null if they cant find items to make it more out of the box compatible with react-query. Makes sense to me since its the framework this package is mostly used within atm.

🦋  info Publishing "@yornaath/batshit" at "0.10.0"