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
Related
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 };
I have a component that gets a value from the local storage and does a useQuery to get some data:
const DashboardComponent = () => {
const [filterState, setFilter] = useState(false);
const returnFilteredState = async () => {
return await localforage.getItem<boolean>('watchedAndReviewedFilterd') || false;
};
useEffect(() => {
returnFilteredState().then((value) => {
setFilter(value);
});
}, []);
const {error, loading, data: {moviesFromUser: movies} = {}} =
useQuery(resolvers.queries.ReturnMoviesFromUser, {
variables: {
userId: currentUserVar().id,
filter: filterState,
},
});
The problem is that the ReturnMoviesFromUser query is called twice. I think it's because of the filterState variable. If I set the filter: true the ReturnMoviesFromUser is only called once.
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
wondering how this could happen when this get call API twice. I'am using nextjs with typescript and using and design.
Below my code
const EditRoles = () => {
const router = useRouter();
const router = useRouter();
const { id } = router.query;
const [detail, setDetail] = useState();
const [rolePermission, setRolePermission] = useState([]);
const [pagination, setPagination] = useState<Params>({
page: 1,
row: 10,
});
const [loading, setLoading] = useState(false);
const [nextPage, setNextPage] = useState(0);
const getRolePermission = async (payload: { page?: number; row?: number; search?: string }) => {
if (id) {
setLoading(true);
const res = await httpService
.get(`${apiUrl.user}/v1/role-permission/${id}`, { params: payload })
.then((resp) => resp);
const rps = await res.data;
setLoading(false);
const roleP = rps.data.map((rp) => {
return { ...rp };
});
setRolePermission(rps.page === 1 ? roleP : [...rolePermission, ...roleP]);
setNextPage(rps.nextPage);
setPagination({ ...pagination, page: pagination.page + 1 });
console.log('this next page: ', rps.nextPage);
}
};
useEffect(() => {
getDetailRole();
getRolePermission({
...pagination,
});
form.setFieldsValue({
name: detail,
permissions: rolePermission,
});
}, [id, form, detail]);
return (
// ... HTML GOES HERE ... //
);
};
export default EditRoles;
And below are this result for the code. testing code
My question is, why this api called twice and the pagination sometimes breaking like the video?
Please help what is wrong with the code.
Thank you
I'm using the Yelp API and Expo Location in React Native. My function for calling the API and getting the location is making 3 calls on each load. I'm guessing it's because my state is changing and causing the function to run again, but I can't seem to get it to stop.
Any thoughts on the hook below?
import React, { useEffect, useState } from 'react';
import yelp from '../api/yelp';
import * as Location from 'expo-location';
export default () => {
const [results, setResults] = useState([]);
const [errorMessage, setErrorMessage] = useState('');
const [location, setLocation] = useState({});
const [errorMsg, setErrorMsg] = useState(null);
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
let location = await Location.getCurrentPositionAsync({});
setLocation(location);
})();
}, []);
const searchAPI = async (defaultTerm) => {
try {
const response = await yelp.get('/search', {
params: {
limit: 50,
term: defaultTerm,
latitude: location.coords.latitude,
longitude: location.coords.longitude,
radius: 4000,
},
});
setResults(response.data.businesses);
} catch (error) {
setErrorMessage('Something went wrong 😢');
}
};
console.log(`latitude: ${location.coords.latitude}`);
console.log(`longitude: ${location.coords.longitude}`);
useEffect(() => {
searchAPI('');
}, []);
return [searchAPI, results, errorMessage];
};
The hook is being used here:
const [searchTerm, setSearchTerm] = useState('');
const [searchAPI, results, errorMessage] = useResults();
const filterResultsByPrice = (price) => {
return results.filter(result => {
return result.price === price;
});
};
return (
<View style={styles.resultsContainerStyle}>
<SearchBar
searchTerm={searchTerm}
onSearchTermChange={setSearchTerm}
onSearchTermSubmit={() => searchAPI(searchTerm)}
/>
)
...