ReactJS call function once if query strings set - javascript

Explain:
In useEffect I get products via getProducts() function based on given data and data contains search filters and will be update by user so need to watch it in realtime, for example data contains an object like this {brand: 'x'}
const [data, setData] = useState({});
useEffect(() => {
getProducts(data) // get products via api based on data, if data is clear, it will return all products
.then(data => {
//
});
}, [data]) // watch data in realtime and send it to getProducts()
useEffect(() => {
getQuery(); // check if there are search querys
}, [])
Also there is a getQuery() function, it will check if there are any query strings in search params when page is reload, and will get query strings and set it to data, and by above code if data get update it will call getProducts() again, actually it update products.
const getQuery = () => {
let obj_url = new URL(window.location.href);
let params = obj_url.searchParams;
let searchParams = new URLSearchParams(params);
// some other code
setData(obj); // get query and set it to data
}
Problem:
I noticed while there are query strings in url it will call getProducts() twice! well this code definitely does that, but I don't want it, how can I prevent to call this twice? I just want if there are query strings call once, if not call once.

Yes, it will call it once for the initialization of useState, and second time for the updated data from getQuery on the useEffect. You don't really need a useEffect for the getQuery function. You can just set the data from query on the initial state directly (the initial state will only be set on the initial render anyway, not on subsequent ones).
const [data, setData] = useState(getQuery());
useEffect(() => {
getProducts(data) // get products via api based on data, if data is clear, it will return all products
.then(data => {
//
});
}, [data]) // watch data in realtime and send it to getProducts()
const getQuery = () => {
let obj_url = new URL(window.location.href);
let params = obj_url.searchParams;
let searchParams = new URLSearchParams(params);
// some other code
return obj ?? {}
}

Bad idea to use that function in state initialization, you can do this with native way, react-router-dom. Based on #Cristian-Florin Calina answer, I provide new answer:
import { useRouter } from 'next/router';
const Router = useRouter();
const [data, setData] = useState(Router.query);
useEffect(() => {
getProducts(data)
.then(data => {
//
});
}, [data]);
Router.query return all search params and with this there is no need to call getQuery function. easy!

Related

Why changes made in database not updates in realtime?

I am trying to learn firestore realtime functionality.
Here is my code where I fetch the data:
useEffect(() => {
let temp = [];
db.collection("users")
.doc(userId)
.onSnapshot((docs) => {
for (let t in docs.data().contacts) {
temp.push(docs.data().contacts[t]);
}
setContactArr(temp);
});
}, []);
Here is my database structure:
When I change the data in the database I am unable to see the change in realtime. I have to refresh the window to see the change.
Please guide me on what I am doing wrong.
Few issues with your useEffect hook:
You declared the temp array in the way that the array reference is persistent, setting data with setter function from useState requires the reference to be new in order to detect changes. So your temp array is updated (in a wrong way btw, you need to cleanup it due to now it will have duplicates) but React is not detectign changes due to the reference to array is not changed.
You are missing userId in the dependency array of useEffect. If userId is changed - you will continue getting the values for old userId.
onSnapshot returns the unsubscribe method, you have to call it on component unMount (or on deps array change) in order to stop this onSnapshot, or it will continue to work and it will be a leak.
useEffect(() => {
// no need to continue if userId is undefined or null
// (or '0' but i guess it is a string in your case)
if (!userId) return;
const unsub = db
.collection("users")
.doc(userId)
.onSnapshot((docs) => {
const newItems = Object.entries(
docs.data().contacts
).map(([key, values]) => ({ id: key, ...values }));
setContactArr(newItems);
});
// cleanup function
return () => {
unsub(); // unsubscribe
setContactArr([]); // clear contacts data (in case userId changed)
};
}, [userId]); // added userId

Trying to store FireStore array in React Native?

