Use timestamp in a key with useSWRInfinite() - javascript

I'm using useSWRInfinite() for data fetching in my React app, and on the first page I need to include the current timestamp as a cursor in the getKey function (as there's no previousPageData). I can't do it because on every millisecond the key is changing and invoking the fetcher function again. Any suggestion will sure be help!
Here is my code at the moment:
const useQuestions = () => {
const fetcher = async (url: string) => fetch(url).then((res) => res.json());
const timestamp = new Date().toISOString();
const getKey = (pageIndex: number, previousPageData: any) => {
if (previousPageData && !previousPageData.questions) return null;
const cursorQuery = `?cursor=${pageIndex ? previousPageData?.from : timestamp}`;
if (pageIndex && !previousPageData?.cursor) return null;
return `/api/questions${fromQuery}`;
};
const { data, size, setSize, error, isLoading, isValidating, mutate } = useSWRInfinite(getKey, fetcher, {
initialSize: 1,
revalidateAll: true,
revalidateFirstPage: false,
persistSize: true,
// I tried to set persistSize as true to prevent refetching when 1st page key is changing but no luck
});

The useMemeo hook might help you in this case. It should stop the key from changing at every millisecond.
const useQuestions = () => {
const fetcher = async (url: string) => fetch(url).then((res) => res.json());
const timestamp = useMemo(() => new Date().toISOString(), []);
const getKey = (pageIndex: number, previousPageData: any) => {
if (previousPageData && !previousPageData.questions) return null;
const cursorQuery = `?cursor=${pageIndex ? previousPageData?.from : timestamp}`;
if (pageIndex && !previousPageData?.cursor) return null;
return `/api/questions${fromQuery}`;
};
const { data, size, setSize, error, isLoading, isValidating, mutate } = useSWRInfinite(getKey, fetcher, {
initialSize: 1,
revalidateAll: true,
revalidateFirstPage: false,
persistSize: true,
});
};

Related

Problem when I try to run two react-query in a row

I have two different endpoints, one that is called with getProjectMapping and one with getStaffing. The getProjectMapping query must be run first in order to set the project variable, which will then be used to make the getStaffing request. But I get the following error:
Uncaught TypeError: project is null
I get that error in the getStaffing request, although before activating it I check that the project is not null. Does anyone know what is wrong?
const Staffing = () => {
const { tokenApi } = useContext(LoginContext);
const [project, setProject] = useState(null);
const {
data: projectMapping,
isLoading: projectMappingIsLoading,
isFetching,
} = useQuery("ProjectMapping", () => getProjectMapping(tokenApi), {
onSuccess: () => {
if (projectMapping != null && projectMapping.length !== 0) {
setProject(projectMapping[0]);
}
},
});
const { data, isLoading } = useQuery(
[project.value, "Staffing"],
() => getStaffing(project.value, tokenApi),
{
enabled: !isFetching && project != null,
dependencies: [project],
}
);
}
This isn't how you structure dependent queries.. Instead of setting state you should derive it. If you have dependent queries it might also make sense to wrap them in a custom hook
e.g.
const useProjectStaffing = (tokenApi) => {
const {
data: [project] = [],
isLoading: projectMappingIsLoading,
} = useQuery("ProjectMapping", () => getProjectMapping(tokenApi), {
},
});
const projectValue = project && project.value
return useQuery(
[projectValue, "Staffing"],
() => getStaffing(projectValue, tokenApi),
{ enabled: !!projectValue }
);
}
const Staffing = () => {
const { tokenApi } = useContext(LoginContext);
const {isLoading, data: staffing} = useProjectStaffing(tokenApi);
// ... do stuff with the staffing data when it comes back.

Multiple axios get request not returning the data properly

I have created a react hook to work on with multiple get request using axios
const useAxiosGetMultiple = (urls,{preventCall = false} = {}) => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState(()=>{
const temp = {}
Object.keys(urls).forEach(key => temp[key] = [])
return temp
})
const [reloadToken, setReloadToken] = useState(false)
const urlObj = useRef({...urls})
const unmountedOnReload = useRef(false)
useEffect(() => {
if(preventCall === true){
return null
}
let unmounted = false;
const source = axios.CancelToken.source();
setLoading(true)
const requests = []
Object.values(urlObj.current).forEach(url => {
requests.push(
axios.get(url, {
cancelToken: source.token,
})
);
});
const result = {}
const errors = {}
console.log(requests)
Promise.allSettled(requests)
.then(resArray => {
if(!unmounted){
console.log('from promise allsettled')
console.log(resArray)
console.log(urlObj.current)
Object.keys(urlObj.current).forEach((key,i) =>{
if(resArray[i].status === 'fulfilled'){
result[key] = resArray[i].value.data.responseData
}
if(resArray[i].status === 'rejected'){
errors[key] = resArray[i].reason
result[key] = []
}
})
setError(errors)
setLoading(false)
setResponse(result)
}
})
.catch(err => {
if (!unmounted) {
setError(err);
setLoading(false);
setResponse([])
if (axios.isCancel(err)) {
console.log(`request cancelled:${err.message}`);
} else {
console.log("another error happened:" + err.message);
}
}
})
return () => {
unmounted = true;
unmountedOnReload.current = true
source.cancel("Api call cancelled on unmount");
};
}, [reloadToken,preventCall]);
const reFetchAll = () => {
setReloadToken((token) => !token);
};
const reload = (urlKey) =>{
unmountedOnReload.current = false
setLoading(true)
axios.get(urls[urlKey])
.then(res =>{
if(!unmountedOnReload.current){
setLoading(false)
setResponse({...response,[urlKey]: res.data.responseData})
}
})
.catch(err=>{
if(!unmountedOnReload.current){
setLoading(false)
setError({...error, [urlKey]: err})
setResponse({...response,[urlKey]: []})
}
})
}
return {response, loading, error, reFetchAll, reload, setLoading};
};
I call this hook as follows..
const {response,loading,setLoading,reload} = useAxiosGetMultiple({
stateCodes: StateCode.api,
countryCodes: CountryCode.api,
districts: District.api,
})
Rather than getting variable stateCodes containing state codes or countryCodes containing country codes it's returning in wrong order or returning same data in multiple variable. Every time the call happens every time it changes. I also tried axios.all method instead of Promise.all but problem remains same.
Even in chrome's network panel the response data is improper.
What's the possible cause for this error and how to fix it ?
Thanks in advance

