React Context - fix unecessary rerenders - eslint issue - reactjs

Eslint throw error:
The object passed as the value prop to the Context provider (at line
147) changes every render. To fix this consider wrapping it in a
useMemo hook
I have all my functions in callbacks, and also use a bit of useMemo however I sort of have no clue how to proceed with others to make my context clean and highly performance without unecessary rerenders, Would be grateful if someone could take a look and see how can it be improved to make error disappear and context work fine
const MapProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const [coordinates, setCoordinates] = useState<LatLngType>({ lat: 9.05789, lng: 29.92313 });
const [activeMarker, setActiveMarker] = useState<string>('');
const [directions, setDirection] = useState<DirectionResults>();
const { isLoaded } = useJsApiLoader({
googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS ?? ''
});
const mapRef = useRef<MapType>();
const options = useMemo<MapOptionsType>(() => {
return {
disableDefaultUI: false,
clickableIcons: false
};
}, []);
const onLoad = useCallback((map: MapType) => {
mapRef.current = map;
}, []);
const getCurrentPosition = useCallback(async () => {
try {
const coord = await Geolocation.getCurrentPosition({ enableHighAccuracy: true });
setCoordinates({
lat: coord.coords.latitude,
lng: coord.coords.longitude
});
} catch (error) {
console.log(error);
}
}, []);
const watchUserPosition = useCallback(async () => {
const watchOptions = { timeout: 60000 };
const showLocation = (position: Position | null, err?: any): void => {
if (err) return console.error('Map was not loaded', err);
if (position) {
const {
coords: { latitude, longitude }
} = position;
setCoordinates({
lat: latitude,
lng: longitude
});
}
};
await Geolocation.watchPosition(watchOptions, showLocation);
}, []);
useEffect(() => {
getCurrentPosition();
watchUserPosition();
}, []);
const moveToMarkerLocation = useCallback(
(marker_coordinates: LatLngType) => {
mapRef.current?.panTo(marker_coordinates);
},
[mapRef.current]
);
const openMarker = useCallback((marker: string) => {
setActiveMarker(marker);
}, []);
const closeMarker = useCallback(() => {
setActiveMarker('');
}, []);
const showDirection = useCallback(
(marker_position: LatLngType) => {
const service = new window.google.maps.DirectionsService();
service.route(
{
origin: coordinates,
destination: marker_position,
travelMode: window.google.maps.TravelMode.WALKING
},
(result, status) => {
if (status === 'OK' && result) {
setDirection(result);
}
}
);
},
[coordinates]
);
const endDirection = useCallback(() => {
setDirection(undefined);
}, []);
return (
<MapContext.Provider
value={{
isLoaded,
mapRef,
moveToMarkerLocation,
coordinates,
options,
onLoad,
openMarker,
closeMarker,
activeMarker,
directions,
showDirection,
endDirection
}}>
{children}
</MapContext.Provider>
);
};
Way to make error disappear is :
const mapValues = useMemo(
() => ({
isLoaded,
mapRef,
moveToMarkerLocation,
coordinates,
options,
onLoad,
openMarker,
closeMarker,
activeMarker,
directions,
showDirection,
endDirection
}),
[
isLoaded,
mapRef,
moveToMarkerLocation,
coordinates,
options,
onLoad,
openMarker,
closeMarker,
activeMarker,
directions,
showDirection,
endDirection
]
);
return <MapContext.Provider value={mapValues}>{children}</MapContext.Provider>;
But I'm not convinced if it solve the problem with performance eventho eslint error is gone

Related

React useState and useEffect does not work as expected

