How can I call function in React native context file before another function is called? - reactjs

I'm trying to get my current position and to get some cafe lists around me.
I made a getLocation function and I import it inside of my Context file CafeContext. However, I can't get the position before getting the cafe list.
It works sometimes when I set the lat/long in the range [37.~~, 125.~~].
This is getLocation
import { useState, useEffect } from "react";
import * as Location from "expo-location";
const getLocation = () => {
const [myX, setMyX] = useState(0);
const [myY, setMyY] = useState(0);
try {
const currentLocation = async () => {
await Location.requestPermissionsAsync();
const coordsObj = await Location.getCurrentPositionAsync();
await setMyY(coordsObj.coords.latitude);
await setMyX(coordsObj.coords.longitude);
};
useEffect(() => {
currentLocation();
}, []);
return { myX, myY };
} catch (err) {
setMyY(37.5572);
setMyX(126.9279);
return { myX, myY };
}
};
export default getLocation;
And this is CafeContext:
import React, { useState, createContext } from "react";
import cafeApi from "../api/cafeApi";
import AsyncStorage from "#react-native-community/async-storage";
import testArray from "../api/testArray.json";
import { navigate } from "../RootNavigation";
import getLocation from "../hooks/getLocation";
const CafeContext = React.createContext();
export const CafeProvider = ({ children }) => {
const [cafeList, setCafeList] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const [distance, setDistance] = useState(300);
//#####This line. I want to get location before getCafeList...
const { myX, myY } = getLocation();
const getCafeList = async () => {
const response = await cafeApi.get("/search", {
params: {
// category_group_code: "CE7",
x: myX,
y: myY,
radius: distance,
},
});
await setCafeList(response.data);
};
return (
<CafeContext.Provider
value={{
cafeList,
getCafeList,
distance,
setDistance,
term,
setTerm,
searchCafeList,
getLikedCafeList,
}}
>
{children}
</CafeContext.Provider>
);
};
export default CafeContext;

Your logic in getLocation is wrong. You are using hooks incorrectly, you can't return a values from a component.
If you want to return values you should create a custom hook also stop awaiting setState functions.
Custom hooks.
import { useState, useEffect } from 'react';
function useLocation() {
const [myX, setMyX] = useState(37.5572);
const [myY, setMyY] = useState(126.9279);
useEffect(() => {
const currentLocation = async () => {
await Location.requestPermissionsAsync();
const coordsObj = await Location.getCurrentPositionAsync();
setMyY(coordsObj.coords.latitude);
setMyX(coordsObj.coords.longitude);
};
currentLocation();
}, []);
return return { myX, myY };
}
Context
const CafeContext = React.createContext();
export const CafeProvider = ({ children }) => {
const [cafeList, setCafeList] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const [distance, setDistance] = useState(300);
//#####This line. I want to get location before getCafeList...
const { myX, myY } = useLocation();
const getCafeList = async () => {
const response = await cafeApi.get("/search", {
params: {
// category_group_code: "CE7",
x: myX,
y: myY,
radius: distance,
},
});
setCafeList(response.data);
};
return (
<CafeContext.Provider
value={{
cafeList,
getCafeList,
distance,
setDistance,
term,
setTerm,
searchCafeList,
getLikedCafeList,
}}
>
{children}
</CafeContext.Provider>
);
};
export default CafeContext;

Related

TypeError: Cannot read properties of undefined (reading 'addEventListener'), don't know how to proceed

