How to Avoid Fetching Data Twice When Using React Hooks - reactjs

I'm trying to fetch a set of questions from a Firestore database, and then store them in a local array for use later on in a quiz. As I'm working to fetch the data, it's running asynchronously. I've dealt with this before, and the solution was a useEffect() hook and conditionally rendering component.
My code currently looks like this:
var questions = [];
var collection = useRef('Planning');
var [loading, setLoading] = useState(true);
useEffect(() => {
if (props.track === 'Testing & Deployment') {
collection.current = 'Test_Deploy';
} else {
collection.current = props.track;
}
if(questions.length !== 0){
setLoading(false)
} else {
var questionSet = db.collection('Quizzes').doc(collection.current).collection('Questions')
questionSet.withConverter(questionConverter).get().then(function(response) {
response.forEach(document => {
var question = document.data();
// running asynch - need to address
console.log('Question in')
questions.push(question)
})
})
setLoading(false)
}
}, [props.track, questions])}
Depending on where setLoading() is, the component either won't re-render at all, or will double fetch the data from Firestore. I've tried playing around with useCallback() to no avail, and have also seen that this might be an issue with React StrictMode.
Any advice on how to fix my code in order to re-render the page after fetching all of the questions, without double fetching them?

To fetch data once on component mount you can specify an empty dependency array. I also suggest moving the questions object into local component state. Since the state is updated in a loop you should use a functional state update to add each new question to the array.
const [questions, setQuestions] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const questionSet = db
.collection("Quizzes")
.doc(collection.current)
.collection("Questions");
questionSet
.withConverter(questionConverter)
.get()
.then(function (response) {
response.forEach((document) => {
const question = document.data();
setQuestions(questions => [...questions, question]);
});
});
setLoading(false);
}, []);
If the linter complains about missing dependencies then you need to either add them and place the appropriate conditional check on the data fetch, or add a comment to ignore/disable the linter for the line with the dependency array, // eslint-disable-next-line react-hooks/exhaustive-deps.

It will rerender again when the props change or update including props.track and questions everytime when you push item into it. So if you want to get rid of it rerender then just remove from useEffect like
useEffect(() => {
.......
}, [loading])} // remove from here and add `loading` here just

Related

How can I stop react from making multiple network request simultaneously

I am making a get request to get data from my rest API, and when I have the data react will keep on making the same request simultaneously multiple times.
this is the code:
export default function Transaction() {
const [transactions, setTransaction] = useState([]);
const [loading, setLoading] = useState(true)
const { id } = useParams();
// this is where I am getting the problem
useEffect(() => {
const fetchTransc = async () => {
const res = await axios.get(`http://localhost:4000/api/get-records/${id}`);
setTransaction(res.data)
setLoading(false)
console.log(res.data)
};
fetchTransc();
},[id,transactions]);
The second argument of the UseEffect hook is the dependency array. It tells react which variables to watch for changes. When a dependency changes or when the component is mounted for the first time, the code inside the hook is executed.
In your case the array indicates that each time “id” or “transations” change, the hook should be executed.
Since setTransation is called in the fetch function, that will trigger the hook again because “transations” is present in hook’s the dependency array.
Each time the transactions state variable is set with a brand a new object fetched from the url, react will trigger the useEffect hook.
If “transations” is removed from the hook’s dependency array, this should work fine. Maybe also adding an IF to check the “id” value could be useful to prevent errors.
useEffect(() => {
const fetchTransc = async () => {
if(id != null) {
const res = await axios.get(`http://localhost:4000/api/get-records/${id}`);
setTransaction(res.data)
setLoading(false)
console.log(res.data)
}
};
fetchTransc();
},[id]);

React state is empty inside useEffect

Currently I'm building a pusher chat app with react. I'm trying to keep a list of online users. I'm using this code below:
const [users, setUsers] = useState([]);
useEffect(() => { // UseEffect so only called at first time render
window.Echo.join("server.0")
.here((allUsers) => {
let addUsers = [];
allUsers.map((u) => {
addUsers.push(u.name)
})
setUsers(addUsers);
})
.joining((user) => {
console.log(`User ${user.name} joined`);
setUsers([users, user]);
})
.leaving((user) => {
console.log(`User ${user.name} left`);
let addUsers = users;
addUsers.filter((u) => {
return u !== user.name;
})
setUsers(addUsers);
})}, []);
Whenever I subscribe to the pusher channel, I receive the users that are currently subscribed and the state is set correctly. All subscribed users are showing. However when a new user joins/leaves, the .joining/.leaving method is called and the users state is empty when I console log it. This way the users state is being set to only the newly added user and all other users are being ignored. I'm new to react so there is probably a simple explanation for this. I was not able to find the answer myself tough. I would really appreciate your help.
I saw the problem in joining. You need to update setState like this: setUsers([...users, user.name]);
And leaving also need to update:
const addUsers = users.filter((u) => {
return u !== user.name;
});
setUsers(addUsers);
here should also rewrite:
let addUsers = allUsers.map((u) => u.name);
setUsers(addUsers);
I found the issue. The problem is that when accessing state from within a callback funtion, it always returns the initial value. In my case an empty array. It does work when using a reference variable. I added the following lines:
const [users, _setUsers] = useState([]);
const usersRef = React.useRef(users);
const setUsers = data => {
usersRef.current = data;
_setUsers(data);
}
Each time I update the users state, I use the setUsers function. Now when I acces the state from inside my callback function with usersRef.current I get the latest state.
Also I used the code from the answer of #Viet to update the values correctly.

React Native + Firestore infinite loop, using hooks

just starting to learn hooks here.
I am getting data from firestore and trying to set it to state using hooks. when I uncomment the line doing so, I get stuck in an infinite loop. no error, but the console goes crazy with logging the state thousands of times.
Let me know if you need more info!
function Lists(props) {
const [lists, setLists] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const subscriber =
firestore().collection('users').doc(props.user).collection('lists')
.onSnapshot(QuerySnapshot => {
const items = []
QuerySnapshot.forEach(documentSnapshot => {
items.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
//setLists(items)
setLoading(false)
console.log(lists)
})
})
// unsubscribe from firestore
return () => subscriber();
})
//rest of func..
this issue happens becauase useEffect gets called over and over again. useEffect is like componentDidMount and componentDidUpdate if you are familiar with React class components.
so whenever you set the state inside the useEffect, you trigger an update, and then, useEffect gets called again, and thus the infinite loop.
to fix this, useEffect accepts a extra argument, which is an array of dependancies, which indicates that this useEffect call should only re-executed whenever a change happens to one of its dependancies. in your case you can provide an empty array, telling react that this useEffect should only be called one time.
useEffect(() => {
const subscriber =
firestore().collection('users').doc(props.user).collection('lists')
.onSnapshot(QuerySnapshot => {
const items = []
QuerySnapshot.forEach(documentSnapshot => {
items.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
//setLists(items)
setLoading(false)
console.log(lists)
})
})
// unsubscribe from firestore
return () => subscriber();
}, []) // <------------ the second argument we talked about