I have one state for storing an object and two useffects. The first useeffect occurs only once in the first render, while the second occurs whenever the 'toFetch' state is changed. In addition, has an initial value for toFetch that is the coordinates of Ankara and is stored in object. First, useffect determines whether the user has granted permission for geolocation. If allowed, it is set to fetch the current location; if it is not, it does nothing. This time, the second useffect works. and basically fetches data from the API and sets the "selected" state. However, when the user allows geolocation, the selected state continues to use Ankara coordinates. What is the problem?
Here is my Code
function Contex({ children }) {
let obj = { lat: 39.57, lng: 32.53 }; // coordinates of ankara
const [weather, setWeather] = useState([]);
const [selected, setSelected] = useState({});
const [toFetch, settoFetch] = useState(obj);
const [loading, setloading] = useState(false);
useEffect(() => {
const initialSetup = () => {
const sb = (pos) => {
const {
coords: { latitude, longitude },
} = pos;
settoFetch((prev) => {
return { type: "single", lat: latitude, lng: longitude };
});
};
const eb = (err) => {};
navigator.geolocation.getCurrentPosition(sb, eb, {
enableHighAccuracy: true,
});
};
initialSetup();
}, []);
useEffect(() => {
const den = async () => {
setloading(true);
let x = toFetch;
if (toFetch.type === "country") x = await getCountryStates(toFetch.name);
const weatherData = await dataProvider([x].flat());
setWeather(weatherData);
setSelected(weatherData[0]);
setloading(false);
};
den();
}, [toFetch]);
return (
<context.Provider
value={{
weather,
setSelected,
selected,
settoFetch,
loading,
toFetch,
}}
>
{children}
</context.Provider>
);
}
export { Contex, context };
Edit 1:
I tried to use geolocation as promise but it also did not work. Please help
function Contex({ children }) {
let obj = { lat: 39.57, lng: 32.53 }; // coordinates of ankara
const [weather, setWeather] = useState([]);
const [selected, setSelected] = useState({});
const [toFetch, settoFetch] = useState(obj);
const [loading, setloading] = useState(false);
useEffect(() => {
const initialSetup = () => {
const sb = (pos) => {
const {
coords: { latitude, longitude },
} = pos;
settoFetch((prev) => {
return { type: "single", lat: latitude, lng: longitude };
});
};
const eb = (err) => {};
navigator.geolocation.getCurrentPosition(sb, eb, {
enableHighAccuracy: true,
});
};
initialSetup();
}, []);
useEffect(() => {
const den = async () => {
setloading(true);
let x = toFetch;
if (toFetch.type === "country") x = await getCountryStates(toFetch.name);
const weatherData = await dataProvider([x].flat());
setWeather(weatherData);
setSelected(weatherData[0]);
setloading(false);
};
den();
}, [toFetch.lat, toFetch.lng]); // Using an object in your useEffect dependency array also causes the infinite loop problem.
return (
<context.Provider
value={{
weather,
setSelected,
selected,
settoFetch,
loading,
toFetch,
}}
>
{children}
</context.Provider>
);
}
export { Contex, context };

When i operate infinity scroll in the mobile view, the scroll goes to the top

I'm using IntersectionObserver to implement an infinite scroll.
It works well in desktop view, but in mobile view, the scroll moves to the top whenever the isVisible of the source code below changes.
This is not what I want to do.
const targetRef = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
const callbackFunction = (entries: IntersectionObserverEntry[]) => {
if (entries[0].isIntersecting) {
setIsVisible(true);
} else {
setIsVisible(false);
}
};
useEffect(() => {
const observer = new IntersectionObserver(callbackFunction, {
root: null,
rootMargin: '1px',
threshold: 0.1,
});
targetRef.current && observer.observe(targetRef.current);
return () => {
observer.disconnect();
};
}, []);
useEffect(() => {
if (!isVisible || cursor === null) return;
dispatch(getDrawings({ userId: Number(profileId), cursor: cursor }))
.unwrap()
.then((res) => {
const { newCursor } = res;
console.log({ newCursor });
setCursor(newCursor);
});
}, [isVisible]);
Can you help me solve this problem?

react hooks useState consuming object

I am not sure how to make it correctly so I can pass object to useState
const App = () => {
const [weatherData, setWeatherData] = useState({data: "", time: ""});
useEffect(() => {
axios.get(apiUrl).then(response => {
setWeatherData({...weatherData, data: response.data, time: timestamp});
});
}, []);
return <div>{weatherData && <Weather data={weatherData.data} />}</div>;
};
when I do the same just with useState() and setWeatherData(response.data) it works fine but I would like to add the time
Have you tried the following:
setWeatherData({
...response.data,
time: timestamp,
});
P.S. Let me know if I understood you correctly.
UPD
Other option, if you need to access the current state:
useEffect(() => {
axios.get(apiUrl).then(response => {
const timestamp = Date.now().timestamp;
setWeatherData((prevWeatherData) => ({
...prevWeatherData,
data: response.data,
time: timestamp,
}));
});
}, []);
Try this:
const App = () => {
const [weatherData, setWeatherData] = useState(null);
useEffect(() => {
async function fetchWeather () {
const response = await axios.get(apiUrl)
setWeatherData({data: response.data, time: new Date().getTime()});
}
fetchWeather()
}, [weatherData]);
return (
<>
{weatherData && <Weather data={weatherData.data} />}
</>
);
};

Separate functions which depend on each other

