API call with React custom hook not taking in updated parameter - reactjs

I am using a custom hook to call the OpenRouteService API and retrieve the safest route from point A to point B. I'm now trying to switch between vehicles (which should give different routes), but the vehicle is not updating in the API call even though the parameter has been updated if I log it. See code below and attached screencaps.
Why is my API call not taking in the updated parameter?
Routehook.js
import { useState, useEffect } from 'react';
import Directions from '../apis/openRouteService';
import _ from 'lodash'
const useRoute = (initialCoordinates, avoidPolygons, vehicle) => {
const [route, setRoute] = useState({route: {}})
const [coordinates, setCoordinates] = useState(initialCoordinates);
const [routeLoading, setRouteLoading] = useState(true);
const [routeError, setRouteError] = useState(false);
useEffect(() => {
const fetchRoute = async () => {
setRouteError(false);
setRouteLoading(true);
try {
// Swap coordinates for openrouteservice
const copy = _.cloneDeep(coordinates)
let startLocation = copy[0];
let endLocation = copy[1];
console.log(vehicle);
// Logs 'cycling-regular' or 'driving-car'
// depending on the parameter when I call the hook
// Call openrouteservice api
const result = await Directions.calculate({
coordinates: [
[startLocation.latLng.lng, startLocation.latLng.lat],
[endLocation.latLng.lng, endLocation.latLng.lat],
],
profile: vehicle,
format: 'geojson',
avoid_polygons: avoidPolygons
})
console.log(result)
// When I check the result the query profile does not contain
// the updated parameter (see screencaps below).
setRoute(result);
} catch (error) {
setRouteError(true);
}
setRouteLoading(false);
}
fetchRoute();
}, [coordinates, avoidPolygons, vehicle]);
return [{ route, routeLoading, routeError }, setCoordinates];
}
export default useRoute;
To give a full overview. In the main component (VanillaMap.js) I have these related snippets:
const [vehicle, setVehicle] = useState('cycling-regular')
const [{ route, routeLoading, routeError }, setCoordinates] = useRoute([startLocation, endLocation], avoidPolygons, vehicle);
const updateVehicle = (state) => {
setVehicle(state);
}
<RouteInfo
route={route}
routeLoading={routeLoading}
routeError={routeError}
vehicle={vehicle}
updateVehicle={updateVehicle}
/>
I then update the vehicle through the updateVehicle function in the RouteInfo.js component.

Solved by creating a new instance of the Directions object each call:
const Directions = new openrouteservice.Directions({
api_key: "XXXXX"
});

Related

How can I define the return type for a React Router v6 loader?

I'm using loaders with the new react-router-dom and when I use the useLoaderData hook in the component, the response type is unknown, so I can't use it. I don't want to use the as type definition as I feel like that's kind of cheating but if that's the only way, then, please let me know:
My loader function:
{
path: "/reset-password/:resetToken",
element: <ResetPassword />,
loader: async ({ params }) => {
if (!params.resetToken){
return null
}
const fakeTokens = ["abcdef123456", "bingbong", "foobar"]
// make API call to check if the given token is valid
const fakeAPICall = (resetToken: string) : Promise<object> => {
if (fakeTokens.includes(resetToken)){
return new Promise(resolve => resolve({ success: true }))
}
return new Promise(resolve => resolve({ success: false }))
}
const resp = await fakeAPICall(params.resetToken);
return resp;
}
},
Inside the <ResetPassword />:
// this type is "unknown", so I can't access the properties on it
const resetTokenResponse = useLoaderData()
The useLoaderData hook's return type is unknown, so you'll need to re-type it in the component.
See source
/**
* Returns the loader data for the nearest ancestor Route loader
*/
export function useLoaderData(): unknown {
let state = useDataRouterState(DataRouterStateHook.UseLoaderData);
let route = React.useContext(RouteContext);
invariant(route, `useLoaderData must be used inside a RouteContext`);
let thisRoute = route.matches[route.matches.length - 1];
invariant(
thisRoute.route.id,
`useLoaderData can only be used on routes that contain a unique "id"`
);
return state.loaderData[thisRoute.route.id];
}
AFAIK using the as typing is how you'd do it, i.e.
interface ResetTokenResponse {
success: boolean;
}
...
const resetTokenResponse = useLoaderData() as ResetTokenResponse;

Why is React Native AsyncStorage not updating state on mount?

