Best approach to extract repeated code from screens using hooks (setState, useEffect)? - javascript

The question may seem a little vague, I'm new using hooks, I'll be quite specific in my example, I have 3 variables, with their setter, and a useEffect that works on them. The code basically asks the user for location permissions and saves his position.
This piece of code is reused exactly the same in two different screens, my question is, to what extent it is feasible to move all the code variables and setters, and use effect to a third file "helper".
Here is the piece of code:
const [localitzacioActual, setlocalitzacioActual] = useState(null);
const [localitzacioPermisos, setlocalitzacioPermisos] = useState(null);
const [mapRegion, setMapRegion] = useState(null);
useEffect( () => {
const demanarPermisos = async () => {
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
setlocalitzacioPermisos('Permisos denegats')
} else {
setlocalitzacioPermisos(true)
}
let location = await Location.getCurrentPositionAsync({});
setlocalitzacioActual(JSON.stringify(location))
setMapRegion({ latitude: location.coords.latitude, longitude: location.coords.longitude, latitudeDelta: 0.0022, longitudeDelta: 0.0121 });
}
demanarPermisos()
}, []);
To what point I can instantiate this code to another file, y still need to declare the constants, and the use effect but I can move all the login to a third function outside of the file?
Thanks!

You can put all of your state variables and the function in a custom hook. Your custom hook will handle the state changes for you.
permisos.js
import { useState } from 'react';
const usePermisos= () => {
const [localitzacioActual, setlocalitzacioActual] = useState(null);
const [localitzacioPermisos, setlocalitzacioPermisos] = useState(null);
const [mapRegion, setMapRegion] = useState(null);
const demanarPermisos = async () => {
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
setlocalitzacioPermisos('Permisos denegats')
} else {
setlocalitzacioPermisos(true)
}
let location = await Location.getCurrentPositionAsync({});
setlocalitzacioActual(JSON.stringify(location))
setMapRegion({ latitude: location.coords.latitude, longitude: location.coords.longitude, latitudeDelta: 0.0022, longitudeDelta: 0.0121 });
};
return [
localitzacioActual,
localitzacioPermisos,
mapRegion,
demanarPermisos,
];
};
export default usePermisos;
Then import them wherever you need them. You still have to use useEffect to fire off your function.
screen1.js
import React, { useEffect } from 'react';
import usePermisos from './usePermisos';
const screen1 = () => {
const [
localitzacioActual,
localitzacioPermisos,
mapRegion,
demanarPermisos,
] = usePermisos();
useEffect(demanarPermisos, []);
return (
<div>React Functional Component</div>
);
};
export default screen1;
If you need your setters outside of demanarPermisos you can return them from usePermisos.

Well, I'll answer my own question. For anyone wondering the same thing:
Yes, it is possible to move all the code out to a third function. Just add a return with all the variables you need in the screen:
LocalitzacioHelper.js
import React, {useState, useEffect} from 'react';
import * as Location from 'expo-location';
import * as Permissions from 'expo-permissions';
export const demanarLocalitzacio = () => {
const [localitzacioActual, setlocalitzacioActual] = useState(null);
const [localitzacioPermisos, setlocalitzacioPermisos] = useState(null);
const [mapRegion, setMapRegion] = useState(null);
useEffect( () => {
const demanarPermisos = async () => {
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
setlocalitzacioPermisos('Permisos denegats')
} else {
setlocalitzacioPermisos(true)
}
let location = await Location.getCurrentPositionAsync({});
setlocalitzacioActual(JSON.stringify(location))
setMapRegion({ latitude: location.coords.latitude, longitude: location.coords.longitude, latitudeDelta: 0.0022, longitudeDelta: 0.0121 });
}
demanarPermisos()
}, []);
return [localitzacioActual, localitzacioPermisos, mapRegion]
}
Then in the screen you just call the function before the return:
MapaScreen.js
const [localitzacioActual, localitzacioPermisos, mapRegion] = demanarLocalitzacio()
The use effect will have the exact same behavior as it was directly inside de screen render function.

Related

How to use Context API with useParams