I am trying to clean up my code an separate into functions that only have one task.
In v1 joinDailyCo(url); was defined inside fetchUrl(). Now I tried to move it out with
const url = fetchUrl();
joinDailyCo(url);
However, as soon I do that, I get the error message:
Unhandled Rejection (TypeError): Cannot read property 'join' of
undefined
const Daily = ({ eventSlug, tableId }) => {
const classes = useStyles();
const iframeRef = useRef();
const dailyRef = useRef();
const joinedRef = useRef();
useEffect(() => {
// Join call
const joinDailyCo = async (url) => {
if (joinedRef.current) {
// This is needed due to it never returning if there wasn't a meeting joined first.
await dailyRef.current.leave();
}
await dailyRef.current.join({ url });
};
// Retrieve dailySessionId and meetingToken.
const fetchUrl = async () => {
try {
const {
data: { dailySessionId, meetingToken },
} = await api.get(
`events/${eventSlug}/space/tables/${tableId}/daily-auth/`
);
const url = `${DAILY_URL}/${dailySessionId}?t=${meetingToken}`;
return url;
// joinDailyCo(url);
} catch (error) {
Sentry.captureException(error);
}
};
const url = fetchUrl();
url && joinDailyCo(url);
}, [eventSlug, tableId]);
useEffect(() => {
dailyRef.current = DailyIframe.wrap(iframeRef.current, {
// showLeaveButton: true,
});
dailyRef.current.on(eventTypes.LEFT_MEETING, () => {
joinedRef.current = false;
});
dailyRef.current.on(eventTypes.JONING_MEETING, () => {
joinedRef.current = true;
});
return () => {
dailyRef.current.destroy();
};
}, []);
return (
<iframe
ref={iframeRef}
className={classes.root}
title="Video Meeting"
allow="camera; microphone; display-capture; fullscreen"
/>
);
};
export default Daily;

How do I call API hooks one at a time?

I am completely new to React and trying to figure this out, while still keeping it as simple as possible.
I have a wrapper functional component called RestaurantMapWrapper that is supposed to:
Get geolocation data from a hook called usePosition.
pass the longitude and latitude data into a hook called useYelpHook, which retrieves data on restaurants using the passed in latitude and longitude data.
Render the yelp data (automatically, without user input).
The problem is that usePosition does not get the location in time, so useYelpHook has nothing to work with. If I set pos to a default value, then useYelpHook is never called again.
How can I ensure that useYelpHook waits for usePosition before rendering? Does this have something to do with one hook being asynchronous?
export function RestaurantMapWrapper(props) {
const { latitude, longitude, timestamp, accuracy, error, isLoadingMap } = usePosition();
const pos = { lat: latitude, lng: longitude }; //ends up being undefined since neither have been retreived yet
const [{ data, isLoading }, setLoc] = useYelpHook(pos); //is there somewhere I could call setLoc?
return <div>JSON.stringify({data})</div>;
export const useYelpHook = (initialLoc) => {
const API_KEY = 'my api key';
const config = {
headers: { Authorization: `Bearer ${API_KEY}` },
params: {
term: 'food',
latitude: '0',
longitude: '0',
radius: '',
sort_by: 'rating'
}
}
const [data, setData] = useState({ businesses: [] });
const [loc, setLoc] = useState(initialLoc);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
config.params.latitude = loc.lat;
config.params.longitude = loc.lng;
const fetchData = async () => {
setIsLoading(true);
const result = await axios(`${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3/businesses/search`, config);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, [loc])
return [{ data, isLoading }, setLoc];
}
import { useState, useEffect } from 'react';
const defaultSettings = {
enableHighAccuracy: false,
timeout: Infinity,
maximumAge: 0,
};
export const usePosition = (settings = defaultSettings) => {
const [position, setPosition] = useState({});
const [error, setError] = useState(null);
const [isLoadingMap, setIsLoadingMap] = useState(true);
const onChange = ({ coords, timestamp }) => {
setPosition({
latitude: coords.latitude,
longitude: coords.longitude,
accuracy: coords.accuracy,
timestamp,
});
};
const onError = (error) => {
setError(error.message);
};
useEffect(() => {
setIsLoadingMap(true);
const geo = navigator.geolocation;
if (!geo) {
setError('Geolocation is not supported');
return;
}
geo.getCurrentPosition(onChange, onError, settings);
setIsLoadingMap(false);
}, [settings]);
return { ...position, error, isLoadingMap };
};
useYelpHook's useEffect runs every time the component renders and is triggered by the dependencies. You should pass loc directly into the useYelpHook useEffect instead of saving its initial state.
export function RestaurantMapWrapper(props) {
const { latitude, longitude, timestamp, accuracy, error, isLoadingMap } = usePosition();
const pos = useMemo(() => ({ lat: latitude, lng: longitude }), [latitude, longitude]); //ends up being undefined since neither have been retrieved yet
const [{ data, isLoading }] = useYelpHook(pos);
return <div>JSON.stringify({data})</div>;
const API_KEY = 'my api key';
const config = {
headers: { Authorization: `Bearer ${API_KEY}` },
params: {
term: 'food',
latitude: '0',
longitude: '0',
radius: '',
sort_by: 'rating'
}
}
export const useYelpHook = (loc) => {
const [data, setData] = useState({ businesses: [] });
//const [loc, setLoc] = useState(initialLoc);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
/* return if loc is invalid */
config.params.latitude = loc.lat;
config.params.longitude = loc.lng;
const fetchData = async () => {
setIsLoading(true);
const result = await axios(`${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3/businesses/search`, config);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, [loc])
return [{ data, isLoading }];
}
Note the useMemo and removal of initialLoc

Resources