I have been trying to push or store a FireStore array in one of my own arrays. I have tried a few versions of code, the first being this:
var data = [];
db.collection('Rooms')
.doc(code)
.get()
.then((docs) => data.push(docs.data()));
However, when I log the data variable, it comes out as an empty array. The second method I have tried is this:
var [data, setData] = useState([]);
db.collection("Rooms")
.doc(code)
.get()
.then((docs) => setData(docs.data()));
However this method seems to setData infinitely, so it is reading into my API infinitely, which I would like to avoid. The last method I tried was this:
var data = db.collection("Rooms").doc(code).get();
console.log(data);
But this just returns
Promise {
"_U": 0,
"_V": 0,
"_W": null,
"_X": null,
}
Could anyone help me with this, ideally I'd like to store the data of an array called "MovieArray" inside the document, but I can't even access the document, so even if you can just help me store the data of the whole document, it would be very helpful.
If you are using react, I would suggest using the hook. You also, don't really need to push objects to an array like that.
Here is an example of how to get some data and store the collection of data.
const Forum = () => {
const [posts, setPosts] = useState(null);
const collectIdsAndDocs = (doc) => {
return { id: doc.id, ...doc.data() };
};
useEffect(() => {
const getPost = async () => {
const snapshot = await firestore.collection('Posts').get();
const myPosts = snapshot.docs.map(collectIdsAndDocs);
console.log(myPosts);
setPosts({ myPosts });
};
const createPost = async (post) => {
const docRef = await firestore.collection('Posts').add(post);
const doc = await docRef.get();
console.log(doc);
};
createPost({ Title: 'My First Post', Content: 'My content' });
getPost();
}, []);
return (
// return some JSX
);
};
Why does this work?
When you get a collection, Firebase returns a snapshot of the collection.
This snapshot has a list of docs or an array if you will.
We then want to map over those docs constructing a new object that contains just the document data and the ID of individual doc. This is what the myPosts variable is.
Using the react state hook, you can set that object to the current state of Posts, in your case this would be rooms.
When you add something to the database, Firestore will return a reference to the newly added item. You can then call get() to get the document back if you need it.
Try changing to (see comment before this)
const [data, setData] = useState({});

How to re-render react component depends on a file?

I have a file that stores an array of objects. I have a component that fetches data from this file then render the list. The file could be updated somewhere else, I need the component to be updated if the file is modified. I have following code example
const header = () => {
const [list, setList] = useState([]);
// fetch
useEffect(() => {
const loadList = async () => {
const tempList = await getList("/getlist"); // get call to fetch data from file
setList(tempList);
};
loadList ();
}, [list]);
// function to render content
const renderList = () => {
return list.map(obj => (
<div key={obj.name}>
{obj.name}
</div>
));
};
return (
<div>{renderList()}</div>
)
}
// get call
router.get('/getlist',
asyncWrapper(async (req, res) => {
const result = await getList();
res.status(200).json(result).end();
})
);
const getList= async () => {
const list = JSON.parse(await fs.readFile(listPath));
return list;
}
Code has been simplified. If I remove the list from useEffect, then it will only render once and will never update unless I refresh the page. If I include list there, loadList() will get called constantly, and component will get re-rendered again and again. This is not the behavior I want. I am just wondering without making header component async component, how do I only re-render this component when the file is changed?
Thank you very much.
There are two approaches you can take to this:
Polling
Request the URL on an interval, and clear it when the component is unmounted.
Replace loadList () with:
const interval = setInterval(loadList, 60000); // Adjust interval as desired
return () => clearInterval(interval)
Make sure the cache control headers set in the response to /getlist don't stop the browser from noticing updates.
Server push
Rip out your current code to get the data and replace it with something using websockets, possibly via Socket.IO. (There are plenty of tutorials for using Socket.io with React that can be found with Google, but its rather too involved to be part of a SO answer).

fetch json data to state array

