When I hover over a geopoint marker the popover appears and then disappears infinitely and until I move mouse off of marker. My HandleMouseOver function should only show the popup once, I am unsure why it is looping through multiple times:
const handleMarkerMouseOver = (args: any) => {
console.log("arges", args)
setPopoverVisible(true)
setDoc(args.payload.doc_fields)
setAnchor(args.anchor)
setPopoverContent(args.payload.doc_fields['#service_address'])
}
All of code:
export function NewMap(props: any): ReactElement {
const [anchor, setAnchor] = useState(undefined)
const [popoverVisible, setPopoverVisible] = useState(false)
const [popoverContent, setPopoverContent] = useState(undefined)
const [doc, setDoc] = useState()
const [showVisualDrawer, setShowVisualDrawer] = useState(false)
const pageSizeInRedux = useAppSelector(selectPageSize)
const mapFormData = props
const mapData = useWaveWatcherEventsMapDataQuery({
index: props.index,
pageSize: pageSizeInRedux,
body: props.body
})
let dataLength = 1
if(mapData.data !== undefined) {
dataLength = mapData?.data?.buckets?.map((t: { doc_count: any; }) => t.doc_count).reduce((a: any, b: any) => a + b, 0);
}
let waveContent: ReactElement = <></>
if (mapData.isSuccess && dataLength > 1) {
const handleMarkerClick = (args: any) => {
setPopoverVisible(true)
setDoc(args.payload.doc_fields)
setAnchor(args.anchor)
setPopoverContent(args.payload.doc_fields['#service_address']);
}
const handleMarkerMouseOut = (args: any) => {
setPopoverVisible(false)
}
const handleMarkerMouseOver = (args: any) => {
console.log("arges", args)
setPopoverVisible(true)
setDoc(args.payload.doc_fields)
setAnchor(args.anchor)
setPopoverContent(args.payload.doc_fields['#service_address'])
}
let center: any = [0, 0]
const points = mapData.data.buckets.map((e: any, index: number) => {
center = e.location.split(',').map((c: string) => (parseFloat(c))) || utilityCoordinates
return (
<Marker key={index}
width={20}
color={Color.darkblue}
anchor={center}
payload={e}
onClick={handleMarkerClick}
onMouseOut={handleMarkerMouseOut}
onMouseOver={handleMarkerMouseOver}
/>
)
}
)
return (
waveContent = <>
<MapStats data={mapData} mapFormData={mapFormData}/>
<div className={styles.mapWrapper}>
<Map height={443} defaultCenter={center} defaultZoom={13}>
{points}
<ZoomControl/>
<Overlay anchor={anchor} offset={[0, 0]}>
<Popover
visible={popoverVisible}
content={popContent}
title={'Marker Details'}
>
</Popover>
</Overlay>
</Map>
</div>
This is the popover that appears and disappears infinitely:
This may or may not be helpful, but it seems related to a problem that I also just encountered today. My Popover would appear then disappear over and over. The culprit was the backdrop that loads (similar to how a modal backdrop loads in the background) so then my component would think I'm not moused over it anymore, then it would disappear, then it would think I'm on it again, reappear, etc... I eventually just used a MUI Popper instead of the Popover. Hope this helps.
Related
Description:
I am using Radix-UI and I want to create a service for creating toasts:
https://www.radix-ui.com/docs/primitives/components/toast
Problem:
I am unable to change the statte of the open-hook inside the object
How it works:
Any random component from anywhere in the app can add toasts to the store
The Toastr.tsx watches the store toasts
whenever a new object/ToastrItem is added it renders the toast
After 5 seconds (default value) onOpenChangeis called to set open=false through a hook. This does not work
Toastr.tsx
const Toastr = () => {
const { toasts, setToasts } = useToastrStore();
const eventDateRef = React.useRef(new Date());
const timerRef = React.useRef(0);
React.useEffect(() => {
console.log(toasts);
return () => clearTimeout(timerRef.current);
}, []);
React.useEffect(() => {
console.log('TOASTS:');
console.log(toasts);
}, [toasts]);
return (
<ToastProvider swipeDirection='right'>
{toasts &&
toasts.length > 0 &&
isArray(toasts) &&
toasts.map((toast: ToastrItem, idx: number) => {
return (
<Toast
open={toast.open}
onOpenChange={(openStatus: boolean) => {
//This does not work
toast.setOpen(openStatus)
}}
key={idx}
>
<ToastTitle>
{toast.title}
<h1>{toast.open}</h1>
</ToastTitle>
<ToastDescription asChild>
<time dateTime={eventDateRef.current.toISOString()}>{'TEEEEEEEEEEEEEEEEEEEEEEEEEEXT'}</time>
</ToastDescription>
<ToastAction asChild altText='Goto schedule to undo'>
<Button variant='green' size='small'>
Close
</Button>
</ToastAction>
</Toast>
);
})}
<ToastViewport />
</ToastProvider>
);
};
The piece of code that does not work is:
onOpenChange={(openStatus: boolean) => {
//This does not work
toast.setOpen(openStatus)
}}
My theory is that it is because of the generator i am using:
getToast.tsx
export const getToast = () => {
const toast = new ToastObject() as ToastrItem;
const [open, setOpen] = useState(false);
toast.open = open;
toast.setOpen = setOpen;
return toast;
};
class ToastObject implements ToastrItem {
id: string;
title: string;
description: string;
variant: ToastrVariant;
open: boolean;
setOpen: (param: boolean) => void;
constructor() {
this.id = nanoid();
this.title = 'test';
this.description = 'description';
this.variant = ToastrVariant.GOOD;
this.open = true;
this.setOpen = () => false;
}
}
ToastrItem.tsx
export interface ToastrItem {
id: string;
title: string;
description: string;
variant: ToastrVariant;
open: boolean;
setOpen: (parameter: boolean) => any;
}
What are you using getToast()?
I think I see the problem without seeing the usage. React state is immutable and can only be changed by calling setState(). This means that if you modify a value within the current state, React doesn't know that the state has actually changed. You need to instead copy the original state, update what you need, and then call setState again.
Since you're using zustand, I'll show how you would accomplish this using that. I haven't actually tested this code, but it's the correct paradigm for what you want to do.
const useToastrStore = create((set) => ({
toasts: [],
// addToast will update the toasts state by appending the new toast
addToast: (toast) => set(state => ({toasts: [...state.toasts, toast]})),
// setOpen takes a toastIndex and an open status
setOpen: (toastIndex, openStatus) => {
set(state => ({
...state,
// map through all toasts, find the one that matches toastIndex, and change it's open status
toasts: state.toasts.map((toast, index) => index === toastIndex ? {
...toast,
open: openStatus
} : {...toast})
})
)
}
}))
const Toastr = () => {
const { toasts, addToast, setOpen } = useToastrStore();
return (
<ToastProvider swipeDirection='right'>
{toasts.map((toast: ToastrItem, idx: number) => (
<Toast
open={toast.open}
onOpenChange={(openStatus: boolean) => {
//This does not work
setOpen(idx, openStatus)
}}
key={idx}
>
</Toast>
)}
</ToastProvider>
);
};
I am currently working with algolia and I have some strange issue with the map. I have this interface (left column is results, right column is map) like on this picture:
When I am trying to drag my map I get for a few seconds results, that I need, but for some reason they reset to previous results from searchboxes.My map code is like this:
const Marker = ({ hit }: { hit: any }) => {
const rHit = hit;
return (
<CustomMarker
key={rHit.objectID}
hit={rHit}
anchor={{ x: 0, y: (rHit.position - rHit.count) * 50 }} // if there are several markers at the exact same position
>
<ArtistsMarker hit={rHit} />
</CustomMarker>
);
};
const Map: FC<MapProps> = ({ fullscreen }): ReactElement => {
const [google, setGoogle] = useState<any>();
useEffect(() => {
if (window) {
setGoogle(window.google);
}
}, []);
const getKey = (hit: any): string => `${hit._geoloc.lat} ${hit._geoloc.lng}`;
const groupThoseWithIdenticalCoordinates = (array: any[]) =>
array.reduce((acc, value) => {
if (!acc[getKey(value)]) {
acc[getKey(value)] = [];
}
acc[getKey(value)].push(value);
value.position = acc[getKey(value)].length;
return acc;
}, []);
const addPositionForIdenticalCoordinates = (
array: any[],
grouped: any
): any[] =>
array.map((e: any) => ({ ...e, count: grouped[getKey(e)].length }));
return google ? (
<GeoSearch
google={google}
enableRefine={true}
enableRefineOnMapMove={true}
maxZoom={17}
gestureHandling={fullscreen ? 'cooperative' : 'greedy'}
>
{(value: any) => {
const grouped = groupThoseWithIdenticalCoordinates(value.hits);
const hits = addPositionForIdenticalCoordinates(value.hits, grouped);
const markers = hits.map((hit: any) => (
<Marker hit={hit} key={hit.uid} />
));
return (
<div>
<Control />
{markers}
</div>
);
}}
</GeoSearch>
) : (
<></>
);
};
export default Map;
Also I have noticed inside my console, that searchState object sometimes misses boundingBox object like this:
Maybe someone knows why can its happen or where should I search?
The issue was caused by filters. I filtered results by location, so thats why I was returned to previous location.
I'm rookie when it comes to react.
Bumped into problem when during handleDragEnd event in trello alike draggable board when wanted to put some logic:
const openCloseDialog = () => {
setOpenDialog(!openDialog);
}
const handleDragEnd = (cardId: number, sourceLaneId: number, targetLaneId: number, position: number, cardDetails: ICard): Boolean => {
if (sourceLaneId != targetLaneId) {
setDialogTitleContent(cardDetails.description)
setCardId(cardId)
setTargetLaneId(targetLaneId)
openCloseDialog();
return true;
}
return false;
}
This bunch of simple setState causes huge performance issue.
Definition of states:
const [openDialog, setOpenDialog] = useState<boolean>(false);
const [dialogTitleContent, setDialogTitleContent] = useState<string>("");
const [cardId, setCardId] = useState<number>(0);
const [targetLaneId, setTargetLaneId] = useState<number>(0);
When it comes to opening dialog then it's getting even worse.
Definition of Dialog component:
interface IProps {
title: string,
cardId: number,
targetLaneId: number,
openDialog: boolean,
openCloseDialog: () => void,
fetchCategories: () => void
}
export const DraggableDialog: FC<IProps> = ({
title,
cardId,
targetLaneId,
openDialog,
openCloseDialog,
fetchCategories
}) => {
return (
<>
<Dialog
open={openDialog}
onClose={() => openCloseDialog()}
aria-labelledby="draggable-dialog-title">
{`Do You want to move all payment records with following title?: "${title}"`}
<Button onClick={moveOneCard} color="primary">
Just that one
</Button>
<Button onClick={moveManyCards} color="primary">
All of them
</Button>
</Dialog>
</>
);
}
Am I doing some obvious mistake which I'm not aware of?
I am building a image slider in React, based on CSS vertical snapping. There are 2 ways to interact with it, either throught scroll vertically or click the navigation buttons. I am using the Intersection Observer API in a React useEffect() to detect the active item. However, I can't seem to get it right without any useEffect lint errors. Whenever I include the functions in the dependecy array as suggested by the lint, the active item isn't set when scrolling.
Am I using a React anti pattern or am I just missing something?
Live demo
Code:
const Slider = ({images}) => {
const [currentSlide, SetCurrentSlide] = React.useState(0);
const setSlide = (id) => {
SetCurrentSlide(id);
};
const moveToSlide = (id) => {
if(id > -1 && id < images.length) {
SetCurrentSlide(id);
}
}
return (
<StyledSlider id="slider">
<SliderWrapper items={images} setSlide={setSlide} currentSlide={currentSlide} />
<SliderNav currentSlide={currentSlide} moveToSlide={moveToSlide} maxItems={images.length}/>
</StyledSlider>
)
}
const SliderWrapper = ({items, setSlide, currentSlide}) => {
const containerRef = React.useRef(null);
const { ref, inView, entry } = useInView({
/* Optional options */
threshold: 0,
});
const handleSetSlide = (id) => {
setSlide(id);
};
const handleIntersection = (entries) => {
const [entry] = entries;
const activeSlide = Number(entry.target.dataset.slide);
if (!entry.isIntersecting || activeSlide === "NaN") return;
handleSetSlide(activeSlide);
};
React.useEffect(() => {
const observer = new IntersectionObserver(
handleIntersection,
{
root: containerRef.current,
threshold: 0.45
}
);
Array.from(containerRef.current.children).forEach((item) => {
observer.observe(item);
});
return function() {
observer.disconnect();
}
}, [items]);
return (
<StyledSliderWrapper ref={containerRef} >
{items.map((item, index) => {
return <SliderItem key={index} index={index} image={item} isActive={currentSlide === index} />
})}
</StyledSliderWrapper>
)
};
const SliderItem = ({index, image, isActive}) => {
const imageContent = getImage(image.url);
const imageRef = React.useRef()
React.useEffect(() => {
if(!isActive) return;
imageRef.current.scrollIntoView({behavior: "smooth", block: "center", inline: "center"});
},[isActive]);
return (
<StyledSliderItem data-slide={index} ref={imageRef}>
<GatsbyImage image={imageContent} alt={image.description} />
</StyledSliderItem>
)
}
So you've missing dependencies in the useEffect of SliderWrapper. You can simplify the code a bit as well.
SliderWrapper
Since nothing else calls handleIntersection callback other than the Observer you can safely move it into the useEffect callback body. This makes the only dependency the setSlide callback that's passed as a prop from the parent component.
const SliderWrapper = ({ items, setSlide, currentSlide }) => {
const containerRef = React.useRef(null);
React.useEffect(() => {
const handleIntersection = (entries) => {
const [entry] = entries;
const activeSlide = Number(entry.target.dataset.slide);
if (!entry.isIntersecting || activeSlide === "NaN") return;
setSlide(activeSlide);
};
const observer = new IntersectionObserver(handleIntersection, {
root: containerRef.current,
threshold: 0.45
});
Array.from(containerRef.current.children).forEach((item) => {
observer.observe(item);
});
return function () {
observer.disconnect();
};
}, [setSlide]);
return (
<StyledSliderWrapper ref={containerRef}>
{items.map((item, index) => (
<SliderItem
key={index}
index={index}
image={item}
isActive={currentSlide === index}
/>
))}
</StyledSliderWrapper>
);
};
Slider
The other issue what that you were memoizing the setSlide prop in the child instead of the parent where it's being passed down. This caused the setSlide prop to be a new reference each render and re-memoized via useCallback in the child. React useState updater functions are stable however, so you can directly pass them to children.
const Slider = ({ images }) => {
const [currentSlide, setCurrentSlide] = React.useState(0);
const moveToSlide = (id) => {
setCurrentSlide(id);
};
return (
<StyledSlider id="slider">
<SliderWrapper
items={images}
setSlide={setCurrentSlide} // <-- pass directly to child
currentSlide={currentSlide}
/>
<SliderNav
currentSlide={currentSlide}
moveToSlide={moveToSlide}
maxItems={images.length}
/>
</StyledSlider>
);
};
If you wanted to remain with the setSlide handler in the parent, here is where you'd memoize the callback so the parent is providing a stable reference. Note that this is only useful if memoizing non-useState functions.
const setSlide = React.useCallback(
(id) => {
setCurrentSlide(id);
},
[setCurrentSlide]
);
I have a simple useEffect hook in my Task:
const TaskAD = ({ match }: TaskADProps) => {
const { taskName } = match.params;
const [task, setTask] = useState<TaskData | null>(null);
const [loading, setLoading] = useState(true);
const authCommunicator = authRequest();
useEffect(() => {
const getTask = async () => {
const taskData = await authCommunicator
.get(`/task/${taskName}`)
.then((response) => response.data);
setTask(taskData);
setLoading(false);
};
getTask();
}, []);
if (loading || task == null) {
return <Spinner centered />;
}
const updateDescription = async (content: string): Promise<boolean> => {
const r = await authCommunicator
.patch(`/task/${task.name}/`, {
description: content,
})
.then((response) => {
console.log("Setting Task data!");
setTask(response.data);
return true;
})
.catch(() => false);
return r;
};
return (
<ProjectEntity name={taskName}>
<Space direction="vertical" size="small" style={{ width: "100%" }}>
<StatusRow status="Open" />
<TaskDetails task={task} />
<Description content={task.description} onSubmit={updateDescription} />
<Title level={2}>Subtasks:</Title>
<Table dataSource={dataSource} columns={columns} />
</Space>
</ProjectEntity>
);
};
Task object contains a description. The description is another component with a text area. The idea is: when a user changes the description in the child component, the child component has a function (passed via props) to update the description.
So I pass updateDescription to my child component (Description) via props. Both useEffect and updateDescription are in my Task component, the Description component is basically stateless. What happens:
user updates a description
child component calls the function, it updates a record in my DB
it gets the response from the API and calls setTask
task variable is passed to Description via props in Task's render, so they both get updated since the state of parent Task has changed
I see updated description
The only problem is that although it work, but when I do this, I can see this in console:
Setting Task data!
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
(i've added the console.log just to see when it happens).
So I wanted to ask if this is a problem of me having async calls outside useEffect or maybe something else?
#Edit Description code (I removed all the unnecessary junk):
interface DescriptionProps {
content: string;
onSubmit?: (content: string) => Promise<boolean>;
title?: string;
rows?: number;
}
const Description = (props: DescriptionProps) => {
const { content, onSubmit, title, rows } = props;
const [descriptionContent, setDescriptionContent] = useState(content);
const [expanded, setExpanded] = useState(true);
const [editMode, setEditMode] = useState(false);
const [descriptionChanged, setDescriptionChanged] = useState(false);
const editable = onSubmit !== undefined;
const resetDescription = () => {
setDescriptionContent(content);
setDescriptionChanged(false);
};
const changeDescription = (value: string) => {
setDescriptionContent(value);
setDescriptionChanged(true);
};
const descriptionTitle = (
<>
<S.DescriptionTitle>{title}</S.DescriptionTitle>
</>
);
return (
<Collapse
defaultActiveKey={["desc"]}
expandIcon={S.ExpandIcon}
onChange={() => setExpanded(!expanded)}
>
<S.DescriptionHeader header={descriptionTitle} key="desc">
<S.DescriptionContent
onChange={(event): void => changeDescription(event.target.value)}
/>
{descriptionChanged && onSubmit !== undefined ? (
<S.DescriptionEditActions>
<Space size="middle">
<S.SaveIcon
onClick={async () => {
setDescriptionChanged(!(await onSubmit(descriptionContent)));
}}
/>
<S.CancelIcon onClick={() => resetDescription()} />
</Space>
</S.DescriptionEditActions>
) : null}
</S.DescriptionHeader>
</Collapse>
);
};
#Edit2
Funny thing, adding this to my Description solves the issue:
useEffect(
() => () => {
setDescriptionContent("");
},
[content]
);
Can anyone explain why?