How to re-run useEffect after a submit function? - reactjs

Hello Guys!
So in my Project, I do a fetch data function in useeffect but when I add a new element to the firestore I want that the useEffect to run again so in the list will contain the added element, somebody can give me advice on how can I do it ?
useEffect(() => {
if (session) {
fetchTodos();
}
}, [session]);
const fetchTodos = async () => {
const fetchedtodos = [];
const querySnapshot = await getDocs(collection(db, session.user.uid));
querySnapshot.forEach((doc) => {
return fetchedtodos.push({ id: doc.id, data: doc.data() });
});
setTodos(fetchedtodos);
};
const submitHandler = async (todo) => {
const data = await addDoc(collection(db, session.user.uid), {
todo,
createdAt: serverTimestamp(),
type: "active",
});
}
I want that when I run the submitHandler the useeffect run again so the list will be the newest

The only way to get a useEffect hook to run again, is to change something in the dependency array, or to not provide an array at all, and get the component to re-render (by changing props or state). See useEffect Documentation
You could just call fetchTodos directly after you call addDoc:
const submitHandler = async (todo) => {
const data = await addDoc(collection(db, session.user.uid), {
todo,
createdAt: serverTimestamp(),
type: "active",
});
return fetchTodos();
}

In my experience, the best way to do what you are trying to do is to have any requests that modify your data in the backend return the difference and then modify your state accordingly:
const submitHandler = async (todo) => {
const data = await addDoc(collection(db, session.user.uid), {
todo,
createdAt: serverTimestamp(),
type: 'active',
});
setTodos((prev) => [...prev, data]);
};
This way you don't have to do any large requests for what is mostly the same data within the same session.
Of course, this method is not ideal if multiple clients/users can modify your backend's data, or if you do not control what the endpoint responds with.
Hope this helps.

Related

React component doesn't render unless I refresh & duplicate render

I've been working on React/Redux app with firestore database
In my app I have simple POST request sent when the user send a message in the input field
and the data the user enters supposed to render in the same page without the need to refresh but I do still need to refresh even without deps in my useEffect!
Here's my code :
Post component
{posts.length > 0 &&
[...posts].map(({ id, data: { message, name, job, avatarUrl } }) => (
<Post
key={id}
name={name}
job={job}
message={message}
avatarUrl={avatarUrl}
/>
))}
However I also encounter a weird behavior after I refresh which is the components are rendered twice!Although my database be containing only one unique data for each messageThe react app renders it twice ( The querySnapshot from the database being added to the state arrayposts twice
useEffect
useEffect(() => {
querySnapshot();
});
}, []);
Database query:
const q = query(
collection(db, "posts"),
where("type", "==", "post"),
orderBy("postDate", "desc")
);
Retrieving the data :
const [input, setInput] = useState("");
const [posts, setPosts] = useState([]);
const [nextId, setNextId] = useState("0");
const addPost = (e) => {
e.preventDefault();
const docData = {
name: "mo",
job: "zoo",
message: input,
avatarUrl: "https://",
postDate: firebase.firestore.FieldValue.serverTimestamp(),
type: "post",
};
setDoc(doc(db, "posts", nextId.toString()), docData);
setNextId(parseInt(nextId) + 1);
setInput("");
};
async function querySnapshot() {
const querySnapshot = await getDocs(q);
console.log(querySnapshot.docs[0].data().message);
setNextId(querySnapshot.docs.length)
querySnapshot.docs.forEach((doc) => {
let data = {
id: doc.id,
data: doc.data(),
};
if (data && !posts.includes(data.id)) {
setPosts((current) => [...current, data]);
console.log("psts now", posts);
}
});
}
I tried to use the JavaScript Set by creating
useState(new Set()) but the same problem occurred of duplicate elements
I also tried to change deps of useEffect to render when the posts state array changes still not rendering untill I refresh
The duplication reason would be caused by setPosts(), I have updated the code as below, try to avoid setting the value inside the loop.
async function querySnapshot() {
const querySnapshot = await getDocs(q);
setNextId(querySnapshot.docs.length)
const data = querySnapshot.docs.map((doc)=>{
return {id:doc.id, data: doc.data()}
})
setPosts((current) => [...current, data])
}