How to check input for "Enter" key press

I'm working on a slightly complicated component that basically allows a user to type into an input, and then trigger a search (external API) for that product, the current issue however is that using the "Enter" key press, causes different behaviour and I want to sync up the behaviour of the "Find" button and "Enter". But before that I'm having some trouble on establishing where that check should happen, here's my React component:
export type CcceHook = {
allowForClassification: boolean,
classifyInProgress: boolean,
dataProfileId: string,
embedID: string,
handleCancelClassify: () => void,
handleClassify: (event?: SyntheticEvent<any>) => void,
handleCloseModal: () => void,
handleShowModal: () => void,
isDebugMode: boolean,
resultCode: string | null,
shouldShowModal: boolean,
};
// returns Ccce input fields based on the object form model - used in context provider
const getCcceValues = (object?: FormObjectModel | null) => {
const ccceInput: $Shape<CcceInput> = {};
//WHERE I THINK THE CHECK SHOULD GO (`ccceInput` is an object, with the `ccce.product` containing the users typed entry)
if (!object) {
return {};
}
// ccce input values
const ccceValues = object.attributeCollection.questions.reduce(
(acc, attribute) => {
const fieldEntry = ccceBeInformedFieldMap.get(attribute.key);
if (fieldEntry) {
acc[fieldEntry] = attribute.value;
}
return acc;
},
ccceInput
);
//check for null or empty string and if so hide "find goods button"
const productValueWithoutSpaces =
ccceValues.product && ccceValues.product.replace(/\s+/g, "");
const canClassify =
Object.values(ccceValues).every(Boolean) &&
Boolean(productValueWithoutSpaces);
return { canClassify, ccceValues };
};
export const useCcceEmbed = (
ccceResultAttribute: AttributeType,
onChange: Function
): CcceHook => {
const { object, form } = useFormObjectContext();
const [resultCode, setResultCode] = useState<string | null>(null);
const { canClassify, ccceValues } = getCcceValues(object);
const { handleSubmit } = useFormSubmit();
// data profile id is the 'api key' for 3ce
const dataProfileId = useSelector(
(state) => state.preferences[DATA_PROFILE_ID]
);
// data profile id is the 'api key' for 3ce
const isDebugMode = useSelector((state) => {
const value = state.preferences[CCCE_DEBUG_MODE_PREFERENCE];
try {
return JSON.parse(value);
} catch (error) {
throw new Error(
`3CE configuration error - non-boolean value for ${CCCE_DEBUG_MODE_PREFERENCE}: ${value}`
);
}
});
const [showModal, setShowModal] = useState<boolean>(false);
const handleCloseModal = useCallback(() => setShowModal(false), []);
const handleShowModal = useCallback(() => setShowModal(true), []);
// state value to keep track of a current active classification
const [classifyInProgress, setClassifyInProgress] = useState<boolean>(false);
// handle results from 3ce
const handleResult = useCallback(
(result) => {
if (result?.hsCode) {
onChange(ccceResultAttribute, result.hsCode);
setResultCode(result.hsCode);
setClassifyInProgress(false);
handleSubmit(form);
}
},
[ccceResultAttribute, form, handleSubmit, onChange]
);
const handleCancelClassify = useCallback(() => {
setClassifyInProgress(false);
handleCloseModal();
}, [handleCloseModal]);
// handle 3ce classify (https://github.com/3CETechnologies/embed)
const handleClassify = useCallback(
(event?: SyntheticEvent<any>) => {
if (event) {
event.preventDefault();
}
if (classifyInProgress || !canClassify) {
return;
}
const ccce = window.ccce;
if (!ccceValues || !ccce) {
throw new Error("Unable to classify - no values or not initialised");
}
setClassifyInProgress(true);
const classificationParameters = {
...ccceValues,
...DEFAULT_EMBED_PROPS,
};
ccce.classify(
classificationParameters,
handleResult,
handleCancelClassify
);
},
[
classifyInProgress,
canClassify,
ccceValues,
handleResult,
handleCancelClassify,
]
);
return {
allowForClassification: canClassify && !classifyInProgress,
classifyInProgress,
dataProfileId,
embedID: EMBED_ID,
handleCancelClassify,
handleClassify,
handleCloseModal,
handleShowModal,
isDebugMode,
resultCode,
shouldShowModal: showModal,
};
};
I have added a comment on where I think this logic should be handled (search "//WHERE I THINK..") - however, I'm unsure how to go from knowing the value of the users input, to checking for an enter press, I'm happy just to be able to console.log a user's key press, I should be able to tie up the logic from there, any advice would be really helpful.
TIA!

