React setState to push all items in array to state - reactjs

so I am trying to load all chat messages on page load. The messages are being retrieved just as I want them but I am having a horrible time trying to push all of them to state, I tried using loops etc but nothing seemed to work. This is the code:
const [name, setName] = useState("")
const [room, setRoom] = useState("")
const [messages, setMessages] = useState([])
const { room, name } = queryString.parse(location.search)
loadMessages(room).then((response) => {
if (response.error) {
console.log(response.error)
} else {
console.log(response)
setMessages([
...messages,
{ text: response.message, user: response.sender },
])
}
})
I just want to push every object in the response array into the messages state. Help greatly appreciated!

You need to use the useEfect hook for data fetching.
The Effect Hook lets you perform side effects in function components
Data fetching, setting up a subscription, and manually changing the
DOM in React components are all examples of side effects. Whether or
not you’re used to calling these operations “side effects” (or just
“effects”), you’ve likely performed them in your components before.
https://reactjs.org/docs/hooks-effect.html
From your snippet, it would look something like this:
useEffect(() => {
const messages = async () =>
await loadMessages(room)
.then(response => {
if(response.error) {
console.log(response.error)
} else {
console.log(response)
setMessages([...messages,
{text: response.message,
user: response.sender}
]);
}
});
messages();
}, []);

if the response is an array of messages, then you should map it and then set the state to it, like:
setMessages(response.map(item => ({
text: item.message,
user: item.sender
})
));
Ps: also as #George answered, also you should put the setMessages on a useEffect(() => {}, []) to prevent it to get datas on every update.

Related

Using a POST request's response values in React Native

everyone. I'm a new developer and this is my first post here, so please bear with me.
What I'm trying to do is access the values that I received from a POST request that I made.
Below you can see what I'm trying to do. I set up a state where I'll store my data and then there's a function called fetchData that does a POST request and receives an object with the values, right?
Now I that I've received my object, I want to store its values in some of my variables.
Since the values have been stored in the 'data' state, I thought I would access its values by doing something like 'userName = data.userName', or something like 'age = data.userAge'. Obviously that didn't work out because my IDE does not know those values yet lol. So how do you think I should access them?
const [data, setData] = useState([{}]);
useEffect(() => {
fetchData({
ids: [0],
})
.then((response: any) => {
setData(response);
})
.catch((error: any) => {
console.log('error', error);
});
}, []);
dont place the function directly in useEffect, do something like this instead
const [data, setData] = useState([])
const getData = async() => {
try{
const response = await fetchData({ids: [0]});
setData(response);
}catch(error){
console.log(error);
}
}
useEffect(() => {
getData();
},[])

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);
}

React custom fetch hook is one step behind