I have an api with details of a farm and I want to show them in different components using an id. Like the data is used in many components and I want to use Context API to display the data in the components.
So here is the code that fetches the data
let navigate = useNavigate();
const [farm, setFarm] = useState('');
const { username } = useParams();
const { farmId } = useParams();
const [isLoading, setIsLoading] = useState(true);
const user = React.useContext(UserContext);
useEffect(() => {
let isMounted = true;
axios.get(`/api/farm/${username}/${farmId}`).then(res => {
if (isMounted) {
if (res.data.status === 200) {
setFarm(res.data.farm);
setIsLoading(false);
console.warn(res.data.farm)
}
else if (res.data.status === 404) {
navigate('/');
toast.error(res.data.message, "error");
}
}
});
return () => {
isMounted = false
};
}, []);
The username is okay because I will use the user context to get the user details.
Now, how do Use this from a context into the components, because I have tried, and it is not working.
Well you can do it by adding another context for farm.
Your context file:
export const FarmContext = React.createContext();
export function FarmProvider({ children }) {
const [farm, setFarm] = useState();
return (
<FarmContext.Provider value={{farm, setFarm}}>
{ children }
</FarmContext.Provider>
)
}
And instead of this:
const [farm, setFarm] = useState();
Use this:
const { farm, setFarm } = useContext(FarmContext);
Include setIsLoading and setFarm into your dependency array. The dispatch function changes during a re-render. In your case, during development double renders components to detect issues with state management.
useEffect(() => {
....
}, [ setIsLoading, setFarm]);

React Hook useEffect has a missing dependency: 'handleLogout'. Either include it or remove the dependency array react

