Help 'm having trouble re-rendering a page on a react function component. I want to fetch data on the initial load of a page, that's why I use an uuid that is fed to the page as a props and initiated it as an empty string when the page loaded for the first time. But setting the uuid or attachment a new value doesn't re-render the page. What did I do wrong here?
const [attachments, setAttachments] = React.useState<IAttachment[]>([]);
const [uuid, setUuid] = React.useState('');
React.useEffect(() => {
if (uuid !== props.uuid) {
const fetchAttachment = async () => {
const accessToken: string = AuthHelper.GetAccessToken() || '';
if (accessToken) {
if (activeItem.AttachmentFolderId !== undefined) {
const attachments: IAttachment[] = await GraphService.getAttachments(accessToken, activeItem.CreatorId, activeItem.AttachmentFolderId);
setAttachments([...attachments]);
}
}
}
fetchAttachment();
setUuid(props.uuid);
console.log('State updated');
}
}, []);
attachments is used as a props for a list component.
Related
I have the following code in my React component:
const { id } = useParams();
const { tripData, facilityData } = useContext(AppContext);
const [data, setData] = useState([]);
useEffect(() => {
const idResults = facilityData.filter(facility => facility.id === id);
if (idResults.length > 0) {
setData(idResults[0]);
}
}, [])
Where:
[data, SetData] is the state that is used to handle populating a container
facilityData is data accessed from my app context
id is accessed from the URL
What seems to happen is that the data loads the first time without fault, but it errors out when hosted on the actual site (on localhost, it waits and eventually loads). To try to get a better idea of what was happening, I tried the following code:
const { id } = useParams();
const { tripData, facilityData } = useContext(AppContext);
const [data, setData] = useState([]);
useEffect(() => {
const idResults = facilityData.filter(facility => facility.id === id);
if (idResults.length > 0) {
setData(idResults[0]);
} else if (idResults.length === 0) {
console.log(`id: ${id}`)
console.log(`len: ${idResults}`)
}, [])
On localhost, on refresh, it console logs the actual id but then console logs the empty array before finally loading the data.
What I'm wondering is why this is the observed behavior. The "id" value seems to be constantly available, but the filter doesn't seem to run prior to the site loading. Is there a way to prevent this?
EDIT:
This is how I get the data (from Firebase)
App.js
import { collection, getDocs } from "firebase/firestore";
import { db } from "./firebase";
const [truckData, setTruckData] = useState([]);
const [facilityData, setFacilityData] = useState([]);
const [tripData, setTripData] = useState([]);
useEffect(() => {
const fetchData = async (resource, setter) => {
let list = [];
try {
const querySnapshot = await getDocs(collection(db, resource));
querySnapshot.forEach((doc) => {
let docData = doc.data();
if (resource === "trips") {
docData.startDate = docData.startDate.toDate();
docData.endDate = docData.endDate.toDate();
}
list.push({ id: doc.id, ...docData });
});
setter(list);
} catch (error) {
console.log(error);
}
};
fetchData("trucks", setTruckData);
fetchData("facilities", setFacilityData);
fetchData("trips", setTripData);
}, []);
The app is at logi-dashboard, if that helps any.
EDIT Turns out the issue was with my hosting service, not the project. Go figure.
Based on my understanding, it seems like the facilityData on which you are trying to apply filter and which is coming from AppContext(Context hook variable) is found to be empty array when the useEffect code is getting executed, this might be scene if you are hitting any API to get the data into facility but the API response is not coming till the time useEffect is getting executed or any other source which is not populating the facilityData until useEffect runs.
In that case, you can add facilityData in the dependency array of useEffect, which will help the useEffect execute again once the facilityData is populated(updated)
This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 11 months ago.
I have a simple page editor, When a user clicks edit page it opens an editor. I am passing the ID of the page using redux which will be used to get data from API.
Here is my Editor.
const [pageData, setPageData] = useState("");
const getPage = async (id) => {
try {
const response = await api.get(`/landing_pages/${id}`);
console.log("page", response.data); // displays data at the end
setPageData(response.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getPage(pageID);
console.log('Page Data', pageData) // displays nothing
let LandingPage = pageData;
const editor = grapesjs.init({
container: "#editor",
components: LandingPage.components || LandingPage.html,
})
}, [pageID, getPage])
Why is Page Data display nothing even though the data from API is returned and is displayed in the console at the end? what am I doing wrong here?
Even if you await your getPage call, the updated pageData won't be available until the next render cycle so your assignment to LandingPage will be one cycle behind.
You should instead update in one useEffect and watch for changes to pageData in another.
const [pageData, setPageData] = useState("");
useEffect(() => {
const getPage = async (id) => {
try {
const response = await api.get(`/landing_pages/${id}`);
console.log("page", response.data); // displays data at the end
setPageData(response.data);
} catch (error) {
console.log(error);
}
};
getPage(pageID);
}, [pageID]);
useEffect(() => {
console.log('Page Data', pageData); // displays updated pageData
let LandingPage = pageData;
const editor = grapesjs.init({
container: "#editor",
components: LandingPage.components || LandingPage.html,
});
}, [pageData]);
I'm using react, node express, postgres
I have a react component that is an html table that gets populated from a postgres table.
Here is parent component Materials:
const Materials = () => {
const [thickness1, setThickness] = useState(0);
const [width1, setWidth] = useState(0);
const [length1, setLength] = useState(0);
const [partTotalDemand, setTotalDemand] = useState(0);
const [partPlanned, setPlanned] = useState(0);
...
Here is a method in the component that retrieves data
// Material requirements calculation
const getReq = async (id) => {
try {
const response = await fetch(`http://localhost:5000/materials/${id}`, [id])
const jsonData = await response.json();
const tempThickness = jsonData.parts_material_thickness
const tempWidth = jsonData.parts_material_width
const tempLength = jsonData.parts_material_length
const tempTotalDemand = jsonData.workorder_total
const tempPlanned = jsonData.parts_produced
stateSetter(tempThickness, tempWidth, tempLength)
} catch (err) {
console.log(err.message);
}
}
I then want to update the states of the global constants:
const stateSetter = (thickness, width, length) => {
try {
setThickness(thickness);
setWidth(width);
setLength(length);
console.log(thickness1);
console.log(width1);
console.log(length1);
} catch (err) {
console.log(err.message)
}
}
useEffect(() => {
stateSetter();
}, [thickness1]);
Essentially the getReq() method is supposed to retrieve the information, and then I need to update the states with those values. As I understand I then need to re-render the component so the new states are usable. I attempted to do this via useEffect() but I'm not successful. The idea was to stop getReq() from firing up on the first render, but if the state changes for thickness1/width1/length1 then it should fire up and re-render, help much appreciated!
You're over-complicating this. All you need to do is set the state values:
const getReq = async (id) => {
try {
const response = await fetch(`http://localhost:5000/materials/${id}`, [id])
const jsonData = await response.json();
// set state values
setThickness(jsonData.parts_material_thickness);
setWidth(jsonData.parts_material_width);
setLength(jsonData.parts_material_length);
setTotalDemand(jsonData.workorder_total);
setPlanned(jsonData.parts_produced);
} catch (err) {
console.log(err.message);
}
}
You don't need to manually do anything to re-render the component. It will re-render whenever state is updated. So the "setter" functions being invoked here will trigger that re-render. (All of the state updates will be batched. So the above won't trigger 5 re-renders, just one with the 5 updated state values.)
Where you would use useEffect is when you want to have some logic which responds to a change in a particular state. For example, if you want to show a message every time thickness changes to a negative value, you'd do something like:
useEffect(() => {
if (thickness < 1) {
alert('negative thickness!');
}
}, [thickness]);
But that's not what you're doing here. All you're doing here is setting state values.
I am using Apollo for graphQL queries in a NextJS project I have the following request. I feel like the solution to this is simple, but the error occurs at the stateData.allProducts section it is saying it is null, but I have set the state in the useEffect and it has data as a dependency in the array, so shouldn't it re-render once data has loaded.
This is all works just fine if I placed these checks below a if statement checking the loading and return ...loading but then I can't use useEffect.
Any help as to what I am doing wrong would be greatly appreciated.
Thanks!
const { data, loading, error } = useQuery(QUERY);
const Router = useRouter();
// Creating state and setting it from query
const [stateData, setStateData] = useState(null);
const [disableAddToCart, setDisableAddToCart] = useState(false);
useEffect(() => {
setStateData(data);
}, [data]);
//~~~~~~~// <--- RIGHT HERE IS WHERE I GET THE NULL ERROR
const productFound = stateData.allProduct.find(
(product: any) => product.slug.current === Router.query.product
);
const currentItem = cartDetails[productFound.id];
useEffect((): void => {
console.log("currentItem", currentItem);
if (currentItem) {
if (currentItem.quantity > 0) {
setDisableAddToCart(true);
} else {
setDisableAddToCart(false);
}
}
}, [currentItem]);
As long as your query is loading, or if there is an error, the data variable from useQuery will be null.
Therefore you have to check for the loading to have finished and that no error has occurred. And/or for the data to be defined.
Also, stateData is unnecessary, because data is already a ready-to-use state variable.
const { data, loading, error } = useQuery(QUERY);
const Router = useRouter();
const [disableAddToCart, setDisableAddToCart] = useState(false);
let productFound;
let currentItem;
if(!loading && !error && data?.allProduct){
productFound = data.allProduct.find(
(product: any) => product.slug.current === Router.query.product
);
currentItem = cartDetails[productFound.id];
}
useEffect((): void => {
//since you are using typescript, you can use the optional chaining operator
if (currentItem?.quantity > 0) {
setDisableAddToCart(true);
} else {
setDisableAddToCart(false);
}
}, [currentItem]);
I'm creating an application where users can create and share notes.
To share each other's notes users have to send requests to specific users.
The requests are fetched whenever home is loaded.
However, requests is a context since it is also consumed in the toolbar and requests page to show the presence of the requests
When I'm using setRequsts method of the context to set all the requests after home loads, the fetch goes into an infinite loop of /noteand /me URLs, since the setRequests method is also provided in the dependency array of useEffect
When removed, useEffect show missing dependencies. What's the work around?
const {setRequests } = useContext(RequestsContext)
const [notes, setNotes] = useState([])
const [fetched, setFetched] = useState('')
const { isAuthenticated } = props
const {page}=useContext(PageContext)
const [sortBy,setSortBy]=useState('latest')
useEffect(() => {
const fetch = async () => {
try {
let url = 'http://192.168.56.1:5000/api/v1/note', p, sort
if (page) p = `?page=${page}&limit=12`
if (sortBy === 'latest') {
sort=''
} else if (sortBy === 'most_liked') {
sort='&sort=likes'
}
const res = await Axios.get(url+p+sort)
setNotes(res.data.data)
if (res.data.data.length > 0) {
setFetched('Y')
} else {
setFetched('N')
}
} catch (err) {
console.log(err)
} finally {
if (isAuthenticated) {
const fetch = async () => {
const res = await axios.get(`user/me`)
if (res.data.data.createdPosts.length > 0) {
const arr = res.data.data.createdPosts.map(el => el.request)
console.log(arr)
setRequests(arr)
}
}
fetch()
}
}
}
fetch()
}, [isAuthenticated, /* setRequests, */ page, sortBy])
The problem is that the context provides a technically different setRequests function on each render (that have a different address). This causes useEffect to fire on each render.
To work around this, you could wrap setRequests in a useCallback() hook, like so:
// ...
const wrappedSetRequests = useCallback(setRequests, []);
// ...
useEffect(() => {
// do your stuff using 'wrappedSetRequests' instead of setRequests.
}, [ wrappedSetRequests /*...*/ ]);