React Hooks Firebase - useEffect hook not returning any data

I am trying to use the useEffect hook in React to listen for changes in a location in firestore.
Initially I didn't have an empty array as the second prop in the useEffect method and I didn't unsubscribe from the onSnapshot listener. I received the correct data in the projects variable after a short delay.
However, when I experienced extreme performance issues, I added in the unsubscribe and empty array which I should have put in earlier. Strangely, now no data is returned but the performance issues are gone.
What might be preventing the variable updating to reflect the data in firestore?
function useProjects(organisation) {
const [projects, setProjects] = useState({
docs: []
});
useEffect(() => {
if (!organisation.docs[0]) return;
const unsubscribe = firebase.firestore().collection('organisations').doc(organisation.docs[0].id).collection("projects").onSnapshot(snapshot => {
setProjects(snapshot);
});
return () => unsubscribe()
}, []);
return projects
};
const projects = useProjects(organisation);
You'll need a value in the dependency array for the useEffect hook. I'd probably suggest the values you are using in the useEffectHook. Otherwise with [] as the dependency array, the effect will only trigger once (on mount) and never again. The point of the dependency array is to tell react to re run the hook whenever a dependency changes.
Here's an example I'd suggest based on what's in the hook currently (using the id that you send to firebase in the call). I'm using optional chaining here as it makes the logic less verbose.
function useProjects(organisation) {
const [projects, setProjects] = useState({
docs: []
});
useEffect(() => {
if (!organisation.docs[0]) return;
const unsubscribe = firebase.firestore().collection('organisations').doc(organisation.docs[0].id).collection("projects").onSnapshot(snapshot => {
setProjects(snapshot);
});
return () => unsubscribe()
}, [organization.docs[0]?.id]);
return projects
};

React Hooks: Referencing data that is stored inside context from inside useEffect()

I have a large JSON blob stored inside my Context that I can then make references to using jsonpath (https://www.npmjs.com/package/jsonpath)
How would I go about being able to access the context from inside useEffect() without having to add my context variable as a dependency (the context is updated at other places in the application)?
export default function JsonRpc({ task, dispatch }) {
const { data } = useContext(DataContext);
const [fetchData, setFetchData] = useState(null);
useEffect(() => {
task.keys.forEach(key => {
let val = jp.query(data, key.key)[0];
jp.value(task.payload, key.result_key, val);
});
let newPayload = {
jsonrpc: "2.0",
method: "call",
params: task.payload,
id: "1"
};
const domain = process.env.REACT_APP_WF_SERVER;
let params = {};
if (task.method === "GET") {
params = newPayload;
}
const domain_params =
JSON.parse(localStorage.getItem("domain_params")) || [];
domain_params.forEach(e => {
if (e.domain === domain) {
params[e.param] = e.value;
}
});
setFetchData({ ...task, payload: newPayload, params: params });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [task]);
}
I'm gonna need to post an answer because of code, but I'm not 100% sure about what you need, so I'll build a correct answer with your feedback :)
So, my first idea is: can't you split your effects in two React.useEffect? Something like this:
export default function JsonRpc({ task, dispatch }) {
...
useEffect(() => {
...
setFetchData(...);
}, [task]);
useEffect(() => {
...
}, [data]);
..
}
Now, if my understanding are correct, this is an example of events timeline:
Due to the update on task you will trigger the first useEffect, which can setFetchData();
Due to the update on fetchData, and AXIOS call is made, which updates data (property in the context);
At this, you enter the second useEffect, where you have the updated data, but NO call to setFetchData(), thus no loop;
Then, if you wanted (but couldn't) put data in the dependencies array of your useEffect, I can imagine the two useEffect I wrote have some shared code: you can write a common method called by both useEffects, BUT it's important that the setFetchData() call is outside this common method.
Let me know if you need more elaboration.
thanks for your reply #Jolly! I found a work around:
I moved the data lookup to a state initial calculation:
const [fetchData] = useState(processFetchData(task, data));
then im just making sure i clear the component after the axios call has been made by executing a complete function passed to the component from its parent.
This works for now, but if you have any other suggestions id love to hear them!

Resources