import { useState, useEffect } from "react";
import LoginModal from "./LoginModal";
import { NavLink, useLocation, useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { userLogout } from "../Features/User/userSlice";
import decode from "jwt-decode";
const Header = ({ toggleModalShow, showModal }) => {
const [burgerAnimation, setBurgerAnimation] = useState(false);
const [user, setUser] = useState();
const location = useLocation();
const dispatch = useDispatch();
const navigate = useNavigate();
// for showing login/sign up modal
const showModalButton = () => {
toggleModalShow();
};
const handleBurgerAnimation = () => {
setBurgerAnimation(!burgerAnimation);
};
const handleLogout = async (id) => {
await dispatch(userLogout({ id, navigate, dispatch }));
setUser(null);
};
const burgerListItemAnimation = ...
const burgerIconAnimation = ...
const guestHeader = (
<ul>
...
</ul>
);
const userHeader = (
<ul>
...
</ul>
);
useEffect(() => {
if (localStorage.getItem("user") && !user) {
setUser(JSON.parse(localStorage.getItem("user")));
}
const accessToken = user?.accessToken;
if (accessToken) {
const decodedAccessToken = decode(accessToken);
if(decodedAccessToken.exp * 1000 < new Date().getTime()){
handleLogout(user.user._id);
}
console.log(decodedAccessToken);
}
}, [location, user]);
return (
<header className="header">
...
</header>
);
};
export default Header;
Hi all.I just wanted to try to log out the user when the expiration date is over. If i put 'handleLogout' to useEffect dependicies warning doesnt change. Why am i getting this warning ? What kind of warning may i get if i dont fix that ? And finally, if you have time to review the repo, would you give feedback ?
repo : https://github.com/UmutPalabiyik/mook
If you keep handleLogout external to the useEffect hook it should be listed as a dependency as it is referenced within the hook's callback.
If i put handleLogout to useEffect dependencies warning doesn't
change.
I doubt the warning is the same. At this point I would expect to you to see the warning change to something like "the dependency handleLogout is redeclared each render cycle, either move it into the useEffect hook or memoize with useCallback..." something to that effect.
From here you've the 2 options.
Move handleLogout into the useEffect so it is no longer an external dependency.
useEffect(() => {
const handleLogout = async (id) => {
await dispatch(userLogout({ id, navigate, dispatch }));
setUser(null);
};
if (localStorage.getItem("user") && !user) {
setUser(JSON.parse(localStorage.getItem("user")));
}
const accessToken = user?.accessToken;
if (accessToken) {
const decodedAccessToken = decode(accessToken);
if (decodedAccessToken.exp * 1000 < new Date().getTime()) {
handleLogout(user.user._id);
}
console.log(decodedAccessToken);
}
}, [location, user, id, navigate, dispatch]);
Memoize handleLogout with useCallback so it's a stable reference and add it to the effect's dependencies.
const handleLogout = useCallback(async (id) => {
await dispatch(userLogout({ id, navigate, dispatch }));
setUser(null);
}, [id, navigate, dispatch]);
...
useEffect(() => {
if (localStorage.getItem("user") && !user) {
setUser(JSON.parse(localStorage.getItem("user")));
}
const accessToken = user?.accessToken;
if (accessToken) {
const decodedAccessToken = decode(accessToken);
if (decodedAccessToken.exp * 1000 < new Date().getTime()) {
handleLogout(user.user._id);
}
console.log(decodedAccessToken);
}
}, [location, user, handleLogout]);

How to centre a MapView on a user's current location when the Map Screen is opened? React Native Expo

How to centre the map to show a user's current location when the map screen is opened? By following the expo documentation, it should be achieved with Expo Location API? However, the documentation is unclear. I took part of the code from expo Location documentation and implemented it in my Map Screen. So, how should I integrate it in MapView to execute the getCurrentPositionAsync method and centre the map accordingly when the map screen is opened?
import React, { useContext, useState, useEffect } from "react";
import MapView from "react-native-maps";
import styled from "styled-components";
import { Searchbar } from "react-native-paper";
import { View } from "react-native";
import * as Location from 'expo-location';
const Map = styled(MapView)`
height: 100%;
width: 100%;
`;
const SearchBarContainer= styled(View)`
padding: ${(props) => props.theme.space[3]};
position: absolute;
z-index: 999;
top: 20px;
width: 100%;
`;
export const MapScreen = ({navigation}) => {
const [location, setLocation] = useState(null);
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);
})();
}, []);
return (
<>
<SearchBarContainer>
<Searchbar placeholder="location"/>
</SearchBarContainer>
<Map showsUserLocation={true}>
</Map>
</>
);};
I have been struggling with this issue for a couple of days as well and I find it very weird that even though the expo MapView can show your own location on the map, it cannot return your own coördinates.
Despite that, the problem can be fixed with the following solution.
Install Expo-location
expo install expo-location
Import it
import * as Location from 'expo-location'
Create a state
const [location, setLocation] = useState({});
Create a useEffect function which retrieves your coördinates asynchronous (retrieving the location at button press may take up to 5-10 seconds which is ridiculously late regarding the user experience)
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
return;
}
let location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
enableHighAccuracy: true,
timeInterval: 5
});
setLocation(location);
})();
}, []);
Your Mapview needs to have a reference to talk to the MapView and set it's coördinates.
<MapView ref={mapRef}>.... </MapView>
const mapRef = React.createRef();
And in the end create a function which can be triggered by a custom button to center to the users location.
const goToMyLocation = async () => {
mapRef.current.animateCamera({center: {"latitude":location.coords.latitude, "longitude": location.coords.longitude}});
}
You have to pass user coordinates provided by Expo location api into your map component inorder to show you current Location, also you need to set an initial state for your location with (lat,lng) as follows
, and if you seek higher acuracy i would recommend to these option to your getCurrentPositionAsync
export const MapScreen = ({navigation}) => {
const [location, setLocation] = useState({
latitude: 37.78825,
longitude: -122.4324,
});
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({
accuracy: Location.Accuracy.Balanced,
enableHighAccuracy: true,
timeInterval: 5
});
setLocation(location);
})();
}, []);
return (
<>
<SearchBarContainer>
<Searchbar placeholder="location"/>
</SearchBarContainer>
<Map region={location} showsUserLocation={true}/>
</>
);};

Local storage data not saved

