-
Thanks for the great lib, I am a happy user of zustand for half a year already. Now I am facing a case that I want to share some refs among different custom hooks. I know how to share refs among components using forwardRef, but I am not clear how to share them amongs hooks. I thought of using zustand, but I prefer not using setters to change their values because I don't want to cause a rerender. So is it possible to use zustand to store refs so they become global? |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 7 replies
-
Essentially, it's just a JS variable, so you can store anything. |
Beta Was this translation helpful? Give feedback.
-
I tried, it doesn't work. It gives The initialization of the store (contains useRef) failed, which makes sense. If I understand correctly, the store initialization is outside of React context causing useRef failed. |
Beta Was this translation helpful? Give feedback.
-
Here is my store: import { fabric } from 'fabric';
import React, { useRef, createContext } from 'react';
import { createStore, State, StoreApi } from 'zustand';
interface StoreData extends State {
lastPosition: React.MutableRefObject<fabric.Point>;
origPosition: React.MutableRefObject<fabric.Point>;
isPanning: React.MutableRefObject<boolean>;
isDrawing: React.MutableRefObject<boolean>;
isEditing: React.MutableRefObject<boolean>;
isObjectMoving: React.MutableRefObject<boolean>;
}
const StoreDataDefault = {
// lastPosition is relative mouse coords on the viewport,
// e.g. top-left is always (0,0)
lastPosition: useRef<fabric.Point>(new fabric.Point(0, 0)),
// origPosition is absolute coords on the canvas,
// e.g. top-left of image is always (offset.x, offset.y)
origPosition: useRef<fabric.Point>(new fabric.Point(0, 0)),
isPanning: useRef<boolean>(false),
isDrawing: useRef<boolean>(false),
isEditing: useRef<boolean>(false),
isObjectMoving: useRef<boolean>(false),
};
interface Store extends StoreData {}
const store = createStore<Store>((set) => ({
...StoreDataDefault,
}));
const StoreContext = createContext<StoreApi<Store>>(store);
export {
Store as ListenerStoreProps,
store as ListenerStore,
StoreContext as ListenerStoreContext,
}; My usecase is that I have multiple custom hooks that each will return a set of fabric canvas event listeners, and all these listeners from different hooks share states e.g. Yeah, i think put ref inside the store is not a good practice. |
Beta Was this translation helpful? Give feedback.
-
import { fabric } from 'fabric';
import React, { createRef, createContext } from 'react';
import { createStore, State, StoreApi } from 'zustand';
interface StoreData extends State {
isPanning: React.MutableRefObject<boolean>;
}
let isPanning = createRef<boolean>() as React.MutableRefObject<boolean>;
isPanning.current = false;
const StoreDataDefault = {
isPanning
};
interface Store extends StoreData {}
const store = createStore<Store>((set) => ({
...StoreDataDefault,
}));
const StoreContext = createContext<StoreApi<Store>>(store);
export {
Store as ListenerStoreProps,
store as ListenerStore,
StoreContext as ListenerStoreContext,
}; |
Beta Was this translation helpful? Give feedback.
-
Since React cannot capture the custom event dispatch without the direct reference Custom event becomes useless inside React because of the moment we need it is simply because we cannot grab the And this is something that seems like you can use I just created an idea here for anyone interests how to do it. example-store.tsexport type EventState = {
eventRef: RefObject<HTMLDivElement>; //<== change the HTMLDivElement to whatever fits your need
setEventRef: (eventRef: RefObject<HTMLDivElement>) => void;
};
export const useEventStore = create<EventState>()((set) => ({
eventRef: { current: null },
setEventRef: (eventRef) => set({ eventRef }),
})); dispatch-event-component(where you want to const DispatchEventComp = () => {
const { eventRef } = useEventStore();
const handleClick = () => {
eventRef?.current?.dispatchEvent(
new CustomEvent("custom-event", {
bubbles: true,
detail: { data: "whatever you want" },
})
);
}
return <div><button onClick={handleClick}></button></div>
} register-event-component(the ugly part) Note: const RegisterEventComp = () => {
const { eventRef, setEventRef } = useEventStore();
// for example if we want to wait for the complex item that only exposes the ref, let assume it's a HTMLDivElement here
const localRef = useRef<HTMLDivElement>(null);
const customFunc = useCallback(
(event: Event) => {
const { data } = (event as CustomEvent<{ data?: WhateverType }>)?.detail;
if (data) {
// do whatever you want
// maybe localRef.current.update(data); ?
}
},
[]
);
// wait until target localRef is ready
useEffect(() => {
if (localRef?.current) {
setEventRef(localRef);
}
}, [setEventRef])
// start to register event
useEffect(() => {
if (eventRef?.current) {
const eRef = eventRef.current;
eRef.addEventListener("custom-event", customFunc);
return () => {
// Remove the event listeners
eRef.removeEventListener("custom-event", customFunc);
};
}
}, [eventRef, customFunc]);
return <div ref={localRef}>whatever it is</div>
} |
Beta Was this translation helpful? Give feedback.
-
This is working quite well for us export const useLoginModal = create<LoginModalStore>((set) => ({
open: false,
setOpen: (open) => set({ open }),
ref: createRef<HTMLDialogElement>()
})); |
Beta Was this translation helpful? Give feedback.
Essentially, it's just a JS variable, so you can store anything.
I'm not sure if storing refs globally can go wrong, in terms of its usage.