In React, how do I copy a state property by value? - reactjs

I'm building a React 16.13 application. I want to copy a property from my state in order to manipulate it and leave the underlying state unchanged. I thought this was the way to do it ...
async handleFormSubmit(e) {
e.preventDefault();
const NC = [...this.state.newCoop]
delete NC.addresses[0].country;
try {
const response = await fetch(FormContainer.REACT_APP_PROXY + "/coops/", {
method: "POST",
body: JSON.stringify(this.state.newCoop),
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
However this line
const NC = [...this.state.newCoop]
gives the error
Unhandled Rejection (TypeError): this.state.newCoop is not iterable
What's the right way to copy a state variable by value?

I think it's just because you're spreading an object into an array; just do:
const NC = {...this.state.newCoop}
Edit:
Regarding the deep copy question, you can take a look at this answer:
https://stackoverflow.com/a/38417085
By the way, this has nothing to do with react :)

Related

What needs to be done in the useMutation hook in order for the returned result to have 'symbol.iterator'?

I am going tgrough the repo that Tanner used when illustrating react query functionality and its benefits. Basically I am doing it step by step in line with the order of his commits in this repo. However when I reached the stage of migrating to using react-query from using useEffect and regular fetching mechanisms I got stuck with the following error
Type 'UseMutationResult<void, unknown, void, unknown>' must have a
'Symbol.iterator' method that returns an iterator.
This error occurs when I am trying to use a custom hook useCreateTransaction. Implementation of this custom hook looks like following:
import { useMutation, useQueryClient } from "react-query";
export default function useCreateTransaction() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (values) => {
const res = await fetch("/api/transactions", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(values),
});
// const data = await res.json();
// return data;
},
onSuccess: (data) => {
queryClient.invalidateQueries("transactions");
// return [createTransaction]
},
});
}
What I tried to do: to assign returned values of this hook by way of destructuring like so
const [createTransaction, { status: createTransactionStatus }] =
useCreateTransaction();
I was expecting to use createTransaction to mutate the state on server but instead am seeing this error
Type 'UseMutationResult<void, unknown, void, unknown>' must have a
'Symbol.iterator' method that returns an iterator.
I am not sure if this is helpful: I am using astrobuild for the client and planetscale as the db and cloudflare functions as the backend. Here is the link to the repo
const [createTransaction, { status: createTransactionStatus }] =
useCreateTransaction();
this is the v2 api. The video is almost 2.5 years old. Concepts are still relevant, but apis have changed. Latest version is v4, where it is:
const { mutate: createTransaction, status } =
useCreateTransaction();
You're getting that error because you're trying to destruct something as an array that isn't an array.

New component not rendering correctly

