I have a basic component which subscribes to an EventEmitters event. All it is meant to do, is accumulate an array of messages.
What is happening however, is the message array only ends up containing the latest message.
Here's the component source:
export const MessageList = ({serverId, connection}) =>
{
var [messages, setMessages] = useState([]);
const appendSystem = (message) =>
{
console.log("Appending " + message);
console.log(messages);
setMessages([...messages, message]);
}
useEffect(() =>
{
connection.on('system', appendSystem);
return () => connection.removeListener('system', appendSystem);
},
[serverId]);
console.log("Rendering");
console.log(messages);
}
The output I'm getting however...
Rendering
[]
Appending Permission granted. Attempting connection.
[]
Rendering
["Permission granted. Attempting connection."]
Appending Connection Succeeded
[]
Rendering
["Connection Succeeded"]
So it seems each time appendSystem is called, messages is an empty array. Therefore, setMessages is always appending the new message to an empty array.
Does anyone know what could be causing this?
I'm under the impression appendSystem is for some reason caching 'messages' at the start, and reusing the original value, but have no idea how I would go about debugging or fixing that.
This is a common issue with React hooks' non-intuitive state setting mechanism.
Try using setMessages with a function as its argument, rather than with the new value. This will guarantee you use its most recent value:
setMessages(prevMessages => [...prevMessages, message]);
Related
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
I'm trying to GET queries from my API with all key=fld_id/value=true pairs in local storage "Selected Fields".
This is how "Selected Fields" appears in the localStorage (they are stored form checkbox form submission):
https://i.imgur.com/nbmC80Z.png
It keeps running an infinite loop in the backend that eventually crashes my browser and inserts wrong fld_id (which is the key in the localStorage): https://i.imgur.com/tzueONR.png
const [monitoringData, setMonitoringData] = React.useState(null);
const selected = localStorage.getItem("Selected Fields");
Object.keys(selected).forEach((fld_id) => {
axios
.get("/api/parsed-logs-list/?fld_id=" + fld_id)
.then((response) => {
setMonitoringData([...monitoringData, fld_id.response.data]);
})
.catch((error) => {
setMonitoringData({ status: "error" });
});
});
Can someone help me resolve this issue please?
Edit: Solution provided in a later comment below to avoid confusions.
Firstly, you must wrap your side effect in useEffect hook (otherwise after each rerender you make request and update state, that cause to infinite loop). Secondly, type of value you receive from localStorage is string not object, you must parse with JSON.parse(selected) before calling Object.keys
With the help of #Mike and #AlaaEddineCherif, it now works as intended so I'm posting the solution in a different post in case it will be of help to someone else one day.
React.useEffect(() => {
const selected = JSON.parse(localStorage.getItem("selected_fld_id"));
Object.keys(selected).forEach((fld_id) => {
axios
.get("/api/parsed-logs-list/?fld_id=" + fld_id)
.then((response) => {
setMonitoringData((prev) => [...prev, response.data]);
// console.log(response.data);
})
.catch((error) => {
setMonitoringData({ status: "error" });
});
});
}, []);
I am new to firebase and using ReactJs, I am wondering how can I update a specific data in Realtime database, I want to change the value of it to true or false interchangeably.
This is my code.
const [status, setStatus] = useState(false);
const [tempId, setTempId] = useState("");
const [todo, setTodo] = useState("");
const updateStatus = () => {
update(ref(db, `/${auth.currentUser.uid}/${tempId}`), {
completed: setStatus(!status),
todo: todo,
id: tempId,
});
};
Thank you in Advance.
If you only want to update the status property, you can do:
update(ref(db, `/${auth.currentUser.uid}/${tempId}`), {
completed: !status
});
Since your calling update, it will leave the other properties of the node unmodified.
Alternatively, you can do:
update(ref(db, `/${auth.currentUser.uid}/${tempId}/status`), !status)
Or:
set(ref(db, `/${auth.currentUser.uid}/${tempId}/status`), !status)
Both of these write to the complete path of just the status property.
What you'll note is that I don't call setStatus in the code above. Instead I recommend listening to the value of the status node, and then calling setStatus when you get a callback there:
onValue(ref(db, `/${auth.currentUser.uid}/${tempId}/status`), (snapshot) => {
setStatus(snapshot.val());
});
Separating the read and write operations like this is known as command query responsibility segregation (or CQRS) and in the case of Firebase won't even be slower for the user, as the local SDK will immediately call the onValue handler when you call set or update on that path.
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).