Learning react, currently trying to create a video call web app, however I get this error:
TypeError: Cannot read properties of undefined (reading 'addEventListener')
on this line of code:
useEffect(() => {
peer.addEventListener("negationneeded",handleNegotiation);
return () =>{
peer.removeEventListener("negotionneeded",handleNegotiation);
};
},[]);
handleNegotiation:
const handleNegotiation = useCallback(() => {
const localOffer = peer.localDescription;
socket.emit("call-user",{userID: remoteUserID, offe: localOffer });
}, []);
here is also the whole file:
import React, {useEffect, useCallback, useState} from 'react';
import ReactPlayer from "react-player";
import { useSocket} from "../providers/Socket";
import { usePeer } from "../providers/Peer";
const SessionPage = () => {
const { socket } = useSocket();
const { peer, createOffer, createAnswer,setRemoteAns,sendStream,remoteStream } = usePeer();
const [myStream,setMyStream] = useState(null);
const [remoteUserID, setRemoteUserID] = useState();
const handleNewUserJoined = useCallback(
async(data) =>{
const {userID} = data
console.log("New user joined the session",userID);
const offer = await createOffer();
socket.emit('call-user',{ userID, offer });
setRemoteUserID(userID);
},
[createOffer,socket]
);
const handleIncomingCall = useCallback( async(data) => {
const {from, offer} = data;
console.log("Incoming Call from", from, offer);
const ans = await createAnswer(offer);
socket.emit("call-accepted",{userID: from, ans});
setRemoteUserID(from);
},
[createAnswer, socket] );
const handleCallAccepted = useCallback(async(data) => {
const {ans} = data;
console.log("Call Got Accepted",ans);
await setRemoteAns(ans);
}, [setRemoteAns]);
const getUserMediaStream = useCallback(async() => {
const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
setMyStream(stream);
}, []);
const handleNegotiation = useCallback(() => {
const localOffer = peer.localDescription;
socket.emit("call-user",{userID: remoteUserID, offe: localOffer });
}, []);
useEffect(() => {
socket.on("user-joined",handleNewUserJoined);
socket.on("incoming-call",handleIncomingCall);
socket.on("call-accepted",handleCallAccepted);
//return () =>{
// socket.off("user-joined",handleNewUserJoined);
//socket.off("incoming-call", handleIncomingCall);
//socket.off("call-accepted",handleCallAccepted);
//};
}, [handleCallAccepted,handleIncomingCall, handleNewUserJoined, socket]);
useEffect(() => {
peer.addEventListener("negationneeded",handleNegotiation);
return () =>{
peer.removeEventListener("negotionneeded",handleNegotiation);
};
},[]);
useEffect(() => {
getUserMediaStream();
},[]);
return(
<div className='session-page-container'>
<h1>Hi mom, Im on TV :D</h1>
<h4>You are now online with {remoteUserID}</h4>
<button onClick={(e) => sendStream(myStream)}>Share my video</button>
<ReactPlayer url={myStream} playing muted/>
<ReactPlayer url={remoteStream} playing/>
</div>
)
}
export default SessionPage;
--> Peer file
import React, { useMemo, useEffect, useState, useCallback } from "react";
const peerContext = React.createContext(null);
export const usePeer = () => React.createContext(null);
export const PeerProvider = (props) => {
const [remoteStream, setRemoteStream] = useState(null);
const peer = useMemo(() =>
new RTCPeerConnection({
iceServers: [
{
urls: [
"stun:stun.l.google.com:19302",
"stun:global.stun.twilio.com:3478",
],
},
],
}),
[]
);
const createOffer = async() => {
const offer = await peer.createOffer();
await peer.setLocalDescription(offer);
return offer;
};
const createAnswer = async (offer) => {
await peer.setRemoteDescription(offer);
const answer = await peer.createAnswer();
await peer.setLocalDescription(answer);
return answer;
};
const setRemoteAns = async(ans) =>{
await peer.setRemoteDescription(ans);
};
const sendStream = async(stream) => {
const tracks = stream.getTracks();
for(const track of tracks){
peer.addTrack(track,stream);
}
};
const handleTrackEvent = useCallback((ev) =>{
const streams = ev.streams;
setRemoteStream(streams[0]);
}, [])
useEffect(() => {
peer.addEventListener("track",handleTrackEvent);
return () =>{
peer.removeEventListener("track",handleTrackEvent)
}
},[handleTrackEvent, peer])
return(
<peerContext.Provider value={{ peer, createOffer, createAnswer, setRemoteAns, sendStream,remoteStream}}>{props.children}</peerContext.Provider>
);
};
useEffect with calling addEventListener on peer works earlier than some value assigned there.
Just add check of value existed:
useEffect(() => {
if (!peer) return
peer.addEventListener("track",handleTrackEvent);
return () =>{
peer.removeEventListener("track",handleTrackEvent)
}
},[handleTrackEvent, peer])