UPDATE: its working. problem was from useEffect
When I try to 'submit' a specific task, it is shown in the local storage that the status attribute is not changed. I wonder where I am going wrong. The data i am trying to use to update the local storage is initialState.stuff .
Here is my code for reducer.js:
const reducer = (state,action)=>{
if(action.type ==="completed"){
//the relevant attributes to be changed
let temp = state.stuff.map((task)=>{
if(task.id === action.payload){
return {...task,status:status,enddate:enddate}
}
return task;
});
// the rest of the parts of this code is irrelevant
here is my code for context.js:
import React, {useState,useContext,useEffect,useReducer} from "react"
import reducer from "./reducer";
const AppContext = React.createContext()
const initialState = {
stuff: null
}
const AppProvider = ({children}) =>{
const [tasks,setTasks] = useState([])
const [status,changeStatus] = useState("");
const [state,dispatch] = useReducer(reducer,initialState)
const complete = (id) =>{
dispatch({type:"completed",payload:id})
console.log("done", initialState.stuff)
setTasks(initialState.stuff)
}
const deleted = (id) =>{
dispatch({type:"deleted",payload:id})
}
const getLocalStorage = () =>{
state.stuff = localStorage.getItem("tasks")
if(state.stuff){
state.stuff = JSON.parse(state.stuff)
return JSON.parse(localStorage.getItem("tasks"))
}
else{
return []
}
}
const fetchData = async()=>{
setLoading(true)
setTasks(getLocalStorage());
setLoading(false);
}
useEffect(()=>{
fetchData()
},[])
useEffect(()=>{
localStorage.setItem("tasks",JSON.stringify(tasks))
},[tasks])
return <AppContext.Provider
value = {{
loading,
tasks,setTasks
}}>
{children}
</AppContext.Provider>
}
export const useGlobalContext = () =>{
return useContext(AppContext)
}
export {AppContext,AppProvider}
I think there might be (I have not read your code completely) variable collision in your code. You can try writing window.localStorage instead of localStorage and check whether it solves your problem.

Using React Context with AsyncStorage using hooks

My goal is to use custom hooks created from Context to pass and modify stored values
The final goal is to use something like useFeedContext() to get or modify the context values
What I am actually getting is either the functions that I call are undefined or some other problem ( I tried multiple approaches)
I tried following this video basics of react context in conjunction with this thread How to change Context value while using React Hook of useContext but I am clearly getting something wrong.
Here is what I tried :
return part of App.js
<FeedProvider mf={/* what do i put here */}>
<Navigation>
<HomeScreen />
<ParsedFeed />
<FavScreen />
</Navigation>
</FeedProvider>
Main provider logic
import React, { useState, useEffect, useContext, useCallback } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
const FeedContext = React.createContext();
const defaultFeed = [];
const getData = async (keyName) => {
try {
const jsonValue = await AsyncStorage.getItem(keyName);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.log(e);
}
};
const storeData = async (value, keyName) => {
console.log(value, keyName);
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(keyName, jsonValue);
} catch (e) {
console.log(e);
}
};
export const FeedProvider = ({ children, mf }) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
const [feedLoaded, setFeedLoaded] = useState(false);
let load = async () => {
let temp = await AsyncStorage.getItem("mainUserFeed");
temp != null
? getData("mainUserFeed").then((loadedFeed) => setMainFeed(loadedFeed))
: setMainFeed(defaultFeed);
setFeedLoaded(true);
};
useEffect(() => {
load();
}, []);
useCallback(async () => {
if (!feedLoaded) {
return await load();
}
}, [mainFeed]);
const setFeed = (obj) => {
setMainFeed(obj);
storeData(mainFeed, "mainUserFeed");
};
return (
<FeedContext.Provider value={{ getFeed: mainFeed, setFeed }}>
{children}
</FeedContext.Provider>
);
};
//export const FeedConsumer = FeedContext.Consumer;
export default FeedContext;
The custom hook
import { useContext } from "react";
import FeedContext from "./feedProviderContext";
export default function useFeedContext() {
const context = useContext(FeedContext);
return context;
}
What I would hope for is the ability to call the useFeedContext hook anywhere in the app after import like:
let myhook = useFeedContext()
console.log(myhook.getFeed) /// returns the context of the mainFeed from the provider
myhook.setFeed([{test:1},{test:2}]) /// would update the mainFeed from the provider so that mainFeed is set to the passed array with two objects.
I hope this all makes sense, I have spend way longer that I am comfortable to admit so any help is much appreciated.
If you want to keep using your useFeedContext function, I suggest to move it into the your 'Provider Logic' or I'd call it as 'FeedContext.tsx'
FeedContext.tsx
const FeedContext = createContext({});
export const useFeedContext = () => {
return useContext(FeedContext);
}
export const AuthProvider = ({children}) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
...
return (
<FeedContext.Provider value={{mainFeed, setMainFeed}}>
{children}
</FeedContext.Provider>
);
};
YourScreen.tsx
const YourScreen = () => {
const {mainFeed, setMainFeed} = useFeedContext();
useEffect(() => {
// You have to wait until mainFeed is defined, because it's asynchronous.
if (!mainFeed || !mainFeed.length) {
return;
}
// Do something here
...
}, [mainFeed]);
...
return (
...
);
};
export default YourScreen;

Categories

Resources