React useState and useEffect does not work as expected - reactjs

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 };

Related

React Context - fix unecessary rerenders - eslint issue

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

React: props has a value at first render afterwards is undefined

export const Modal = (props, { farm, id }) => {
const [currentUser, setCurrentUser] = useState();
console.log("NewId", props.id);
const initialFieldValues = {
title: "",
image: "",
product: "",
content: "",
phone: "",
email: "",
};
const [values, setValues] = useState(initialFieldValues);
function handleChange(e) {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
}
const handleFormSubmit = (e) => {
e.preventDefault();
const obj = {
...values,
};
getFirebase()
.database()
.ref(`Users/${props.id}`)
// .ref(`Users/${newId}`)
.push(obj, (err) => {
if (err) console.log(err);
// console.log("ID", newId);
});
};
PARENT COMPONENT
const App = (props) => {
const [currentUser, setCurrentUser] = useState();
const [id, setId] = useState("");
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.on("value", (snapShot) => {
setCurrentUser({ key: snapShot.key, ...snapShot.val() });
});
// }
}
if (setCurrentUser(userAuth)) {
} else if (!userAuth && typeof window !== "undefined") {
return null;
}
const id = userAuth.uid;
setId(id);
});
<>
<Modal id={id} />
</>;
return unsubscribe;
}, []);
When i console.log it here .ref(Users/${props.id}) it's undefined.
This is how my console looks like:
12modal.jsx:12 'NewId' fRHyyzzEotPpIGUkkqJkKQnrTOF2
12modal.jsx:12 'NewId' undefined
it comes one time with the ID and 12 times undifiend.
Any advice would be greatly appreciated.
Firstly you can't change a props values, and if props.id is a state(i.e. const [id, setId] = React.useState(initial value)) then you can change id only by setId which is not received by child, so i think parent might be causing this issue.

React Native: API Function making multiple calls to API server

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)}
/>
)
...

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

How can I initialize in useState with the data from custom hooks?

I'm learning to React Hooks.
And I'm struggling initialize data that I fetched from a server using a custom hook.
I think I'm using hooks wrong.
My code is below.
const useFetchLocation = () => {
const [currentLocation, setCurrentLocation] = useState([]);
const getCurrentLocation = (ignore) => {
...
};
useEffect(() => {
let ignore = false;
getCurrentLocation(ignore);
return () => { ignore = true; }
}, []);
return {currentLocation};
};
const useFetch = (coords) => {
console.log(coords);
const [stores, setStores] = useState([]);
const fetchData = (coords, ignore) => {
axios.get(`${URL}`)
.then(res => {
if (!ignore) {
setStores(res.data.results);
}
})
.catch(e => {
console.log(e);
});
};
useEffect(() => {
let ignore = false;
fetchData(ignore);
return () => {
ignore = true;
};
}, [coords]);
return {stores};
}
const App = () => {
const {currentLocation} = useFetchLocation();
const {stores} = useFetch(currentLocation); // it doesn't know what currentLocation is.
...
Obviously, it doesn't work synchronously.
However, I believe there's the correct way to do so.
In this case, what should I do?
I would appreciate if you give me any ideas.
Thank you.
Not sure what all the ignore variables are about, but you can just check in your effect if coords is set. Only when coords is set you should make the axios request.
const useFetchLocation = () => {
// Start out with null instead of an empty array, this makes is easier to check later on
const [currentLocation, setCurrentLocation] = useState(null);
const getCurrentLocation = () => {
// Somehow figure out the current location and store it in the state
setTimeout(() => {
setCurrentLocation({ lat: 1, lng: 2 });
}, 500);
};
useEffect(() => {
getCurrentLocation();
}, []);
return { currentLocation };
};
const useFetch = coords => {
const [stores, setStores] = useState([]);
const fetchData = coords => {
console.log("make some HTTP request using coords:", coords);
setTimeout(() => {
console.log("pretending to receive data");
setStores([{ id: 1, name: "Store 1" }]);
}, 500);
};
useEffect(() => {
/*
* When the location is set from useFetchLocation the useFetch code is
* also triggered again. The first time coords is null so the fetchData code
* will not be executed. Then, when the coords is set to an actual object
* containing coordinates, the fetchData code will execute.
*/
if (coords) {
fetchData(coords);
}
}, [coords]);
return { stores };
};
function App() {
const { currentLocation } = useFetchLocation();
const { stores } = useFetch(currentLocation);
return (
<div className="App">
<ul>
{stores.map(store => (
<li key={store.id}>{store.name}</li>
))}
</ul>
</div>
);
}
Working sandbox (without the comments) https://codesandbox.io/embed/eager-elion-0ki0v

Resources