I need to refresh the page to login | React and Axios

I have a problem when I want to log in to the login by entering the email and password. What happens is that when I enter with the correct email and correct password, the animation appears but it stays cycled, and if I refresh the page and try again, now it lets me enter into the application
Here's my login form code:
import axios from "axios";
import { useRef, useState } from "react";
import { storeToken } from "../utils/authServices";
import { useNavigate } from "react-router-dom";
import { useLoading } from "../context/hooks/useLoading";
import { LoginForm } from "../components";
export const Login = () => {
const API_URL = "https://api.app"; //I hide the API for security reasons
const { run } = useLoading();
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const navigate = useNavigate();
const correoRef = useRef("");
const passwordRef = useRef("");
const handleSubmit = async (e) => {
e.preventDefault();
const { value: correo } = correoRef.current;
const { value: password } = passwordRef.current;
await axios
.post(`${API_URL}/api/auth/login/`, {
correo,
password,
})
.then((response) => {
storeToken(response.data.token);
run();
setTimeout(() => {
navigate("/nueva-solicitud");
}, 1000);
})
.catch((err) => {
console.log(err.response.data);
setError(true);
setErrorMessage(err.response.data.msg);
});
};
return (
<LoginForm
correoRef={correoRef}
passwordRef={passwordRef}
handleSubmit={handleSubmit}
error={error}
errorMessage={errorMessage}
/>
);
};
import { createContext, useReducer, useContext } from "react";
const initialState = {
loading: false,
alerts: [],
};
const reducers = (state, action) => {
switch (action.type) {
case "LOADING_RUN":
return {
...state,
loading: true,
};
case "LOADING_STOP":
return {
...state,
loading: false,
};
default:
return { ...state };
}
};
const AppContext = createContext();
const AppContextProvider = (props) => {
const [state, dispatch] = useReducer(reducers, initialState);
return <AppContext.Provider value={{ state, dispatch }} {...props} />;
};
const useAppContext = () => useContext(AppContext);
export { AppContextProvider, useAppContext };
import { useMemo } from "react";
import { useAppContext } from "../AppContext";
export const useLoading = () => {
const { dispatch } = useAppContext();
const loading = useMemo(
() => ({
run: () => dispatch({ type: "LOADING_RUN" }),
stop: () => dispatch({ type: "LOADING_STOP" }),
}),
[dispatch]
);
return loading;
};
import jwt_decode from "jwt-decode";
export const storeToken = (token) => {
localStorage.setItem("token", token);
};
export const getToken = (decode = false) => {
const token = localStorage.getItem("token");
if (decode) {
const decoded = jwt_decode(token);
return decoded;
}
return token;
};
export const logout = () => {
localStorage.removeItem("token");
};
How can I log in without refreshing the page?
There's two problems here. One is you're using await with a .then .catch block. Pick one or the other. You're also never calling the stop() dispatch when your async call is complete which appears to be responsible for removing the loader.
Instead of:
const { run } = useLoading();
Use:
const { run, stop } = useLoading();
Then change this:
setTimeout(() => {
navigate("/nueva-solicitud");
}, 1000);
To this:
setTimeout(() => {
navigate("/nueva-solicitud");
stop();
}, 1000);
Although I would just recommend writing the entire promise like this:
try {
run();
const response = await axios
.post(`${API_URL}/api/auth/login/`, {
correo,
password,
});
storeToken(response.data.token);
navigate("/nueva-solicitud");
stop();
} catch (err) {
stop();
console.log(err.response.data);
setError(true);
setErrorMessage(err.response.data.msg);
}