I am creating my custom fetch hook for both get and post data. I was following official React docs for creating custom fetch hooks, but it looks like my hook-returned state variables are one step behind behind due to useState asynchronous behaviour. Here is my custom useMutation hook
export const useMutationAwait = (url, options) => {
const [body, setBody] = React.useState({});
const [data, setData] = React.useState(null);
const [error, setError] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
const fetchData = async () => {
setError(null);
setIsLoading(true);
console.log("...fetching");
try {
const response = await axiosInstance.post(url, body, options);
setData(response.status);
} catch (error) {
console.error(error.response.data);
setError(error.response.data);
}
setIsLoading(false);
};
fetchData();
}, [body]);
return [{ data, isLoading, error }, setBody];
};
And I am using it in my component like this (simplified) - when user presses register button I want to be able immediately decide if my post was successful or not and according to that either navigate user to another screen or display fetch error.
const [email, setEmail] = React.useState('');
const [password, setPassword] React.useState('');
const [{ data: mutData, error: mutError }, sendPost] =
useMutationAwait("/auth/pre-signup");
const registerUser = async () => {
sendPost({
email,
password,
}); ---> here I want to evaluate the result but when I log data and error, the results come after second log (at first they are both null as initialised in hook)
Is this even correct approach that I am trying to achieve? Basically I want to create some generic function for data fetching and for data mutating and I thought hooks could be the way.
Your approach isn't wrong, but the code you're sharing seams to be incomplete or maybe outdated? Calling sendPost just update some state inside your custom hook but assuming calling it will return a promise (your POST request) you should simply use async-await and wrap it with a try-catch statement.
export const useMutationAwait = (url, options) => {
const sendPost = async (body) => {
// submit logic here & return request promise
}
}
const registerUser = async () => {
try {
const result = await sendPost({ login, password });
// do something on success
} catch (err) {
// error handling
}
}
Some recommendations, since you're implementing your custom hook, you could implement one that only fetch fetch data and another that only submit requests (POST). Doing this you have more liberty since some pages will only have GET while others will have POST or PUT. Basically when implementing a hook try making it very specific to one solution.
You're absolutely correct for mentioning the asynchronous nature of state updates, as that is the root of the problem you're facing.
What is happening is as follows:
You are updating the state by using sendPost inside of a function.
React state updates are function scoped. This means that React runs all setState() calls it finds in a particular function only after the function is finished running.
A quote from this question:
React batches state updates that occur in event handlers and lifecycle methods. Thus, if you update state multiple times in a handler, React will wait for event handling to finish before re-rendering.
So setBody() in your example is running after you try to handle the response, which is why it is one step behind.
Solution
In the hook, I would create handlers which have access to the data and error variables. They take a callback (like useEffect does) and calls it with the variable only if it is fresh. Once it is done handling, it sets it back to null.
export const useMutationAwait = (url, options) => {
const [body, setBody] = React.useState({});
const [data, setData] = React.useState(null);
const [error, setError] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
const handleData = (callback) => {
if (data){
callback(data);
setData(null);
}
}
const handleError = (callback) => {
if (error){
callback(error);
setError(null);
}
}
React.useEffect(() => {
const fetchData = async () => {
setError(null);
setIsLoading(true);
console.log("...fetching");
try {
const response = await axiosInstance.post(url, body, options);
setData(response.status);
} catch (error) {
console.error(error.response.data);
setError(error.response.data);
}
setIsLoading(false);
};
fetchData();
}, [body]);
return [{ data, handleData, isLoading, error, handleError }, setBody];
};
We now register the handlers when the component is rendered, and everytime data or error changes:
const [
{
data: mutData,
handleData: handleMutData,
error: mutError,
handleError: handleMutError
}, sendPost] = useMutationAwait("/auth/pre-signup");
handleMutData((data) => {
// If you get here, data is fresh.
});
handleMutError((error) => {
// If you get here, error is fresh.
});
const registerUser = async () => {
sendPost({
email,
password,
});
Just as before, every time the data changes the component which called the hook will also update. But now, every time it updates it calls the handleData or handleError function which in turn runs our custom handler with the new fresh data.
I hope this helped, let me know if you're still having issues.

useEffect not triggering but the template is being rendered somehow

I am getting too many re-renders while using react-hooks.
I am trying to fetch data from api by using a parameter in URL.
Here's the code:
export default function Details() {
const { title } = useParams();
const [loading, setLoading] = useState(true);
const [details, setDetails] = useState([]);
const [error, setError] = useState("");
function getDetails(keyword) {
if (keyword) {
setLoading(true);
fetch(
`API`
)
.then((res) => {
let result = res.data.results;
result = result.filter(function (result) {
return (result.title = keyword);
});
setDetails(result[0]);
setLoading(false);
console.log(details);
})
.catch((err) => {
setError(["Unable to fetch data"]);
setLoading(false);
});
}
}
getDetails(title)
return(
// template
)
now I think this is happening at the line where I call getDetails.
then I tried using useEffect to load the data only once after it is mounted,
useEffect(() => {
getDetails(title);
}, []);
It still is unable to fetch the data, as the getDetails function is never called.
How can I resolve this?
Edit:
Fixed one silly error.
Here's the codesandbox link:
Codesandbox
There are multiple issues with this, first you need to specify what you want to be notified about when the useEffect gets called again. You could do this by adding the variables you want within the array
useEffect(() => {
getDetails(title);
}, [
// Add what you want here
]);
The second issue you have is that you declared the detalis variable twice. Once using the set state here: const [details, setDetails] = useState([]);
The second time here:
const details = getDetails(title)
the code here has two obvious error beside the functionality problems you mentioned:
1 - you cannot declare two variables with same name using let or const; it will throw a SyntaxError
const [details, setDetails] = useState([]);
...
const details = getDetails(title)
2- getDetails function is written with a asynchronous mindset, and it will return nothing,
so details in const details = getDetails(title) will be set to undefined
Looks like your getDetails function has title param so I would add title and getDetails both in the dependency list of useEffects
Or place getDetails inside the useEffect
Here is your working code. You had multiple problems where res.data was undefined so you need to get res.results directly based on your response object
useEffect(() => {
function getDetails(keyword) {
if (keyword) {
setLoading(true);
fetch(
`https://api.jikan.moe/v3/search/anime?q=${keyword}&page=1&genre_exclude=0`
)
.then((res) => res.json())
.then((res) => {
console.log(res.results);
let result = res.results;
console.log(result);
result = result.filter(function (result) {
return (result.title = keyword);
});
setDetails(result[0]);
setLoading(false);
console.log(3);
})
.catch((err) => {
console.log(err);
setError(["Unable to fetch data"]);
setLoading(false);
});
}
}
console.log('calling getDetails')
getDetails(title);
}, [title]);
Note: tested in the code sandbox link provided in the question. Its working code.

Set state function is not properly updating the state

const component = () => {
const [data, setData] = useState(null);
const fetchData = async () => {
try {
new DataService().getData().then((response) => {
setData(response);
console.log(data);
console.log(response);
}
} catch (error) {
console.log(error);
}
}
useEffect(() => {
fetchData();
}, []);
}
Why does console.log(data) display null even though console.log(response) displays the correct data? The data state should've been set before I console.log'd it, no?
The data state should've been set before I console.log'd it, no?
No.
This is most likely because setData is asynchronous. Since you're immediately asking for the value of data after using setData it's likely that the value hasn't had the chance to update yet.
If you're a Chrome user, have you used React Developer Tools? This will allow you to check the state of a component without needing to rely on console.log.

Resources