I fetch and get the data, I want to save it to state using setState as an array or object so I can call to each field in the return part and display it on screen
but I get this error:
Uncaught Error: Objects are not valid as a React child (found: object
with keys {gender, name, location, email, login, dob, registered,
phone, cell, id, picture, nat}). If you meant to render a collection
of children, use an array instead
import {useState} from 'react'
export default function Test(){
const [data, setData] = useState([]);
const getInfo = async () =>{
const response = await fetch('https://randomuser.me/api');
if(response.status === 200){
const res = await response.json();
setData(res.results[0]);
// also try this: setData(res)
// also try this: setData(res.result)
}else{
throw new Error('Unable to fetch data')
}
}
return(
<div>
<button onClick={() =>{getInfo()}}>fetch data</button>
{data}
</div>
)
}
Please have a look at this codesandbox URL, I feel you can make use of useEffect for your usecase link
The problem is your are getting an response which is not an array. You are trying to use higher order array methode on a object. Use ,
const resultArray = res.results;
get the array of results from the JSON.
It seems like res.results[0] is the object that has the keys of gender, name, location, email, login, dob, registered, phone, cell, id, picture, nat. But you are trying to set the state value data as an array.
You should set the data type to object.
In render function to browse the data, iterate the keys using Object.keys().
for the onClick triggering way in render, you don't need to () => {getInfo()} since there is no specific parameters passed to the method, just use onClick={getInfo}.
import {useState} from 'react'
export default function Test(){
const [data, setData] = useState({});
const getInfo = async () =>{
const response = await fetch('https://randomuser.me/api');
if(response.status === 200){
const res = await response.json();
setData(res.results[0]);
// also try this: setData(res)
// also try this: setData(res.result)
}else{
throw new Error('Unable to fetch data')
}
}
return(
<div>
<button onClick={getInfo}>fetch data</button>
{Object.keys.map(key => (<div>
{key} : {data.key}
</div>)}
</div>
)
}
The data being returned here from "https://randomuser.me/api" is not a simple JSON data. It is really a JSON object with multiple sub-objects in it such as "name", which is again having "title", "first" and "last" as sub-objects. This is the reason you are getting error that "found: object with keys".
If you are looking for dummy data for testing purpose, I would suggest you to check "https://jsonplaceholder.typicode.com/", which is a good source of dummy JSON data.
I have updated your code as under, which is returning data in proper JSON format and can be assigned to state.
const getInfo = async () =>{
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if(response.status === 200){
const res = await response.json();
console.log(res)
setData(res);
}else{
throw new Error('Unable to fetch data')
}
}
Your problem might be that you are using setData(res.results[0]);
I bet that this is an Object and not an Array.
const [data, setData] = useState([]);
states that the setData function only stores arrays.
try using
const [data, setData] = useState({});
and see if that is working
if it is not working give us the ouput of console.log(res.results)
and console.log(typeof(res.results[0]))

Store data from useQuery with useState

I'm using React hooks both to fetch GraphQL data with react-apollo and to store local state:
const [userData, setUserData] = useState({})
const { loading, error, data } = useQuery(USER_QUERY)
However, I'm wondering how to store data to userData. Is this how it's supposed to work:
useEffect(() => {
setUserData(data)
}, [Object.entries(data).length])
Looks like what you have probably works. There is also a onCompleted option available in the options parameter. it takes a callback of type:
(data: TData | {}) => void
so this is another way of doing it:
const { loading, error, data } = useQuery(USER_QUERY, {onCompleted: setUserData})
What are you trying to do with the returned data that you are unable to accomplish by simply using it as destructured from the query hook? In most use cases it can be used immediately, as it will update itself when refetched.
If it is necessary (and it could be), as the other answer says, the useEffect hook you posted should work, but I would replace the dependency with simply data, to prevent an edge case where the response has an equal length consisting of different data and does not update:
useEffect(() => {
setUserData(data)
}, [data])
I think something like this would work - you will need to create the initial state with useState, could be empty array and then onComplete in the useQuery would setTranscationsData... it is triggered every render when state or props change. Could of course add an inital state inside useState which insn't an empty array.
const [transactionsData, setTransactionsData] = React.useState([]);
const { error, data } = useQuery(GET_TRANSACTIONS, {
onCompleted: () => {
setTransactionsData(data.transactions);
},
});
another example
const [getLegalStatement] = useLazyQuery(GET_LEGAL_STATEMENT, {
fetchPolicy: 'network-only',
onCompleted: (data) => {
setTempLegalStatement(data.getLegalStatement);
},
onError: () => {
setTempLegalStatement({
consentedLegalStatementHash: '',
consentedSuppliersHash: '',
statement: '',
suppliersModal: '',
});
setTimeout(() => {
setRefetchNeeded(true);
}, 10000);
},
});
Use onSuccess
const [userData, setUserData] = useState({})
const { data, isLoading, error } = useQuery('QueryKey', QueryFunction, { onSuccess: setUserData })
This onSuccess callback function will fire setUserData(data) for you automatically any time the query successfully fetches new data.
To elaborate above, you can't use onSuccess/onSettled because those will not rerun if the data is cached, so if you leave the component and come back before the query expires your data won't get set.

Categories

Resources