is there a way to persist data from useLocation() ?
Basically I'm sending data that is located on a nested collection from firebase
(There's a lot of background on my question/explanation, you can just skip to the important bit)
Usually you have no issues when you only have one collection you can always access the UID pretty much from anywhere by sending it from the App.js for example:
function App() {
const [user, setUser] = useState([]);
useEffect(() => {
auth.onAuthStateChanged((authUser) => {
if (authUser) {
setUser(authUser);
} else {
setUser(false);
}
})
}, [])
return (
< div >
<Router>
<Switch>
<Route path = "/SomePath">
<SomeLocation user={user}/>
</Route>
</Switch>
</Router>
</div >
);
}
export default App;
and since the user have all the data you need to get any other piece of information (usually) you don't need to worry about nested collections however, what if I'm using nested collections ?
if you want to access all the data from a nested collection that's also fine you do not require any other extra information apart from the user
Ej:
useEffect(() => {
const dbRef= db.collection("users").doc(user.uid).collection("someCollection")
dbRef.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setDataSomewhere(tempData);
})
}, [user]);
However how you get the data of the uid of an specific document inside a nested location with just the user ? you can't (as far as I'm aware)
The Important bit without all the background
EJ:
if you have let's say a "parent" and he adds "students" which is my case:
and I wanted to edit this "student" that is in a nested collection, let's make an example on the first one the one named "alfonsa" this is the edit form which is in another "/Path"
The way I'm handling the data of the student is the following, when you check the student and you select the edit icon from the data-table it sends some data through useLocation()
//PATH-A (The one with the data table)
const editStudent= {
pathname: '/EDIT_STUDENT',
data: studentData
}
const editStudents= () => {
if(studentData== 0)
{
window.alert("Select a student")
}
else {
history.push(editStudent);
}
};
///EDIT_STUDENT (Edit form)
let studentData= useLocation();
let data = studentData.data;
console.log(data)
const uid = data[0].uid <- Here I get that student specific uid
This is what the console log returns (All correct data):
However all the data disappears on refresh (which makes sense because is no longer getting the useLocation data from the previous "/path" location) this is the question:
How can I keep/store/maintain that data that comes from the useLocation() on refresh
Any help/tip/example is welcome
Forgot to add this
This is basically what comes up when I refresh
UPDATE So I tried localStorage() as someone mention but now I can't access the data because is coming as a string how can I separate the data ?
This is how I'm storing the data:
useEffect(() => {
const localStorageEstData = window.localStorage.getItem("students");
localStorageEstData && setStudentsData(JSON.parse(localStorageEstData));
}, []);
useEffect(() => {
window.localStorage.setItem("students", JSON.stringify(studentsData));
}, [studentsData]);
This is how I'm getting the data + how it shows in console:
let data = window.localStorage.getItem("estudiantes")
is coming as a whole string how can I separate it ?
Since no one posted an answer I will based on what #Daniel Beck said
Instead of using useLocation() that is to pass data from one /Path to another is better to use localStorage for multiple reasons and the more important one is to persist/keep data on refresh.
if you use useLocation() and you refresh the data will disappear because is no longer parsing data from one /Path to another. However the localStorage will persist on refresh which is why is more useful for this case
simple solution for me was:
useEffect(() => {
const localStorageEstData = window.localStorage.getItem("students");
localStorageEstData && setStudentsData(JSON.parse(localStorageEstData));
}, []);
useEffect(() => {
window.localStorage.setItem("students", JSON.stringify(estudiantesData));
}, [studentsData]);
and then in the other end:
const data = JSON.parse(window.localStorage.getItem("students"))
const uid = data[0].uid
that way I have access to all the information not just the uid
Related
So I have an array with the following structure:
`
export const transacciones = [
{
id:100,
cantidad: 0,
concepto : 'Ejemplo',
descripcion: 'Ejemplo',
},
]
`
This array will dynamically increase or decrease as I push or filter items in it (Exactly like data in a task list)
The problem is that I am trying to add some data persistence using local storage. I guess data is getting stored but not shown when I refresh my browser (chrome).
However, when I refresh data disappears from where it was in the upper image so I`m not even sure if I am correctly storing it.
I've tried two things using useEffect hooks.
First aproach:
`
const [transacciones,setTransacciones] = useState([]);
useEffect(()=>{
localStorage.setItem('transacciones',JSON.stringify(transacciones))
},[transacciones])
useEffect(() =>{
const transacciones = JSON.parse(localStorage.getItem('transacciones'))
if (transacciones){
setTransacciones(transacciones)
}
},[])
`
I read somewhere that as the initial value of use state is [] I should chage things in there, so...
Second aproach:
`
const [transacciones,setTransacciones] = useState([],()=>{
const localData = localStorage.getItem('transacciones');
return localData ? JSON.parse(localData) : [];
});
useEffect(()=>{
localStorage.setItem('transacciones',JSON.stringify(transacciones))
},[transacciones])
`
However, when I refresh I get the same result: No persistence.
What am I missing here? Any help would be appreciated
In both scenarios your transacciones array is empty when you perform the localStorage.setItem. if you're trying to keep your local state sync with localStorage this might help:
export function useTransacciones(initialValue){
const localData = localStorage.getItem('transacciones');
const [transacciones,_setTransacciones] = useState(localData?JSON.parse(localData) : initialValue); // you can choose your own strategy to handle `initialValue` and cachedValue
const setTransacciones = (data) => {
_setTransacciones(data)
localStorage.setItem(JSON.stringify(data))
}
hydrate(){
const data = localStorage.getItem("transacciones")
setTransacciones(JSON.prase(data))
}
return [ transacciones, setTransacciones, hydrate ]
}
which you can use it anywhere with caching compelexity hidden inside:
const [transacciones, setTransacciones] = useTransacciones([])
I am trying to make a webpage that displays the price of assets from different exchanges. I have a java class that makes the http requests and now I need to somehow call those variables with my js code that is designing my webpage. Any help would be amazing, and please let me know if there is anything else that should be added code-wise to help determine my issue?
I figure the calls go around here, but I am unsure if I need to also do anything in my java class, like save the variables in certain formats as right now they are in maps.
<div className = 'Middle'>
<Exchange name = "Coinbase" btcBuy = "" btcSell = "" ethBuy = "" ethSell = ""/>
<Exchange name = "Binance" btcBuy = "" btcSell = "" ethBuy = "" ethSell = ""/>
<Recommendations/>
</div>
There are a couple of issues. The first is how you deal with state - once you've got your data how can your component render it. The second is how do you call an endpoint lots of times, but still update the state with just one collection of data.
First: React functional components can use hooks, and in this example we're using both useState to store the data, and useEffect to get the data.
Second: build an array of promises (where each element in the array is a new fetch to the server), and then use Promise.all to wait for all of the server calls to resolve. You can then update the state with the data, and then use that state to render the component, and its child components.
The code will look vaguely like this.
const { useEffect, useState } = React;
function Example() {
const [ data, setData ] = useState([]);
useEffect(() => {
// Generic fetch params
const arr = [1, 2, 3];
async getData() {
// Get an array of fetch promises using the params
// Wait for them to resolve, return the parsed data, and
// then set the state with that data
const promises = arr.map(el => fetch(`endpoint?query${el}`));
const response = await Promise.all(promises);
const data = response.map(async el => await el.json());
setData(data);
}
getData();
}, []);
// `map` over the data to create your
// Exchange components
return (
<div>
{data.map(obj => {
<Exchange
name={obj.name}
btcBuy=""
btcSell=""
ethBuy=""
ethSell=""
/>
})}
</div>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
I currently have some issues trying to add the infinite query feature to a recipes app I'm working on using Edamam API.
All the examples I have looked for (even React Query's documentation) implement the infinite scroll using a page/cursor number system... I understand this is the ideal way, but... Edamam API doesn't work this way with paginated queries.
Instead, the API has the following structure for each recipe query we look for (let's assume we are searching for "chicken", this would be the JSON structure):
from: 1,
to: 20,
count: 10000,
_links: {
next: {
href: "https://api.edamam.com/api/recipes/v2?q=chicken&app_key=APIKEYc&_cont=CHcVQBtNNQphDmgVQntAEX4BYldtBAAGRmxGC2ERYVJ2BwoVX3cVBWQSY1EhBQcGEmNHVmMTYFEgDQQCFTNJBGQUMQZxVhFqX3cWQT1OcV9xBB8VADQWVhFCPwoxXVZEITQeVDcBaR4-SQ%3D%3D&type=public&app_id=APPID"
title: "Next Page"
}
},
hits: [{}] ... (This is where the actual recipes are)
As you can see, there is no numbering system for paginated queries, instead, it's a whole URL and it's giving me a hard time since I'm also new to React Query.
I tried the following, but it just fetches the same data over and over again as I reach the bottom of the page:
const getRecipes = async ({ pageParam }) => {
try {
const path = pageParam
? pageParam
: `https://api.edamam.com/api/recipes/v2?q=${query}&app_id=${process.env.REACT_APP_APP_ID}&app_key=${process.env.REACT_APP_API_KEY}&type=public`;
const response = await axios.get(path);
return response.data;
} catch (error) {
console.log(error);
}
const { ref, inView } = useInView();
useEffect(() => {
inView && fetchNextPage();
}, [inView]);
const {
data,
isFetching,
isFetchingNextPage,
error,
status,
hasNextPage,
fetchNextPage,
} = useInfiniteQuery(
["recipes", query],
({ pageParam = "" }) => getRecipes(pageParam),
{
getNextPageParam: (lastPage) => lastPage._links.next.href,
}
);
Since the next page param is a whole URL, I just say that IF there is a pageParam, then use that URL for the request, if not, then do a normal request using the query value the user is searching for.
Please help!
Since the next page param is a whole URL, I just say that IF there is a pageParam, then use that URL for the request, if not, then do a normal request using the query value the user is searching for.
I'd say that this is the correct approach. The only code issue I can see in your example is that you destruct page param, and then pass the page param string to getRecipes:
({ pageParam = "" }) => getRecipes(pageParam),
but in getRecipes, you expect an object to come in (which you again destructure):
const getRecipes = async ({ pageParam }) => {
You can fix that by either changing the call side, or the function syntax, and then it should work.
Working on a project here and ran into an issue. I haven't had this problem before but now I do for some reason.
So I am making an GET request to ASOS API, but it is acting very strange. Some of these, such as name is received upon page refresh, but mainly the other things like information about the brand becomes undefined. Now, the brand is in another object inside of the API. But I have had other things at other parts of the page before that were also in objects. But I did not have any issue there.
Here is how the API call looks like:
And here is my code for the API fetch:
const FetchAPI = (props) => {
const [product, setProduct] = useState({});
const [brand, setBrand] = useState({});
const [price, setPrice] = useState({});
const [params, setParams] = useState({
id: "23363645",
lang: "en-US",
store: "US",
sizeSchema: "US",
currency: "USD",
});
useEffect(() => {
const options = {
method: "GET",
url: "https://asos2.p.rapidapi.com/products/v3/detail",
params: params,
headers: {
"x-rapidapi-key": "",
"x-rapidapi-host": "",
},
};
axios
.request(options)
.then(function (response) {
console.log(response.data);
setProduct(response.data);
setBrand(response.data.brand);
setPrice(response.data.price.current);
})
.catch(function (error) {
console.error(error);
});
}, []);
return (
<div>
<Product
name={product.name}
price={price.text}
brand={brand.description.replace( /(<([^>]+)>)/ig, '')}
/>
</div>
);
};
I am sending the data over to Product which is the product page of the item I am requesting. But whenever I refresh the page, I get TypeError: Cannot read property 'replace' of undefined. I did remove replace, and it worked fine. And if I placed replace back into the brand.description and saved, still worked fine. But on the page refresh, it crashes.
Is it perhaps trying to load my return before the useEffect? If so, how do I solve this?
I think the issue here is that while data from API is being fetched, brand.description is undefined and there is no replace method on undefined. You can either do this - >
<div>
<Product
name={product.name}
price={price.text}
brand={brand.description ? brand.description.replace( /(<([^>]+)>)/ig, '') : ""}
/>
</div>
or
const [brand, setBrand] = useState({ description: ""});
and keep the remaining code same.
First of all, unrelated to your question, you have many superfluous state variables. You have product which stores all the data of the product and then price and brand which stores subsets of the same data.
Consider using only the product state variable, or if you want to keep the names do something like
const price = product.price.current;
Second, your default value for brand is an empty object, meaning brand.description is undefined.
You can solve this with optional chaining like so:
<Product
name={product?.name}
price={price?.text}
brand={brand?.description?.replace( /(<([^>]+)>)/ig, '')}
/>
The useEffect hook gets called once the component is rendered so in this case initially when your API is not called your brand.description will be undefined and when you are trying to use replace on undefined the error is coming. So you can always add a check using optional chaining(?.) so even if we don't get the description in the brand it will not break the website and you should also use a loader till the API call is through.
<div>
<Product
name={product.name}
price={price.text}
brand={brand.description?.replace( /(<([^>]+)>)/ig, '')}
/>
</div>
useEffect gets called only after the component is rendered . So when the component is rendered for the first time you have the state brand as an empty object . So what you are trying to do is {}.description -> . This is undefined .
This is the reason why its a good practice to always have a loading state when the component is making an api call.
const [ loading, setLoading ] = useState(true);
const getProductDetails = async() => {
const options = {
method: "GET",
url: "https://asos2.p.rapidapi.com/products/v3/detail",
params: params,
headers: {
"x-rapidapi-key": "",
"x-rapidapi-host": "",
},
};
try {
const { data } = await axios.request(options);
setProduct(data);
setBrand(data.brand);
setPrice(data.price.current);
}
catch(error){
console.log(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
getProductDetails();
}, []);
if(loading)
return <p> Loading ... </p>
return (
// return your JSX here
)
Currently my app is accessing external data with fetch on mounting of the main component and whenever I reload the page it reloads the data and resets local storage items that I have linked to it.
However, what I would like is:
1) Very first time user opens the page the whole dataset must be loaded
2) If user removes any of the items in the table, then closes the tab or reloads the page, those removed items should not reappear in the dataset and the table (unless the "reload" button is clicked)
My main component currently looks like this:
function App() {
const DEFAULT_ERROR = null
const DEFAULT_IS_LOADED = false
const DEFAULT_DATASET = []
const DEFAULT_INPUT_VALUE = ''
const DEFAULT_DROPDOWN_VALUE = 'year'
const DEFAULT_GRAPH_DATA = []
const URL = 'https://reqres.in/api/unknown'
const [ error, setError ] = React.useState(DEFAULT_ERROR)
const [ isLoaded, setIsLoaded ] = React.useState(DEFAULT_IS_LOADED)
const [ dataset, setDataset ] = React.useState(DEFAULT_DATASET)
const [ inputValue, setInputValue ] = React.useState(DEFAULT_INPUT_VALUE)
const [ dropdownValue, setDropdownValue ] = React.useState(DEFAULT_DROPDOWN_VALUE)
const { graphData, setGraphData } = React.useState(DEFAULT_GRAPH_DATA)
localStorage.setItem('hasBeenLoaded', false)
let hasBeenLoaded = JSON.parse(localStorage.getItem('hasBeenLoaded'))
if (!hasBeenLoaded) {
localStorage.setItem('hasBeenLoaded', true)
hasBeenLoaded = JSON.parse(localStorage.getItem('hasBeenLoaded'))
console.log(hasBeenLoaded)
React.useEffect(() => {
requestData(URL, setIsLoaded, setDataset, setError)
}, [])
}
I tried storing a value in the local storage that tracks whether the app has been already loaded on users PC so it won't re-run the useEffect with fetch request, but for some reason it still seems to be running and any of the table items that I delete re-appear on reload.
The requestData function is the following:
function requestData(url, setIsLoaded, setDataset, setError) {
return fetch(url)
.then(response => response.json())
.then(
(result) => {
setIsLoaded(true)
for (let i=0; i < result.data.length; i++) {
localStorage.setItem(result.data[i].id, JSON.stringify(result.data[i]))
setDataset(
prevDataset => [...prevDataset, JSON.parse(localStorage.getItem(result.data[i].id))]
)
}
},
(error) => {
setIsLoaded(true)
setError(error)
}
)
}
What should I do in order to make my component work as planned? Here is my pen just in case.
Best regards,
Konstantin
As #charlietfl said, you are setting the value of hasBeenLoaded to false always, so it will fetch the data always, the logic for this to work would be:
check if the hasBeenLoaded variable exists in the LocalStorage
if not, call your requestData method (you don't even need the useEffect)
if yes, don't do anything
Then when the user clicks the reload button, then you set the variable to false
const hasBeenLoaded = localStorage.getItem('hasBeenLoaded')
if(!hasBeenLoaded){
requestData(URL, setIsLoaded, setDataset, setError)
localStorage.setItem('hasBeenLoaded', true)
}