When I try to load the state from AsyncStorage for the screen I just navigated to, I am getting this error:
TypeError: undefined is not an object (evaluating 'weights[numExercise].map') It is trying to use the initial state that the screen initializes the state with, but I want the state to be loaded with the data that I specifically try to load it with on mount, within my useEffect hook.
const WorkoutScreen = ({ navigation, route }) => {
const [workoutName, setWorkoutName] = useState("");
const [exercisesArr, setExercisesArr] = useState([""]);
// Each array inside the arrays (weights & reps), represents an exercise's sets.
const [weights, setWeights] = useState([[""]]);
const [reps, setReps] = useState([[""]]);
const [restTimers, setRestTimers] = useState([""]);
useEffect(() => {
try {
console.log("loading workoutscreen data for:", route.params.name);
const unparsedWorkoutData = await AsyncStorage.getItem(route.params.name);
if (unparsedWorkoutData !== null) {
// We have data!
const workoutData = JSON.parse(unparsedWorkoutData);
setWorkoutName(route.params.name.toString());
setExercisesArr(workoutData[0]);
setWeights(workoutData[1]);
setReps(workoutData[2]);
setRestTimers(workoutData[3]);
}
} catch (error) {
// Error retrieving data
console.log("ERROR LOADING DATA:", error);
}
}, []);
Then further down the line in a component it realizes the error because, again, it's using the initialized state for the weights state.
Return (
{weights[numExercise].map((weight, i) => {
return (
<SetComponent
key={i}
numSet={i}
numExercise={numExercise}
prevWeights={prevWeights}
weights={weights}
setWeights={setWeights}
prevReps={prevReps}
reps={reps}
setReps={setReps}
isDoneArr={isDoneArr}
setIsDoneArr={setIsDoneArr}
/>
);
})}
);
I've made sure that the data is being stored, loaded, and used correctly, so (I think) I've narrowed it down to be something asynchronous; whether it's the setting of the state or loading from storage, I don't know and I can't find a solution. I am new to React Native and would love some suggestions, thank you!
It turns out that using multiple states was causing an issue, I'm assuming because it's asynchronous. So instead I used one state that held an object of states, like so:
const [states, setStates] = useState({
workoutName: "",
exercisesArr: [""],
weights: [[""]],
reps: [[""]],
restTimers: [""],
isDoneArr: [[false]],
originalWorkoutName: "",
});
The data was loaded as such:
const loadWorkoutData = async () => {
try {
console.log("loading workoutscreen data for:", route.params.name);
const unparsedWorkoutData = await AsyncStorage.getItem(route.params.name);
if (unparsedWorkoutData !== null) {
// We have data!
const workoutData = JSON.parse(unparsedWorkoutData);
setStates({
workoutName: route.params.name,
exercisesArr: workoutData[0],
weights: workoutData[1],
reps: workoutData[2],
restTimers: workoutData[3],
isDoneArr: workoutData[4],
originalWorkoutName: route.params.name,
});
}
} catch (error) {
// Error retrieving data
console.log("ERROR LOADING DATA:", error);
}
};

update values in the Reducer in Redux

i got two values i.e.company and id from navigation.
let id = props.route.params.oved;
console.log("id-->",id);
let company = props.route.params.company;
console.log("company--->",company);
i got two values as a integer like this:--
id-->1
comapny-->465
Description of the image:---
if i am giving input 1 in that textInput and click on the card(lets say first card i.e.465 then i am getting those two values in navigation as in interger that i have mention above.so each time i am getting updated values.
i am getting updated values from navigation.
so i want to store those values in redux.
action.js:--
import { CHANGE_SELECTED_COMPANY } from "./action-constants";
export const changeCompany = (updatedCompany, updatedId) => {
return {
type: CHANGE_SELECTED_COMPANY,
updatedCompany,
updatedId,
};
};
reducer.js:--
import { CHANGE_SELECTED_COMPANY } from "../actions/action-constants";
const initialState = {
company: "",
id: "",
};
const changeCompanyReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_SELECTED_COMPANY:
return {
company: {
company: action.updatedCompany,
id: action.updatedId,
},
};
}
return state;
};
export default changeCompanyReducer;
congigure-store.js:--
import changeCompanyReducer from "./reducers/change-company-reducer";
const rootReducer = combineReducers({changeCompanyReducer});
How can i store the update values getting from navigation in Redux?
could you please write code for redux??
in the component create a function that updates the values
const updateReducer = () => {
dispatch(changeCompany(props.route.params.oved, props.route.params.company))
}
then call the function in react navigation lifecycle event
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
updateReducer()
});
return unsubscribe;
}, [navigation])
its possible that a better solution would be to update the reducer before the navigation happens and not pass the data in the params but rather pull it from redux but this is the answer to the question as asked

context is giving me undefined

