I have a react native app where i am fetching orders made by customer and display them in restaurant side in a specific screen, I am using fetch api to get the data,
so THE WORKFLOW is customer first place order and store it in database and in the restaurant side i have this function :
const loadData = async () => {
const response = await fetch(`${API_URL}/getActiveOrders?ID=${id}`);
const result = await response.json();
if (result.auth === true) {
setCurrentOrders(result.data)
} else {
setCurrentOrders([])
}
}
useEffect(() => {
const interval = setInterval(() => {
loadData();
}, 1000)
return () => clearInterval(interval)
}, [id]);
as in this function it runs every second and it makes api call to express server to fetch the data from database so i keep restaurant receiving orders without delay. But i noticed that the app is lagging when the interval set to 1 second and it keep making frequent calls to the express server.
my question: Is this the best approach to perform same as this scenario (fetching the orders the moment they been placed by the customer) or Is there a better way to do it without lagging as well as when fetching large data will the performance remain the same or there will be some issues?
If I had to guess what the cause of the lagging issue was it would be because you have a dependency on the useEffect hook. Each time the state updates it triggers another render and the effect runs again, and sets up another interval, and your app is doing this once every second.
I suggest removing the dependency and run the effect only once when the component mounts, and clearing the interval when unmounting.
useEffect(() => {
const interval = setInterval(() => {
loadData();
}, 1000)
return () => clearInterval(interval)
}, []);
If the id for the GET requests updates and you need the latest value, then use a React ref to store the id value and use this in the request URL.
const idRef = React.useRef(id);
useEffect(() => {
idRef.current = id;
}, [id]);
const loadData = async () => {
const response = await fetch(`${API_URL}/getActiveOrders?ID=${idRef.current}`);
const result = await response.json();
setCurrentOrders(result.auth ? result.data : []);
}
You should use websockets in this case. This is the best scenario to use websockets. Your scenario is just like a trading website.
Related
Hi guys hoping someone can help me with an issue. I built a function that fetches user posts from backend and returns them as a response:
const [posts, setPosts] = useState([]);
const newsFeed = async () => {
await axios
.post(`${process.env.REACT_APP_API}/news-feed`)
.then((res) => {
setPosts(res.data);
})
.catch((err) => {
console.log(err);
});
};
This function gets called by useEffect and also whenever a post is submitted, liked, commented on etc. so that users' posts are always re-rendered every time they are modified or added.
It was working fine until I decided to implement Infinite Scroll to my application. I installed the npm package react-infinite-scroll-component and modified my function to look like this:
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const newsFeed = async () => {
await axios
.post(
`${process.env.REACT_APP_API}/news-feed/${page}`)
.then((res) => {
let newPosts = posts;
newPosts = newPosts.concat(res.data);
setPosts(newPosts);
setPage(page + 1);
})
.catch((err) => {
console.log(err);
});
};
The infinite scroll is working just fine but now the posts are not being re-rendered every time this function gets called and instead I need to refresh the page to see changes. I tried resetting the state of page back to 1 again on my postSubmit/likeHandler functions but this didn't have any effect. I'm not seeing any errors in the console so am unsure what is going on.
Replace let newPosts = posts; with let newPosts = [...posts]; Passing the same array reference to setState will not cause an update, since the value hasn't changed. By using [...posts], you are creating a new array, causing the component to update.
So I am working in a React platform that has data that updates every second(I would like to move to web-sockets but its currently only supports gets).Currently, each component makes a fetch request for itself to get the data for the widget. Because the fetch requests are built into the widgets there are redundant api requests for the same data. I am looking for a possible better solution to remove these redundant api requests.
The solution I came up with uses what I call a data service that checks for any subscription to data sources then makes those api calls and places the data in a redux state for the components to then be used. I am unsure if this is the best way to go about handling the issue I am trying to avoid. I don't like how I need an interval to be run every second the app is running to check if there are "subscriptions". I am unsure if thats the correct way to go about it. With this solution I don't duplicate any requests and can add or remove a subscription without affecting other components.
One more thing, the id can change and will change what data I recieve
Here is a simplified version of how I am handling the service.
const reduxState = {
id: "specific-id",
subscriptions: {
sourceOne: ["source-1-id-1", "source-1-id-2", "source-1-id-3"],
sourceTwo: ["source-2-id-1", "source-one-id-2"],
},
data: {
sourceOne: { id: "specific-id", time: "milliseconds", data: "apidata" },
sourceTwo: { id: "specific-id", time: "milliseconds", data: "apidata" },
},
};
const getState = () => reduxState; //Would be a dispatch to always get current redux state
const dataService = () => {
const interval = setInterval(() => {
const state = getState();
if (state.subscriptions.sourceOne.length > 0)
fetchSourcOneAndStoreInRedux();
if (state.subscriptions.sourceTwo.length > 0)
fetchSourceTwoAndStoreInRedux();
}, 1000);
};
const fetchSourcOneAndStoreInRedux = (id) =>{
return async dispatch => {
try {
const res = await axios.get(`/data/one/${id}`)
dispatch(setSourceOneDataRedux(res.data))
} catch (err) {
console.error(err)
}
}
}
I am building my components to only show data from the correct id.
Here is a simple working example of a simple "DataManager" that would achieve what you are looking for.
class DataManager {
constructor(config = {}) {
this.config = config;
console.log(`DataManager: Endpoint "${this.config.endpoint}" initialized.`);
if (this.config.autostart) { // Autostart the manager if autostart property is true
this.start();
}
}
config; // The config object passed to the constructor when initialized
fetchInterval; // The reference to the interval function that fetches the data
data; // Make sure you make this state object observable via MOBX, Redux etc so your component will re-render when data changes.
fetching = false; // Boolean indicating if the APIManager is in the process of fetching data (prevent overlapping requests if response is slow from server)
// Can be used to update the frequency the data is being fetched after the class has been instantiated
// If interval already has been started, stop it and update it with the new interval frequency and start the interval again
updateInterval = (ms) => {
if (this.fetchInterval) {
this.stop();
console.log(`DataManager: Updating interval to ${ms} for endpoint ${this.config.endpoint}.`);
this.config.interval = ms;
this.start();
} else {
this.config.interval = ms;
}
return this;
}
// Start the interval function that polls the endpoint
start = () => {
if (this.fetchInterval) {
clearInterval(this.fetchInterval);
console.log(`DataManager: Already running! Clearing interval so it can be restarted.`);
}
this.fetchInterval = setInterval(async () => {
if (!this.fetching) {
console.log(`DataManager: Fetching data for endpoint "${this.config.endpoint}".`);
this.fetching = true;
// const res = await axios.get(this.config.endpoint);
// Commented out for demo purposes but you would uncomment this and clear the anonymous function below
const res = {};
(() => {
res.data = {
dataProp1: 1234,
dataProp2: 4567
}
})();
this.fetching = false;
this.data = res.data;
} else {
console.log(`DataManager: Waiting for pending response for endpoint "${this.config.endpoint}".`);
}
}, this.config.interval);
return this;
}
// Stop the interval function that polls the endpoint
stop = () => {
if (this.fetchInterval) {
clearInterval(this.fetchInterval);
console.log(`DataManager: Endpoint "${this.config.endpoint}" stopped.`);
} else {
console.log(`DataManager: Nothing to stop for endpoint "${this.config.endpoint}".`);
}
return this;
}
}
const SharedComponentState = {
source1: new DataManager({
interval: 1000,
endpoint: `/data/one/someId`,
autostart: true
}),
source2: new DataManager({
interval: 5000,
endpoint: `/data/two/someId`,
autostart: true
}),
source3: new DataManager({
interval: 10000,
endpoint: `/data/three/someId`,
autostart: true
})
};
setTimeout(() => { // For Demo Purposes, Stopping and starting DataManager.
SharedComponentState.source1.stop();
SharedComponentState.source1.updateInterval(2000);
SharedComponentState.source1.start();
}, 10000);
// Heres what it would look like to access the DataManager data (fetched from the api)
// You will need to make sure you pass the SharedComponentState object as a prop to the components or use another React mechanism for making that SharedComponentState accessible to the components in your app
// Accessing state for source 1: SharedComponentState.source1.data
// Accessing state for source 2: SharedComponentState.source2.data
// Accessing state for source 3: SharedComponentState.source3.data
Basically, each instance of the DataManager class is responsible for fetching a different api endpoint. I included a few other class methods that allow you to start, stop and update the polling frequency of the DataManager instance.
I'm making a react app that works with a API that provides data to my App. In my data base I have data about pins on a map. I want to show the info of those pins on my react app, I want them to render. I get that information with axios and this url: http://warm-hamlet-63390.herokuapp.com/pin/list
I want to retrieve the info from that url, with axios.get(url), stringify the JSON data and then parse it to an array of pins.
The Problem:
My page will be rendered before I get the data back from the server, because axios is async, so I will not be able to show anything. UseEffect and useState won't work because I need something in the first place (I think).
What i've tried:
I tried to use useEffect and useState, but as I said, I think I need something in the first place to change it after. I also tried to use await, but await won't stop the whole React App until it has a response, although it would be nice if there is something that stops the app and waits until I have the array with the info so I can show it on the App then. I tried everything with async. I'm fairly new to React so there might be something basic i'm mssing (?). I've been on this for days, I can't get this to work by any means.. Any help, youtube videos, documentation, examples, is help. Anything. How the hell do I render something that needs to wait for the server respond?
My code:
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
async function pin(){
const result = []
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
const res = await axios.get(url)
console.log(res.data.data);
if(res.data){
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
console.log(result);
}
return result;
}
class App extends React.Component{
render(){
return(
<div>
<Pin/>
<Mapa/>
</div>
)
}
}
export default App
I don't fully understand what you are trying to output but how you would usually handle this is with both the useState hook and the useEffect hook see example below.
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
const pin = () => {
const [result, setResults] = useState([]);
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
useEffect(() => {
//Attempt to retreive data
try {
const res = transformData();
if (res) {
// Add any data transformation
setResults(transformData(res))
}
else {
throw (error)
}
}
catch (error) {
//Handle error
}
}, [])
// Handle data transformation
const transformData = async () => {
const res = await axios.get(url)
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
return result
}
if (!result) {
// Return something until the data is loaded (usually a loader)
return null
}
// Return whatever you would like to return after response succeeded
return <></>;
}
This is all assuming that Pin is a component like you have shown in your code, alternatively, the call can be moved up to the parent component and you can add an inline check like below to render the pin and pass some data to it.
{result && <Pin property={someData} />}
Just a bit of background the useEffect hook has an empty dependency array shown at the end "[]" this means it will only run once, then once the data has updated the state this will cause a rerender and the change should be visible in your component
Rest assured, useEffect() will work. You need to use a condition to conditionally render the content when it comes back from the server.
In the example below if results has a length < 1 the message Loading ... will be rendered in the containing <div>, once you're results are received the state will be updated (triggering a re-render) and the condition in the template will be evaluated again. This time though results will have a length > 1 so results will be rendered instead of Loading ...
I’m operating under the assumption that you’re function pin() is returning the results array.
const app = (props) => {
const [results, setResult] = useState([]);
React.useEffect(() => {
const getPin = async () => {
if (!results) {
const results = await pin();
setResult([…results])
}
}
getPin();
},[results]);
return (
<div>
{result.length ? result : 'Loading ... '}
</div>
)
}
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).
I'm trying to implement a custom hook to provide the app with a guest shopping cart. My hook wraps around the useMutation hook from Apollo and it saves the shopping cart id in a cookie while also providing a function to "reset" the cart (basically, to remove the cookie when the order is placed).
Code time! (some code omitted for brevity):
export const useGuestCart = () => {
let cartId;
const [createCart, { data, error, loading }] = useMutation(MUTATION_CREATE_CART);
console.log(`Hook!`);
if (!cartId || cartId.length === 0) {
createCart();
}
if (loading) {
console.log(`Still loading`);
}
if (data) {
console.log(`Got cart id ${data.createEmptyCart}`);
cartId = data.createEmptyCart;
}
const resetGuestCart = useCallback(() => {
// function body here
});
return [cartId, resetGuestCart];
};
In my component I just get the id of the cart using let [cartId, resetCart] = useGuestCart(); .
When I run my unit test (using the Apollo to provide a mock mutation) I see the hooked invoked several times, with an output that looks something like this:
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:59
Still loading
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:62
Got cart id guest123
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
I'm only getting started with hooks, so I'm still having trouble grasping the way they work. Why so many invocations of the hook?
Thank you for your replies!
Think of hooks as having that same code directly in the component. This means that every time the component renders the hook will run.
For example you define:
let cartId;
// ...
if (!cartId || cartId.length === 0) {
createCart();
}
The content inside the statement will run on every render as cartId is created every time and it doesn't have any value assigned at that point. Instead of using if statements use useEffect:
export const useGuestCart = () => {
const [cartId, setCartId] = useState(0);
const [createCart, { data, error, loading }] = useMutation(
MUTATION_CREATE_CART
);
const resetGuestCart = () => {
// function body here
};
useEffect(() => {
if(!cartId || cartId.length === 0){
createCart();
}
}, [cartId]);
useEffect(() => {
// Here we need to consider the first render.
if (loading) {
console.log(`Started loading`);
} else {
console.log(`Finished loading`);
}
}, [loading]);
useEffect(() => {
// Here we need to consider the first render.
console.log(`Got cart id ${data.createEmptyCart}`);
setCartId(data.createEmptyCart);
}, [data]);
return [cartId, resetGuestCart];
};
Also notice that there is no actual benefit from using useCallback if the component which is receiving the function is not memoized.