LocalStorage values are one login behind

I'm having this problem with my AuthContext from a React app. I check the localStorage and the values stored are from the last user logged, so I have to login twice to get the corrent info.
This is the Context code. And the 'useLocalStorage' function. I guess in the useMemo is the problem but I haven't been able to solve it. Is this a bad approach?
import { createContext, useContext, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useLocalStorage } from "./useLocalStorage";
import jwtDecode from "jwt-decode";
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [token, setToken] = useLocalStorage("accessToken", null);
const [userId, setUserId] = useLocalStorage("currentUserId", null);
const [userEmail, setUserEmail] = useLocalStorage("currentUserEmail", null);
const [userRole, setUserRole] = useLocalStorage("currentUserRole", null);
const [user, setUser] = useState({});
const navigate = useNavigate();
const login = async (credentials) => {
return fetch("https://localhost:7264/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials),
})
.then((res) => res.json())
.then((res) => {
setToken(res);
console.log(res);
const tokenToDecode = localStorage.getItem("accessToken");
setUser(jwtDecode(tokenToDecode));
setUserId(user.sub);
setUserEmail(
user[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
]
);
setUserRole(
user["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]
);
});
};
const logout = () => {
setToken(null);
setUserId(null);
setUserEmail(null);
setUserRole(null);
navigate("/", { replace: true });
};
const value = useMemo(
() => ({
token,
login,
logout,
userId,
userEmail,
userRole,
}),
[token]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
return useContext(AuthContext);
};
import { useState } from "react";
export const useLocalStorage = (keyName, defaultValue) => {
const [storedValue, setStoredValue] = useState(() => {
try {
const value = localStorage.getItem(keyName);
if (value) {
return JSON.parse(value);
} else {
localStorage.setItem(keyName, JSON.stringify(defaultValue));
return defaultValue;
}
} catch (err) {
return defaultValue;
}
});
const setValue = (newValue) => {
try {
localStorage.setItem(keyName, JSON.stringify(newValue));
} catch (err) {}
setStoredValue(newValue);
};
return [storedValue, setValue];
};

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

I got 'Too many re-renders' while converting my axios codes to react-query

It seems like react-query is a quiet popular so, I trying to add react-query to my exist codes.
the code below is the exist codes. it uses hooks (useEffect & useState), axios and returns response data.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
export const useCommonApi = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
useEffect(() => {
try {
const getState = async () => {
const result: any = await apiProvider.get('common/' + url, params);
let resultData = result.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
return resultData;
};
getState();
} catch (e) {
console.error(e);
}
}, []);
return State;
};
Here is the my new codes for react-query. I am trying to convert code above into react-query as below.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
import axios from 'axios';
import { useQuery } from 'react-query';
export const useCommonApi_adv = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
const { isLoading, error, data } = useQuery('fetchCommon', () =>
axios('/api/v1/admin/common/' + url).then( (res) :any => {
return res.data
})
)
if (isLoading) return 'Loading...'
let resultData = data.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
return State;
};
the my new codes(react-query) prints "too many render" when it is executed.
What did I wrong with it? any help please
You are calling your state update function setState outside of an useEffect. This will run on the first render, update the state, which in turn triggers a rerender, update the state again and you end up in an endless loop. You probably want to wrap that logic into useEffect and only run it if data changes.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
import axios from 'axios';
import { useQuery } from 'react-query';
export const useCommonApi_adv = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
const { isLoading, error, data } = useQuery('fetchCommon', () =>
axios('/api/v1/admin/common/' + url).then( (res) :any => {
return res.data
})
)
useEffect(() => {
let resultData = data.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
}, [data])
if (isLoading) return 'Loading...'
return State;
};

Resources