Select to view content in your preferred language

React: Basemap Toggle Viewmodel mutates its input props

544
2
Jump to solution
10-20-2023 01:09 AM
JonnyDawe
Emerging Contributor

I have created a custom React hook so that I can utilise the Basemap toggle view model logic in another section of my application. I am following a pattern similar to that which is described in this repo: https://github.com/rslibed/2023DS-build-a-custom-ui-for-api-widgets-bookmarks/tree/master/src/Compon...

The issue I am seeing is that the hook I create infinitely re-renders because the view model mutates the properties it takes in. In this case I specifically see the nextBasemap property is being altered when I call new BaseMapToggleVM.

The only fix I can see is that I clone some of these objects within the custom hook, so that I don't directly pass them into the viewmodel, but it really surprised me that the esri logic would be so gungho about directly mutating the values I passed in. In fact it seems to break a very standard ESlint rule: https://eslint.org/docs/latest/rules/no-param-reassign

Am I missing something here? Is this a bug or just something I need to deal with/work around?

import BasemapToggleVM from "@arcgis/core/widgets/BasemapToggle/BasemapToggleViewModel";
import React from "react";
import Handles from "@arcgis/core/core/Handles";
import { watch } from "@arcgis/core/core/reactiveUtils";


export function useBaseMapToggleModel({ ...props }: __esri.BasemapToggleViewModelProperties) {
const [basemapToggleVM, setBasemapToggleVm] =
React.useState<__esri.BasemapToggleViewModel | null>(null);


const [state, setState] = React.useState<BasemapToggleVM["state"]>("disabled");


React.useEffect(() => {
const basemapToggleModel = new BasemapToggleVM(props);
setBasemapToggleVm(basemapToggleModel);


return () => {
basemapToggleModel.destroy();
};
}, [props]);


React.useEffect(() => {
if (!basemapToggleVM) return () => {};


const handles = new Handles();


addHandlers({ basemapToggleVM, handles, onStateChange: setState });


return () => {
handles.removeAll();
handles.destroy();
};
}, [basemapToggleVM]);


return {
state,
toggle: () => {
basemapToggleVM?.toggle();
}
};
}


interface HandlerSetup {
basemapToggleVM: __esri.BasemapToggleViewModel;
handles: Handles;
onStateChange: (state: BasemapToggleVM["state"]) => void;
}
function addHandlers({ basemapToggleVM, handles, onStateChange }: HandlerSetup) {
handles.removeAll();


handles.add([
watch(
() => basemapToggleVM.state,
(state: BasemapToggleVM["state"]) => onStateChange?.call(null, state),
{ initial: true }
)
]);
}
 

 

0 Kudos
1 Solution

Accepted Solutions
AddisonShaw
Occasional Contributor

Objects in effect dependencies are compared with Object.is(). Without taking a closer look at your example, I’d bet you aren’t memoizing the arguments before they are passed to your hook. Having arguments to a hook at all is kind of a dangerous pattern, you can safeguard in a hacky way by comparing my incoming props to a useReffed value of them with something like fast-deep-equals. If they are comparatively different, then you can mutate your hooks inner state and kick off subsequent effects.

View solution in original post

0 Kudos
2 Replies
AddisonShaw
Occasional Contributor

Objects in effect dependencies are compared with Object.is(). Without taking a closer look at your example, I’d bet you aren’t memoizing the arguments before they are passed to your hook. Having arguments to a hook at all is kind of a dangerous pattern, you can safeguard in a hacky way by comparing my incoming props to a useReffed value of them with something like fast-deep-equals. If they are comparatively different, then you can mutate your hooks inner state and kick off subsequent effects.

0 Kudos
JonnyDawe
Emerging Contributor

Thanks Addison - I made a slight change which was to not pass in the props into the new BasemapToggleVm function directly which looked to resolve the issue. Here is a working sample: https://codesandbox.io/p/sandbox/esri-infinite-hook-forked-nrkp39?file=%2Fsrc%2FBasemapToggleButton....

 

 

 

0 Kudos