const [data, setData] = useState([]);
const addOne = (newObj) => {
setData((prev) => {
return [newObj, ...prev];
});
};
The above is called and adds a new object to my array when I have finished making calls to my backend.
I get the data from there like so:-
const [posts, refreshPosts, loadingPosts, reachedEnd, resetPosts, addOne] =
usePaginate(
`post/wall/${id}/${toSkip}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
},
setToSkip
);
Then pass the above into the feed component:-
const posts = props.posts.map((item) => {
return <Post data={item} />});
Where I create my post objects from the data, then inside post I create a new post and try to add the returned value to my array through the addOne method that I posted above:-
const handleClick = async () => {
const url = "post";
const options = {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
method: "POST",
body: JSON.stringify({ content: value, location: id }),
};
const post = await fetchData(url, options);
reset();
props.addOne(post)};
But it doesn't seem to trigger a re-render. Inside the post component I make calls that depend on the ID of the new object that was added, as an example:-
const [
commentData,
refreshComments,
loadingComments,
reachedEnd,
resetComments,
addOne,
] = usePaginate(
`post/comments/${props.data._id}/${toSkip}`,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
method: "GET",
},
setToSkip
);
I would expect with a new object, an entirely new post to be created, but it doesn't seem to do that. What seems to happen is that the first element on the page re-renders partially
<div className="postContent">
{profData && props.data.content} </div>
So the text content of the top element on the page changes to reflect the correct text content of the post I'm trying to add, but then if I add a new comment to the new post, then add another post on top of that, only the props.data.content part changes, and the "shell" of the post remains the same (i.e. the entire post doesn't shift down, so the comments remain on the top post of the page, even though they should be on the second post at that point because the top post is now displaying the content from the newest data item).
resetComments();
refreshComments();
refreshLikes();
I can manually fire reach dataloader individually and then everything seems to work as expected, but it feels like I shouldn't have to do it because they should detect that a new item has been added to my array and just create an entirely new post object from it? It doesn't seem to be any issue with my backend because once I refresh the page everything is as should be. I can add any more information required, just didn't want to bloat the post so tried to pick the functions that seemed relevant to the problem, but I know something else could cause a side effect. It has been bugging me for hours.
The git link is here if anyone could have a look a bit more closely: -
https://github.com/Legandjl/odinbook_client
-edit
So far thanks to the comment I have changed return [newObj, ...prev] to return [...prev, newObj] and it seems to work okay when the new post is being rendered last, but then if I try to prepend or unshift the new object, like const newArr = [...prev], newArr.unshift(newObj) I either get a blackscreen or the same issue.
solved with the help of this post:-
problem when adding new item to start of array in react
const posts = props.posts.map((item, i) => {
return <Post key={item._id} data={item} />;
});
posts needed a unique key, tried with index and it didn't work but then used the _id and now all is as expected. Thanks all who tried to help me.

React: Can't figure out how to use fetched API response

I'm working with a nice chatbot program for React someone wrote, and the thing is, you can actually bind the bot's responses to function calls like this:
render() {
const { classes } = this.props;
return (
<ChatBot
steps={[
{
...
{
id: '3',
message: ({ previousValue, steps }) => {
this.askAnswer(previousValue)
},
end: true,
},
]}
/>
);
Where message is the answer of the bot that it calculates based on the previousValue and askAnswer is a custom function you'd write. I'm using an API that inputs the previousValue to a GPT model, and I want to print the response of this API.
However, I just can't wrap my head around how I could pass the response of the API to the message. I'm trying this:
constructor(props) {
super(props);
this.state = { response: " " };
}
...
askAnswer(question) {
var jsonData = { "lastConversations": [question] }
fetch('http://my_ip:80/query', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(jsonData)
}).then(response => response.json())
.then(data => { this.setState({ response: data["botResponse"] }) });
return (this.state.response)
}
I've been struggling with this for the past 2-3 hours now.
I've tried a couple of combinations, and nothing seems to work:
If I do as seen above, it seems like this.state.response just won't get
updated (logging data["botResponse"] shows there is a correct
reply, so the API part works and I get the correct response).
If I try async-await for askAnswer and the fetch call, then I can
only return a Promise, which is then incompatible as input for the
ChatBot message.
If I do not await for the fetch to complete, then
this.state.response just stays the default " ".
If I return the correct data["botResponse"] from within the second .then after the fetch, nothing happens.
How am I supposed to get the API result JSON's data["botResponse"] text field out of the fetch scope so that I can pass it to message as a text? Or, how can I get a non-Promise string return after an await (AFAIK this is not possible)?
Thank you!
In your last 2 line
.then(data => { this.setState({ response: data["botResponse"] }) });
return (this.state.response)
You are updating the state and trying to read and return the value in the same function which I think will never work due to async behaviour of state. (this is why your this.state.response in the return statement is the previous value, not the updated state value).
I'm not sure but you can write a callback on this.setState and return from there, the callback will read the updated state value and you can return the new state.

Encountered "Error: Material-UI: capitalize(string) expects a string argument" when using this.setState in React

I'm working on an app and I'm trying to save the API response into my state but I keep getting an error at this point. Here's the error below:
Error: Material-UI: capitalize(string) expects a string argument.
The similar errors I found online is about Snackbars and I couldn't even get a clue to apply the fix to mine.
The tiring part of this is that, it was working until suddenly (without changing anything in that part of the code) it started giving me this error.
Here's my code below:
fetchData = async (data) => {
await axios.get("...", { headers: { "Content-Type": "application/json" }})
.then(res => {
this.setState({ userData: res.data.user }) //it throws error on this line
})
}
If I remove the setState function, it did work fine, just that I won't be able to pass my data to the template.
How do I get this fixed?
Can you try this:
fetchData = (data) => {
axios.get("...", { headers: { "Content-Type": "application/json" }})
.then(res => {
console.log("res: ", res.data)
res.data.user &&
this.setState({ userData: res.data.user })
})
}
what I'm trying to do here is to avoid hitting the setState if there is no res.data.user, if it exists it will work properly, and I don't actually think you need to use async-await here as you are using a promise passed solution

Why am I getting empty array in fetch request to MongoDB from ReactJS?

I am trying to fetch all the articles from a document in MongoDB in React. It is perfectly working in Backend with NodeJS when I tested with Postman. But in Frontend , React, I am getting empty array. How to solve this.
Server.js (Backend)
app.get('/api/articles', async (req, res)=>{
const client = await MongoClient.connect('mongodb://localhost:27017', {useNewUrlParser:true, useUnifiedTopology:true})
const db = client.db('my-blog')
const articleInfo= await db.collection('articles').find({}).toArray(function(err, result){
if (err) console.log(err)
res.status(200).send(result)
client.close()
})
})
articlePage.js (FrontEnd)
componentDidMount(){
const requestOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
};
const fetchdata = fetch('/api/articles/').then(res=>res.json())
.then(data=>this.setState({articles:data}))
console.log(this.state.articles)
}
Api address is set up in package.json with proxy:http://localhost:8000
How to get the documents data from MongoDB in React?
Firstly, check if you the API call went through to the server from your React app. If it has, then check for the response code in the network. In your case, 200 is where you get the desired result from the API. If you are not getting the desired result, then check your collection and document names and also arguments your are passing in the query.
As setState is not synchronized, you have to access it in the callback.
this.setState({ articles: data }, () => {
console.log(this.state.articles)
})

Resources