Updating the state correctly after fetch data from firestore

I am trying to use Firestore Snapchats to get real time changes on a database. I am using react-native-cli: 2.0.1 and react-native: 0.64.1 .
export const WelcomeScreen = observer(function WelcomeScreen() {
const [listData, setListData] = useState([]);
const onResult = (querySnapshot) => {
const items = []
firestore()
.collection("Test")
.get()
.then((querySnapshot) => {
querySnapshot.forEach(function(doc) {
const tempData = {key: doc.id, data: doc.data}
items.push(tempData);
});
setListData(items)
});
}
const onError = (error) => {
console.error(error);
}
firestore().collection('Test').onSnapshot(onResult, onError);
}
Every thing is working perfectly, until I use setListData to update the data list. The App does not respond anymore and I get a warning message each time I try to add or delete data from the database
Please report: Excessive number of pending callbacks: 501. Some pending callbacks that might have leaked by never being called from native code
I am creating a deadlock by setting the state this way?
First, you don't want to set up a snapshot listener in the body of your component. This results in a growing number of listeners, because every time you render you add a new listener, but every listener results in rendering again, etc. So set up the listener just once in a useEffect:
const [listData, setListData] = useState([]);
useEffect(() => {
function onResult(querySnapshot) {
// ...
}
function onError(error) {
console.log(error);
}
const unsubscribe = firestore().collection('Test').onSnapshot(onResult, onError);
return unsubscribe;
}, []);
In addition, your onResult function is going to get called when you get the result, and yet you're having it turn around and immediately doing a get to re-request the data it already has. Instead, just use the snapshot you're given:
function onResult(querySnapshot) {
const items = []
querySnapshot.forEach(function(doc) {
const tempData = {key: doc.id, data: doc.data()}
items.push(tempData);
});
setListData(items);
}

Update state with Object using React Hooks

I'm getting data from Firebase and want to update state:
const [allProfile, setAllProfile] = useState([]);
.....
const displayProfileList = async () => {
try {
await profile
.get()
.then(querySnapshot => {
querySnapshot.docs.map(doc => {
const documentId = doc.id;
const nProfile = { id: documentId, doc: doc.data()}
console.log(nProfile);//nProfile contains data
setAllProfile([...allProfile, nProfile]);
console.log(allProfile); // is empty
}
);
})
} catch (error) {
console.log('xxx', error);
}
}
The setAllProfile will update the state when the iteration is done. So in order for your code to work, you will need to pass the callback function to the setAllProfile as shown in the docs
setAllProfile((prevState) => [...prevState, nProfile])
UPDATE
Example demonstrating this at work
Since setAllProfile is the asynchronous method, you can't get the updated value immediately after setAllProfile. You should get it inside useEffect with adding a allProfile dependency.
setAllProfile([...allProfile, nProfile]);
console.log(allProfile); // Old `allProfile` value will be printed, which is the initial empty array.
useEffect(() => {
console.log(allProfile);
}, [allProfile]);
UPDATE
const [allProfile, setAllProfile] = useState([]);
.....
const displayProfileList = async () => {
try {
await profile
.get()
.then(querySnapshot => {
const profiles = [];
querySnapshot.docs.map(doc => {
const documentId = doc.id;
const nProfile = { id: documentId, doc: doc.data()}
console.log(nProfile);//nProfile contains data
profiles.push(nProfile);
}
);
setAllProfile([...allProfile, ...profiles]);
})
} catch (error) {
console.log('xxx', error);
}
}
You are calling setState inside a map and therefore create few async calls, all referred to by current ..allProfile value call (and not prev => [...prev...)
Try
let arr=[]
querySnapshot.docs.map(doc => {
arr.push({ id: doc.id, doc: doc.data() })
}
setAllProfile(prev=>[...prev, ...arr])
I don't sure how the architecture of fetching the posts implemented (in terms of pagination and so on, so you might don't need to destruct ...prev

React hooks dependencies, including it creates an infinite loop, not including it doesn't give me the latest value

Using React hooks.
I'm trying to do a simple API fetch call with some data, but I can't seem to make this work.
Here is the sandbox link
In this example, the objective is that every 5secs, it fetches to the server to get any updates to the username since the latest latestUpdate.
But for convenience, I will include the code here as well:
const SmallComponent = () => {
const { id, username, latestUpdate } = useItemState();
const dispatch = useItemDispatch();
console.log("Render id", id, "Latest", latestUpdate);
const fetchUsername = useCallback(async () => {
console.log("Getting Id", id, "Latest", latestUpdate);
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/" + id
);
const user = await response.json();
dispatch({ type: "setUsername", usernameUpdated: user.name });
}, [dispatch, id]);
const updateId = useCallback(() => {
dispatch({ type: "setId", id: id + 1 });
}, [dispatch, id]);
useEffect(() => {
fetchUsername();
const refresh = setInterval(() => {
updateId();
}, 5000);
return () => clearInterval(refresh);
}, [fetchUsername, updateId]);
return (
<div>
<h4>Username from fetch:</h4>
<p>{username || "not set"}</p>
</div>
);
};
As you'll notice, my fetchUsername is missing a dependency for latestUpdate (which is used on my server to only send udpates since that date). I update latestUpdate when the fetchUsername is finished in my reducer.
What I need:
on mount: fetch username => updates state for username and latestUpdate
interval: every 5secs => fetch updates to username and update latestUpdate to new Date()
The problem is:
If I add the dependency to the useCallback for fetchUsername, I get an infinite refresh loop.
If I don't add it, my latestUpdate value is wrong (ie initial value)
What am I doing wrong?
As you're not using the fetch method anywhere else, it makes sense to put it inside the useEffect directly. No need for useCallback:
useEffect(() => {
const fetchUsername = async () => {
console.log("FETCH", latestUpdate);
const url =
"https://jsonplaceholder.typicode.com/users/" + id + "#" + latestUpdate;
const response = await fetch(url);
const user = await response.json();
dispatch({ type: "setUsername", usernameUpdated: user.name });
};
const refresh = setInterval(() => {
fetchUsername();
}, 5000);
return () => clearInterval(refresh);
}, [dispatch, id, latestUpdate]);
Here is the full CodeSandBox:
https://codesandbox.io/s/trusting-framework-hvw06?file=/src/App.js
You can find more in the official docs (look for "...to move that function inside of your effect"):
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
And I also recommend Robin Wieruch's hook-fetching tutorial: https://www.robinwieruch.de/react-hooks-fetch-data
In general, I would highly recommend using something like react-query, as it will also take care of caching. It is a better way to consume your server data (instead of fetching and putting the response in your context): https://github.com/tannerlinsley/react-query

Await not working in useEffect with redux

I am trying to fetch some data from the API and setting the data in my redux store. However, when I try to do some operation with the data from the redux store that variable is empty. I have used await but it seems it does not work. However, after useEffect the redux store(api data) data is visible and can do operations on it. Any help is appreciated. Please note I need to access the redux store field not just get the returned data from the function. Accessing the redux store field is important. Here is my code:
useEffect(() => {
async function loadData() {
const startingDateYear = moment();
const eventDates = generateDatesForYear(startingDateYear.year().toString());
await dispatch(fetchData('0009', eventDates[0], eventDates[1]));
}
loadData();
console.log(event.myDataArray) // event is a reducer and myDataArray is the field. It can be accessed outside the function with data incorporated but within useEffect I am not able to use the freshly fetched data.
return {};
}, []);
export const fetchData = (p1, p2, p3) => {
return async dispatch => {
const path = `dataFromAPIURL`;
try {
dispatch({
type: FETCH_STARTED,
});
const myDataArray = await RestService.get(path);
dispatch({
type: FETCH_FINISHED,
});
dispatch({
type: UPDATE_REDUCER_STATE,
payload: myDataArray,
});
return myDataArray;
} catch (error) {
// TODO: error handling
dispatch({ type: FETCH_ERROR, payload: error });
}
};
};
You need a second useEffect with dependency event.myDataArray
useEffect(() => {
async function loadData() {
const startingDateYear = moment();
const eventDates = generateDatesForYear(startingDateYear.year().toString());
await dispatch(fetchData("0009", eventDates[0], eventDates[1]));
}
loadData();
}, []);
useEffect(() => {
console.log(event.myDataArray);
}, [event.myDataArray]);

Resources