nanostores/query

Is there a way of dynamically updating `createFetcherStore` inputs?

taktran opened this issue · 2 comments

Hi,

I'm trying to figure out how to update createFetcherStore inputs, when react props change, but can't quite figure it out.

Here is the code:

export const [createFetcherStore, createMutatorStore] = nanoquery({
  fetcher: (...keys: string[]) => fetch(keys.join('')).then((r) => r.json()),
});

const createStore = (tag: string) => () => {
  const store = createFetcherStore<{ content: string }>([
    `https://api.quotable.io/random?tags=`,
    tag,
  ]);
  return store;
};

function Quote({ tag }: { tag: string }) {
  const [$tags] = useState(createStore(tag));
  const { data, loading } = useStore($tags);

  return (
    <p style={{ marginBottom: '20px' }}>
      {loading ? 'loading' : data?.content}
    </p>
  );
}

when tag changes in Quote, createStore won't update and hence the query is not refetched. How would I do this?

Here is an example: https://stackblitz.com/edit/react-ts-bbwme2?file=App.tsx
Notice that the headings change, but the quote is not refetched when changed in the select box.

dkzlv commented

@taktran Hi there!

That's quite simple: the useState hack should rarely be used, and you should always remember that the underlying function (the return of createStore in your case) isn't rerun when props change, it's a factory function in useState that's only called once (twice in StrictMode during development). So props are not passed to the store automatically.

In your case the simplest solution would be to manually sync the state of the closure with the state of the component with an effect. Basically, you need to create a bridge between two reactive paradigms: the react one and the nanostores one. Treat the createStore function as a JS module that's scoped to a component instance. You can use all reactive features of nanostores there, lifecycle hooks and new stores as well!

export const [createFetcherStore, createMutatorStore] = nanoquery({
	fetcher: (...keys: string[]) => fetch(keys.join('')).then((r) => r.json()),
});

const createStore = (tag: string) => () => {
	const $tag = atom(tag);
	const store = createFetcherStore<{ content: string }>([
		`https://api.quotable.io/random?tags=`,
		$tag,
	]);
	return { store, changeTag: $tag.set };
};

function Quote({ tag }: { tag: string }) {
	const [{ store: $tags, changeTag }] = useState(createStore(tag));
	const { data, loading } = useStore($tags);
	useEffect(() => {
		changeTag(tag);
	}, [tag]);

	return (
		<p style={{ marginBottom: '20px' }}>
			{loading ? 'loading' : data?.content}
		</p>
	);
}

(it goes without saying that the best way to avoid all of this would be to put as much state as possible into JS; but in your case I think that's impossible)

I think, I answered your question, but if something's unclear feel free to reopen the issue 👍

Amazing, thanks for the speedy response! 🏎️

I've updated the stackblitz with the fix: https://stackblitz.com/edit/react-ts-qjlkoh?file=App.tsx