How to increment results of an Api Pagininated response on scroll in React Redux

i have i a component on scroll of bottom i need to increment the results of an api data but as total Results will be dynamic and when we reach the total Results count i dont want to call the increment results , stop api fetching and set loader to false,but api keeps calling again and again even if reach the total results count.
const initialState = {
resultsData: [],
resultsPerPage: 3,
totalResults: null,
loader: true,
}
Action
export const set_Results = (value) => {
return async (dispatch) => {
dispatch({ type: SET_RESULTS_PERPAGE, payload: value });
};
};
const getTotalResults = useSelector((state) => {
return state.results.totalResults;
});
const getresultsData = useSelector((state) => {
return state.results.resultsData;
});
Reducer
case SET_RESULTS_PERPAGE:
return {
...state,
resultsPerPage: action.payload,
};
dispatch
const loaderAndScroll = () => {
if (getresultsData.length < getTotalResults) {
return getResultsPerPage + 3;
}
};
const scrollToEnd = (event) => {
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
if (scrollHeight - scrollTop === clientHeight) {
dispatch(set_Results(loaderAndScroll()));
}
};

Why I'm not getting back the new value of useState - React.JS?

In the line setVotedPosts([...previousVotedPosts, postId]);
I'm trying to get the previous value of votedPosts, but I'm getting back the newest value.
full code : https://github.com/silvertechguy/reddit-clone/blob/main/src/components/vote-buttons.js
App live : https://reddit-clone-official.vercel.app/
const VoteButtons = ({ post }) => {
const [isVoting, setVoting] = useState(false);
const [votedPosts, setVotedPosts] = useState([]);
useEffect(() => {
const votesFromLocalStorage =
JSON.parse(localStorage.getItem("votes")) || [];
setVotedPosts(votesFromLocalStorage);
}, []);
const handleDisablingOfVoting = (postId) => {
const previousVotedPosts = votedPosts;
setVotedPosts([...previousVotedPosts, postId]);
localStorage.setItem(
"votes",
JSON.stringify([...previousVotedPosts, postId])
);
};
const handleClick = async (type) => {
setVoting(true);
// Do calculation to save the vote.
let upVotesCount = post.upVotesCount;
let downVotesCount = post.downVotesCount;
const date = new Date();
if (type === "upvote") {
upVotesCount = upVotesCount + 1;
} else {
downVotesCount = downVotesCount + 1;
}
await db.collection("posts").doc(post.id).set({
title: post.title,
upVotesCount,
downVotesCount,
createdAt: post.createdAt,
updatedAt: date.toUTCString(),
});
// Disable the voting button once the voting is successful.
handleDisablingOfVoting(post.id);
setVoting(false);
};
const checkIfPostIsAlreadyVoted = () => votedPosts.includes(post.id);
Problem
const previousVotedPosts = votedPosts;
In JavaScript, arrays are reference types, so you can't just create a new copy of an array using =.
Try this solution
Clone array using spread syntax(...).
const handleDisablingOfVoting = (postId) => {
const previousVotedPosts = [...votedPosts];
setVotedPosts([...previousVotedPosts, postId]);
localStorage.setItem(
"votes",
JSON.stringify([...previousVotedPosts, postId])
);
};

Categories