I am uncertain why after the initial load the values in context becomes undefined.
The way I have my context written up is:
export const ProductListContext = createContext({});
export const useListProductContext = () => useContext(ProductListContext);
export const ListProductContextProvider = ({ children }) => {
const [listProduct, setListProduct] = useState({
images: [],
title: "Hello",
});
return (
<ProductListContext.Provider value={{ listProduct, setListProduct }}>
{children}
</ProductListContext.Provider>
);
};
On the initial load of my component. I so get the listProduct to be correct as a console.log will produce
the list is Object {
"images": Array [],
"title": "Hello",
}
The problem is when I try to read listProduct again after it says it is undefined unless I save it to a useState. Any help on this is appreciated. The problem is within the pickImage function
// Initial has all properties correctly
const { listProduct, setListProduct } = useListProductContext();
// Seems to work at all times when I save it here
const [product] = useState(listProduct);
console.log('the list product listed is ', listProduct);
useEffect(() => {
(async () => {
if (Platform.OS !== 'web') {
const {
status,
} = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
alert('Sorry, we need camera roll permissions to make this work!');
}
}
})();
}, []);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
exif: true,
});
// PROBLEM - listProduct is undefined
console.log('before copy it is ', listProduct);
const listProduct = { ...product };
console.log('the list is', listProduct);
listProduct.images.push(result.uri);
// listProduct.images.push(result.uri);
// const images = listProduct.images;
// images.push(result.uri);
setListProduct({ ...listProduct });
return;
};
Your useListProductContext is violating the rules of hooks, as React sees the use qualifier to validate the rules of hooks.
Rules of Hooks
Using a Custom Hook
"Do I have to name my custom Hooks starting with “use”? Please do. This convention is very important. Without it, we wouldn’t be able to automatically check for violations of rules of Hooks because we couldn’t tell if a certain function contains calls to Hooks inside of it."

useEffect and redux a cache trial not working

I have a rather complex setup and am new to React with Hooks and Redux.
My setup:
I have a component, which when first mounted, should fetch data. Later this data should be updated at a given interval but not too often.
I added useRef to avoid a cascade of calls when one store changes. Without useEffect is called for every possible change of the stores linked in its array.
The data is a list and rather complex to fetch, as I first have to map a name to an ID and
then fetch its value.
To avoid doing this over and over again for a given "cache time" I tried to implement a cache using redux.
The whole thing is wrapped inside a useEffect function.
I use different "stores" and "reducer" for different pieces.
Problem:
The cache is written but during the useEffect cycle, changes are not readable. Even processing the same ISIN once again the cache returns no HIT as it is empty.
Really complex implementation. It dawns on me, something is really messed up in my setup.
Research so far:
I know there are libs for caching redux, I do want to understand the system before using one.
Thunk and Saga seem to be something but - same as above plus - I do not get the concept and would love to have fewer dependencies.
Any help would be appreciated!
Component - useEffect
const calculateRef = useRef(true);
useEffect(() => {
if (calculateRef.current) {
calculateRef.current = false;
const fetchData = async (
dispatch: AppDispatch,
stocks: IStockArray,
transactions: ITransactionArray,
cache: ICache
): Promise<IDashboard> => {
const dashboardStock = aggregate(stocks, transactions);
// Fetch notation
const stockNotation = await Promise.all(
dashboardStock.stocks.map(async (stock) => {
const notationId = await getXETRANotation(
stock.isin,
cache,
dispatch
);
return {
isin: stock.isin,
notationId,
};
})
);
// Fetch quote
const stockQuote = await Promise.all(
stockNotation.map(async (stock) => {
const price = await getQuote(stock.notationId, cache, dispatch);
return {
isin: stock.isin,
notationId: stock.notationId,
price,
};
})
);
for (const s of dashboardStock.stocks) {
for (const q of stockQuote) {
if (s.isin === q.isin) {
s.notationId = q.notationId;
s.price = q.price;
// Calculate current price for stock
if (s.quantity !== undefined && s.price !== undefined) {
dashboardStock.totalCurrent += s.quantity * s.price;
}
}
}
}
dispatch({
type: DASHBOARD_PUT,
payload: dashboardStock,
});
return dashboardStock;
};
fetchData(dispatch, stocks, transactions, cache);
}
}, [dispatch, stocks, transactions, cache]);
Action - async fetch action with cache:
export const getXETRANotation = async (
isin: string,
cache: ICache,
dispatch: AppDispatch
): Promise<number> => {
Logger.debug(`getXETRANotation: ${isin}`);
if (CACHE_ENABLE) {
const cacheTimeExceeding = new Date().getTime() + CACHE_NOTATION;
if (
isin in cache.notation &&
cache.notation[isin].created < cacheTimeExceeding
) {
Logger.debug(
`getXETRANotation - CACHE HIT: ${isin} (${cache.notation[isin].created} < ${cacheTimeExceeding} current)`
);
return cache.notation[isin].notationId;
}
}
// FETCH FANCY AXIOS RESULT
const axiosRESULT = ...
if (CACHE_ENABLE) {
Logger.debug(`getXETRANotation - CACHE STORE: ${isin}: ${axiosRESULT}`);
dispatch({
type: CACHE_NOTATION_PUT,
payload: {
isin,
notationId: axiosRESULT,
created: new Date().getTime(),
},
});
}
//FANCY AXIOS RESULT
return axiosRESULT;
}

Resources