I have this function that basically helps me implement infinite scroll everywhere. Still, I faced a problem where when I invalidate a tag related to an endless scroll tag it doesn't update the needed portion because of the offset and limit parameters.
The way I provide tags:
providesTags: (item) => item?.result
? [...item.result.departures.map(({ ID }) => ({
type: 'Departures',
id: ID,
})),
{ type: 'Departures', id: 'LIST' },
]
: [{ type: 'TransitDepartures', id: 'LIST' }],
To Invalidate tags I use invalidatesTags
The function I described
export const isValidNotEmptyArray = (array) =>
!!(array && array?.length && array?.length > 0)
const useFetchQuery = (
useGetDataListQuery,
{ offset = 0, limit = 10, ...queryParameters },
filter = () => true,
) => {
const [localOffset, setLocalOffset] = useState(offset)
const [combinedData, setCombinedData] = useState([])
const [gotWiped, setGotWiped] = useState(0)
const queryResponse = useGetDataListQuery(
{
offset: localOffset,
limit,
...queryParameters,
},
)
const { data: fetchData = { result: [], total: 0 } } = queryResponse || {}
const total = useMemo(() => fetchData.total, [fetchData])
useEffect(() => {
const value = departure ? fetchData.result.departures : fetchData.result
if (isValidNotEmptyArray(value)) {
setGotWiped(0)
if (localOffset === 0 || !localOffset) {
setCombinedData(value)
} else {
setCombinedData((previousData) => [...previousData, ...value])
}
} else if (gotWiped === 0) {
setGotWiped(1)
}
}, [fetchData])
useEffect(() => {
if (gotWiped) {
setCombinedData([])
}
}, [gotWiped])
const refresh = () => {
setLocalOffset((prev) => (prev === 0 ? null : 0))
setCombinedData([])
}
const loadMore = () => {
if (combinedData.length < total) {
setLocalOffset(combinedData.length)
}
}
return {
data: useMemo(() => combinedData.filter(filter), [combinedData, filter]),
offset: localOffset,
total:
combinedData.length > combinedData.filter(filter).length
? combinedData.filter(filter).length
: total,
loadMore,
refresh,
isLoading: queryResponse?.isLoading,
isFetching: queryResponse?.isFetching,
}
}
I want to save an array of object to device using AsyncStorage, I have used AsyncStorage for save my jwt token and it working well. But when i tried to save an array of object it wasn't working here is my code :
const storeCart = async (value) => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(`cart-${user.id}`, jsonValue);
} catch (e) {
console.log(e);
} finally {
const jsonValue = await AsyncStorage.getItem(`cart-${user.id}`);
if (jsonValue != null) {
console.log(
'this is from async storage after save',
JSON.parse(jsonValue),
user.id,
);
}
}
};
const getCart = async () => {
try {
const jsonValue = await AsyncStorage.getItem(`cart-${user.id}`);
if (jsonValue != null) {
setCarts(JSON.parse(jsonValue));
console.log('carts after refresh the app', jsonValue, user.id);
}
} catch (e) {
console.log(e);
}
};
I have tried to console log the result after setItem, and it was saved successly, but when i reload the app and tried to console.log, it return and empty array, the key is already correct, and i have console log the user id too for make sure.
Here's the full code, if needed :
import React, { useState, createContext, useEffect, useContext } from 'react';
import AsyncStorage from '#react-native-async-storage/async-storage';
import AuthenticationContext from '../authentication/AuthenticationContext';
const CartsContext = createContext();
export const CartsContextProvider = ({ children }) => {
const { user } = useContext(AuthenticationContext);
const [carts, setCarts] = useState([]);
const storeCart = async (value) => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(`cart-${user.id}`, jsonValue);
} catch (e) {
console.log(e);
} finally {
const jsonValue = await AsyncStorage.getItem(`cart-${user.id}`);
if (jsonValue != null) {
console.log(
'this is from async storage after save',
JSON.parse(jsonValue),
user.id,
);
}
}
};
const getCart = async () => {
try {
const jsonValue = await AsyncStorage.getItem(`cart-${user.id}`);
if (jsonValue != null) {
setCarts(JSON.parse(jsonValue));
console.log('carts after refresh the app', jsonValue, user.id);
}
} catch (e) {
console.log(e);
}
};
useEffect(() => {
storeCart(carts);
}, [carts, user]);
useEffect(() => {
getCart();
}, [user]);
const searchByMerchant = (merchantId) => {
for (let i = 0; i < carts.length; i++) {
if (carts[i].merchantId === merchantId) {
return carts[i];
}
}
};
const searchByItem = (itemId, arrayOfItems) => {
for (let i = 0; i < arrayOfItems.length; i++) {
if (itemId === arrayOfItems[i].productId) {
return arrayOfItems[i];
}
}
};
const deletePerMerchant = (merchantId) => {
return carts.filter((x) => {
return x.merchantId != merchantId;
});
};
const deletePerItem = (itemId, arrayOfItems) => {
return arrayOfItems.filter((x) => {
return x.productId != itemId;
});
};
const addItem = (merchantId, productId, qty) => {
let merchantCheck = searchByMerchant(merchantId);
let temp = null;
if (merchantCheck) {
let itemCheck = searchByItem(productId, merchantCheck.items);
if (itemCheck) {
let itemAfterRemoveSelectedItem = deletePerItem(
productId,
merchantCheck.items,
);
temp = deletePerMerchant(merchantId);
if (qty === 0) {
if (itemAfterRemoveSelectedItem.length === 0) {
setCarts([...temp]);
} else {
setCarts([
...temp,
...[
{
merchantId,
items: [...itemAfterRemoveSelectedItem],
},
],
]);
}
} else {
setCarts([
...temp,
...[
{
merchantId,
items: [
...itemAfterRemoveSelectedItem,
...[{ productId, qty: qty }],
],
},
],
]);
}
} else {
temp = deletePerMerchant(merchantId);
setCarts([
...temp,
...[
{
merchantId,
items: [...merchantCheck.items, ...[{ productId, qty }]],
},
],
]);
}
} else {
if (qty > 0) {
setCarts([...carts, ...[{ merchantId, items: [{ productId, qty }] }]]);
}
}
};
return (
<CartsContext.Provider value={{ carts, addItem }}>
{children}
</CartsContext.Provider>
);
};
export default CartsContext;
Thanks !
I believe this is happening because the cart value is being overwritten when you reload the app because the useEffect is called each time you reload the app.
The setCarts is being called after adding something in cart, and therefore the first useEffect (which has in deps [cart, user]) is being called too and it sets correctly the data in local storage. But afterwards, if you reload the app, the same useEffect is being called again and the cart = [] is being set into the local storage.
I would solve this by giving up to the first useEffect and setting directly the data into local storage without having any state related to it.
import React, { useState, createContext, useEffect, useContext } from 'react';
import AsyncStorage from '#react-native-async-storage/async-storage';
import AuthenticationContext from '../authentication/AuthenticationContext';
const CartsContext = createContext();
export const CartsContextProvider = ({ children }) => {
const { user } = useContext(AuthenticationContext);
const storeCart = async (value) => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(`cart-${user.id}`, jsonValue);
} catch (e) {
console.log(e);
} finally {
const jsonValue = await AsyncStorage.getItem(`cart-${user.id}`);
if (jsonValue != null) {
console.log(
'this is from async storage after save',
JSON.parse(jsonValue),
user.id,
);
}
}
};
const getCart = async () => {
try {
const jsonValue = await AsyncStorage.getItem(`cart-${user.id}`);
if (jsonValue != null) {
storeCart(JSON.parse(jsonValue));
console.log('carts after refresh the app', jsonValue, user.id);
}
} catch (e) {
console.log(e);
}
};
useEffect(() => {
getCart();
}, [user]);
const searchByMerchant = (merchantId) => {
for (let i = 0; i < carts.length; i++) {
if (carts[i].merchantId === merchantId) {
return carts[i];
}
}
};
const searchByItem = (itemId, arrayOfItems) => {
for (let i = 0; i < arrayOfItems.length; i++) {
if (itemId === arrayOfItems[i].productId) {
return arrayOfItems[i];
}
}
};
const deletePerMerchant = (merchantId) => {
return carts.filter((x) => {
return x.merchantId != merchantId;
});
};
const deletePerItem = (itemId, arrayOfItems) => {
return arrayOfItems.filter((x) => {
return x.productId != itemId;
});
};
const addItem = (merchantId, productId, qty) => {
let merchantCheck = searchByMerchant(merchantId);
let temp = null;
if (merchantCheck) {
let itemCheck = searchByItem(productId, merchantCheck.items);
if (itemCheck) {
let itemAfterRemoveSelectedItem = deletePerItem(
productId,
merchantCheck.items,
);
temp = deletePerMerchant(merchantId);
if (qty === 0) {
if (itemAfterRemoveSelectedItem.length === 0) {
storeCart([...temp]);
} else {
storeCart([
...temp,
...[
{
merchantId,
items: [...itemAfterRemoveSelectedItem],
},
],
]);
}
} else {
storeCart([
...temp,
...[
{
merchantId,
items: [
...itemAfterRemoveSelectedItem,
...[{ productId, qty: qty }],
],
},
],
]);
}
} else {
temp = deletePerMerchant(merchantId);
storeCart([
...temp,
...[
{
merchantId,
items: [...merchantCheck.items, ...[{ productId, qty }]],
},
],
]);
}
} else {
if (qty > 0) {
storeCart([...carts, ...[{ merchantId, items: [{ productId, qty }] }]]);
}
}
};
return (
<CartsContext.Provider value={{ carts, addItem }}>
{children}
</CartsContext.Provider>
);
};
export default CartsContext;
I'm working on a project with graphs and I need to be able to cancel my requests if the user selects a different tab.
Here's my API call
export const getDifferentialData = (
sourceId: string,
sourceLine: string,
source: any
) => {
const graph1Request = getData(
sourceId,
sourceLine,
source
)
const graph2Request = getData(
sourceId,
sourceLine,
source
)
return Promise.all([graph1Request, graph2Request]).then(results => {
const [graphA, graphB] = results
return {
graphA: parsedData(graphA),
graphB: parsedData(graphB),
}
})
}
export const getData = (
sourceId: string,
sourceLine: string,
source?: any
) => {
if (sourceId && sourceLine) {
return api.get(`apiGoesHere`, { cancelToken: source.token }).then(response => {
const { data } = response
return parsedData(data)
})
} else {
return api.get(`apiGoesHere`, { cancelToken: source.token }).then(response => {
const { data } = response
return parsedData(data)
})
}
}
And the component where I'm doing the call. userDidChangeTab is called when pressing on a tab and it calls fetchGraph
const Graph: FC<Props> = () => {
const source = axios.CancelToken.source();
// we ensure that the query filters are up to date with the tab selected
const userDidChangeTab = (tabIndex: number) => {
const isDifferentialTabSelected = isDifferentialTab(tabIndex)
let newFilters = queryFilters
if (isDifferentialTabSelected) {
newFilters = {
// props go here
}
} else {
newFilters = {
// props go here
}
}
source.cancel()
fetchGraph(isDifferentialTabSelected)
setActiveTab(tabIndex)
}
// Function to fetch two differential graphs.
const fetchGraph = (isDifferential: boolean) => {
setFetching(true)
if (isDifferential) {
getDifferentialData(
sourceId,
sourceLine,
source
)
.then(({ graphA, graphB }: any) => {
setGraphData(graphA)
setMatchData(new diffMatch(graphA, graphB, 1.0))
})
.catch(reason => {
const errorMessage = errorMessageFromReason(reason)
addMessageToContainer(errorMessage, true)
})
.finally(() => {
setFetching(false)
})
} else {
getGraph(
sourceId,
sourceLine,
source
)
.then((graphData: any) => {
setGraphData(graphData)
setMatchData(null)
})
.catch(reason => {
const errorMessage = errorMessageFromReason(reason)
addMessageToContainer(errorMessage, true)
})
.finally(() => {
setFetching(false)
})
}
}
}
I use i18next to localize HTML and I am trying to cache the selected language so it will not fallback on page refresh but can't get it to work.
here is my code.
<script src="https://unpkg.com/i18next/dist/umd/i18next.min.js"></script>
<script>
function updateContent() {
const elements = document.getElementsByClassName("i18nelement");
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const k = element.getAttribute("data-i18n");
element.innerHTML = i18next.t(k);
}
}
async function i18Loader() {
const langs = ["en", "ru"];
const jsons = await Promise.all(
langs.map((l) => fetch("src/i18/" + l + ".json").then((r) => r.json()))
);
const res = langs.reduce((acc, l, idx) => {
acc[l] = { translation: jsons[idx] };
return acc;
}, {});
await i18next.init({
lng: 'en',
debug: true,
resources: res,
fallbackLng: "en-US"
});
updateContent();
i18next.on("languageChanged", () => {
updateContent();
});
const langSelector = document.getElementById("langSelector");
langSelector.removeAttribute("disabled");
langSelector.addEventListener("change", (e) => {
i18next.changeLanguage(e.target.value);
});
}
i18Loader();
</script>
How can I store selected language in localstorage?
I found the solution. the correct code here
async function i18Loader() {
const langs = ["en", "ru"];
const jsons = await Promise.all(
langs.map((l) => fetch("src/i18/" + l + ".json").then((r) => r.json()))
);
const res = langs.reduce((acc, l, idx) => {
acc[l] = { translation: jsons[idx] };
return acc;
}, {});
await i18next
.init({
lng: localStorage.getItem("lan") || 'en',
debug: true,
resources: res,
fallbackLng: "en-US",
backend: {
backendOptions: [{
// can be either window.localStorage or window.sessionStorage. Default: window.localStorage
store: typeof window !== 'undefined' ? window.localStorage : null
}, {
loadPath: '/scr/i18/{{lng}}.json' // xhr load path for my own fallback
}]
}
});
function updateContent() {
const elements = document.getElementsByClassName("i18nelement");
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const k = element.getAttribute("data-i18n");
element.innerHTML = i18next.t(k);
}
}
updateContent();
i18next.on("languageChanged", () => {
updateContent();
});
const langSelector = document.getElementById("langSelector");
langSelector.removeAttribute("disabled");
langSelector.addEventListener("change", (e) => {
i18next.changeLanguage(e.target.value);
localStorage.setItem("lan", e.target.value);
});
}
i18Loader();
I've been baffled as to why an item - card does not disappear from the DOM after deletion and state update. My edit function works fine and I've soured my code for spelling errors and wrong variables, etc.
Here's my App (top component) state object:
state = {
decks: [],
cards: [],
selectedCards: [],
selectedDecks: [],
currentUser: null,
users: []
}
my Delete function (optimistic) in App that gets passed down to a deckLayout component:
deleteCard = (cardId, deckId) => {
const cardCopy = this.state.cards.slice()
const foundOldCardIdx = cardCopy.findIndex(card => card.id === cardId)
cardCopy.splice(foundOldCardIdx, 1)
this.setState({
cards: cardCopy
}, console.log(this.state.cards, cardCopy))
this.filterCards(deckId)
console.log(this.state.cards)
fetch(`http://localhost:9000/api/v1/cards/${cardId}`, {
method: 'DELETE'
})
};
And this is a filterCards functions that gets called after Delete and State update (this works for Edit):
filterCards = (deckId) => {
if (this.state.cards.length === 0) {
alert('No cards yet!')
} else {
const filteredCards = this.state.cards.filter(card => card.deck_id === deckId)
this.setState({
selectedCards: filteredCards
})
this.filterDecks()
}
};
which then calls a filterDecks function:
filterDecks = () => {
if (this.state.currentUser) {
const filteredDecks = this.state.decks.filter(deck => {
return deck.user_id === this.state.currentUser.id
})
this.setState({
selectedDecks: filteredDecks
})
} else {
alert('Login or sign up')
}
};