setState hook not updating after fetch with HTTP PUT - javascript

So I'm trying out basic todo app with edit and delete feature. I'm having problems with my edit feature. I have two main components in my app namely InputTodo for adding todo items and ListTodo which contains two additional subcomponents (TodoItem for each todo and EditTodo which shows an editor for a selected todo). Whenever the Edit Button inside a certain TodoItem is clicked, the EditTodo component is showed. When the Confirm button in EditTodo component is clicked, a PUT request will be sent to update the database (PostgreSQL in this case) through Node. After successfully sending this send request, I would like to re-render the list of TodoItem components. I'm doing this by fetching the updated list of values from the database through a different GET request then calling setState given the response from the GET request. However, the GET request's response doesn't reflect the PUT request done earlier. Thus, the app still renders the un-updated list of todos from the database.
Here are some code snippets
const ListTodo = (props) => {
const [todos, setTodos] = useState([]);
const [editorOpen, setEditorOpen] = useState(false);
const [selectedId, setSelectedId] = useState();
const getTodos = async () => {
console.log('getTodos() called');
try {
const response = await fetch("http://localhost:5000/todos");
const jsonData = await response.json();
setTodos(jsonData);
console.log(todos);
} catch (err) {
console.error(err.message);
}
console.log('getTodos() finished');
};
const editTodo = async description_string => {
console.log('editTodo() called');
try {
const body = { description: description_string };
const response = await fetch(
`http://localhost:5000/todos/${selectedId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
}
);
console.log(response);
await getTodos();
props.handleListModified();
} catch (err) {
console.error(err.message);
}
console.log('editTodo() finised');
}
const handleItemButtonClick = (button, row_key) => {
if (button === 'delete') {
deleteTodo(row_key);
setEditorOpen(false);
} else if (button === 'edit') {
setEditorOpen(true);
setSelectedId(row_key);
console.log(todos.filter(todo => { return todo.todo_id === row_key})[0].description);
}
};
const handleEditorButtonClick = async (button, description_string) => {
if (button === 'cancel') {
setSelectedId(null);
} else if (button === 'confirm') {
await editTodo(description_string);
}
setEditorOpen(false);
};
useEffect(() => {
console.log('ListTodo useEffect() trigerred');
getTodos();
}, [props.listModified]);
return(
<Fragment>
<table>
<tbody>
{todos.map( todo => (
<TodoItem
key={todo.todo_id}
todo_id={todo.todo_id}
description={todo.description}
handleClick={handleItemButtonClick} />
))}
</tbody>
</table>
{ editorOpen &&
<EditTodo
handleEditorButtonClick={handleEditorButtonClick}
description={todos.filter(todo => { return todo.todo_id === selectedId})[0].description}
selectedId={selectedId} /> }
</Fragment>
);
};

I guess that the problem is - In editTodo function, you are calling getTodos() function. But, you are not updating the state with the response you get. See if this helps.
const response = await fetch(
`http://localhost:5000/todos/${selectedId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
}
);
console.log(response);
setTodo(await getTodos()); // Update the state with the values from fetch

Related

Unable to Map Array of MongoDB Documents Returned from Backend into React Components

I am trying to get my front-end to call to the back-end for all the "blog posts" that are stored in my MongoDB database. At the moment there is only one document for testing.
On the backend I have this api endpoint:
app.get("/api/blogs", async (req, res) => {
console.log("Getting blog items...");
try{
const blogs = await blogActions.getBlogItems();
res.status(200).json({blogs});
} catch (err) {
console.log(err)
}
});
This calls to a separate JS file with this function:
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri);
const connection = async () => {
try {
const database = client.db('personalwebsite');
const blogs = database.collection('blogs');
return blogs;
} catch (err) {
console.log(err);
}
}
const getBlogItems = async () => {
const conn = await connection();
try {
return await conn.find({}).toArray();
} catch (err) {
console.log(err);
}
};
Then in my React front-end I am trying to take the returned array and set it to an Array there in order to map over it and create a new BlogItem component for each blog returned from the database:
import { useState, useEffect } from "react";
import Navbar from "../components/Navbar.tsx";
import BlogItem from "../components/BlogItem.tsx";
import '../styles/Blog.css';
export default function Blog () {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
const [isAdmin, setIsAdmin] = useState<boolean>(false);
const [token, setToken] = useState<string>('');
const [blogs, setBlogs] = useState([]);
useEffect(() => {
setToken(localStorage.getItem('token'));
async function checkToken () {
const response = await fetch('/api/token', {
method: 'POST',
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (response.ok){
const jsonResponse = await response.json();
if (jsonResponse.delete){
localStorage.clear();
return false;
}
return true;
} else {
console.log("Failed to fetch status of the User Login Session.");
}
}
async function checkIfAdmin () {
const response = await fetch('/api/users/permissions', {
method: 'POST',
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if(response.ok) {
const jsonResponse = await response.json();
if (jsonResponse.role === 'admin') {
setIsAdmin(true);
} else {
setIsAdmin(false);
}
}
}
async function getBlogItems () {
try {
const response = await fetch('/api/blogs');
const data = await response.json();
console.log("Before setBlogs", data.blogs)
if(data.blogs.length > 0) {
setBlogs(data.blogs);
}
} catch (err) {
console.log(err);
}
}
if (token) {
checkToken().then(isValid => {
if (!isValid) return;
checkIfAdmin();
});
}
getBlogItems();
}, [])
console.log("After setBlogs", blogs);
return (
<div className="App">
<Navbar />
<main className="main-content">
<div className="blogs-container">
{blogs.length > 0 ? (
blogs.map((blog) => (
<BlogItem
key={blog._id}
title={blog.title}
shortDesc={blog.shortDesc}
imgSrc={blog.imgSrc}
pubDate={blog.pubDate}
/>
))
) : (
<div>Loading...</div>
)}
</div>
<div className="most-popular"></div>
</main>
</div>
);
}
I have tried quite a few different methods for trying to get this to work correctly. At first I thought it was just a problem with the data not being returned quickly enough but even after getting the code to wait for the data to be returned and getting the Array to set. I get an error that Objects are not valid as a React child.
This is meant to be an array of Objects so that I can access the properties of each object for the elements in the component but I cannot get it to work for the life of me. I spent awhile using ChatGPT to try and get some progress out of it but this seems to be a problem that requires human intervention instead.
So this is annoying but the issue wasn't with the rest of my code but actually that the BlogItem component had the props wrapped only in parentheses.
Here is the component:
export default function BlogItem ({title, shortDesc, imgSrc, pubDate}) {
return (
<div className="blog-item">
<img src={imgSrc} className="blog-image" />
<h1 className="blog-title">{title === "" ? "Placeholder Title" : title }</h1>
<p className="blog-short-desc">{shortDesc}</p>
<p className="blog-date">{pubDate}</p>
</div>
);
}
The fix was that title, shortDesc, imgSrc, pubDate. All needed to be wrapped in Curly Braces. It now works :\

React useSelector Value Not Changed Inside Async Function

I want to use the useSelector state to send the new bugs state to a server inside an async function, but the value does not change. What am I doing wrong?
When submitting a form I want to update the bugs state and send it to the server
Inside an async function I do dispatch(updateBugs(newBug)) and the state is updated so this works
I get the state with const { bugs } = useSelector((state) => state.bugs); and the state seems to be updated
When I send the updated bugs list with await sendUpdatedBugsToServer(bugs); inside the async function, the bugs state seems to be the old one.
■bug-actions.js
export const sendUpdatedBugsToServer = (newBugsList) => {
const storeData = async (newBugsList) => {
const response = await fetch(`${databaseURL}/bugs.json`, {
method: 'PUT',
body: JSON.stringify(newBugsList),
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error('cannot store new bug');
}
};
try {
storeData(newBugsList);
} catch (error) {
console.error(error.message);
}
};
■UpdateBugs.js
const EditBug = () => {
const dispatch = useDispatch();
const { bugs } = useSelector((state) => state.bugs); //new bug state reflected here
const submitUpdatedBugs = async () => {
const newBug = {
title: enteredTitle,
details: enteredDetails,
steps: enteredSteps,
version: enteredVersion,
priority: enteredPriority,
assigned: enteredAssigned,
creator: enteredCreator,
id: enteredId,
};
await dispatch(updateBugs(newBug)); //update bugs state
await sendUpdatedBugsToServer(bugs); //new bug state not reflected here
}
};
const submitUpdatedBugHandler = (e) => {
e.preventDefault();
submitUpdatedBugs();
};

How to pre-fetch data using prefetchQuery with React-Query

I am trying to pre-fetch data using react-query prefetchQuery. When I am inspecting browser DevTools network tab I can see that data that was requested for prefetchQuery is coming from the back-end but for some reason when I look into react-query DevTools it does generate the key in the cache but for some reason the Data is not there. Let me know what I am doing wrong.
import { useState, useEffect } from 'react';
import { useQuery, useQueryClient } from 'react-query';
import axios from 'axios';
const baseURL = process.env.api;
async function getSubCategoryListByCategoryId(id) {
// await new Promise((resolve) => setTimeout(resolve, 300));
console.log(`${baseURL}/category/subcategories/${id}`);
try {
const { data } = await axios.request({
baseURL,
url: `/category/subcategories/${id}`,
method: 'get',
});
console.log('data getSubCategoryListByCategoryId index: ', data);
return data;
} catch (error) {
console.log('getSubCategoryListByCategoryId error:', error);
}
}
// const initialState = {
// };
const ProductCreate = () => {
const [values, setValues] = useState(initialState);
const queryClient = useQueryClient();
const { data, isLoading, isError, error, isFetching } = useQuery(
'categoryList',
getPosts
);
const dataList = JSON.parse(data);
useEffect(() => {
setValues({ ...values, categories: dataList });
dataList.map((item) => {
console.log('useEffect values.categories item.id: ', item._id);
queryClient.prefetchQuery(
['subCategoryListByCategoryId', item._id],
getSubCategoryListByCategoryId(item._id)
);
});
}, []);
return <h1>Hello</h1>;
};
export default ProductCreate;
The second parameter to prefetchQuery expects a function that will fetch the data, similar to the queryFn passed to useQuery.
But here, you are invoking the function, thus passing the result of it into prefetchQuery:
getSubCategoryListByCategoryId(item._id)
if you want to do that, you can manually prime the query via queryClient.setQueryData, which accepts a key and the data for that key passed to it.
otherwise, the fix is probably just:
() => getSubCategoryListByCategoryId(item._id)

how can i pass data by using react navigation in react-native?

I have a NotifiCard component and a ReplyComment component
When I run the LookforReply function, the GETONECOMMENT_REQUEST and LOAD_POST_REQUEST dispatches are executed, and the data comes into onecomment.
which is
const {onecomment} = useSelector((state) => state.post);
And I also want to pass the {item:one comment} data in the ReplyComment using navigation.navigate.
However, when we run our code, the data does not come into ReplyComment immediately, so it causing an error.
this is my code
(NotifiCard.js)
const NotifiCard = ({item}) => {
const dispatch = useDispatch();
const navigation = useNavigation();
const {onecomment} = useSelector((state) => state.post);
const LookforReply = useCallback(() => {
dispatch({
type:GETONECOMMENT_REQUEST,
data:item?.CommentId,
}),
dispatch({
type: LOAD_POST_REQUEST,
data:item.PostId,
}),
navigation.navigate('ReplyComment',{itemm:onecomment})
},[]);
return (
<LookContainer onPress={LookforReply}>
<Label>대댓보기</Label>
</LookContainer>
);
};
when dispatch GETONECOMMENT_REQUEST, getonecommentAPI this api run and get data from backend router
(postsaga.js)
function getonecommentAPI(data) {
return axios.get(`/post/${data}/getonecomment`);
}
function* getonecomment(action) {
try {
const result = yield call(getonecommentAPI, action.data);
yield put({
type: GETONECOMMENT_SUCCESS,
data: result.data,
});
} catch (err) {
console.error(err);
yield put({
type: GETONECOMMENT_FAILURE,
error: err.response.data,
});
}
}
which is this backend router
(backend/post.js)
router.get('/:onecommentId/getonecomment', async (req, res, next) => {
try {
// console.log("req.params.onecomment:",req.params.onecommentId);
const onecomment = await Comment.findOne({
where:{id: req.params.onecommentId},
include: [{
model: User,
attributes: ['id', 'nickname'],
}],
})
// console.log("onecomment:",JSON.stringify(onecomment));
res.status(200).json(onecomment);
} catch (error) {
console.error(error);
next(error);
}
});
if i get result data this will put draft.onecomment
(reducer/post.js)
case GETONECOMMENT_REQUEST:
draft.loadPostLoading = true;
draft.loadPostDone = false;
draft.loadPostError = null;
break;
case GETONECOMMENT_SUCCESS:
// console.log("action.data:::",action.data);
draft.loadPostLoading = false;
draft.onecomment = action.data;
draft.loadPostDone = true;
break;
case GETONECOMMENT_FAILURE:
draft.loadPostLoading = false;
draft.loadPostError = action.error;
break;
and i can get data in onecomment by using useselector at (NotifiCard.js)
const {onecomment} = useSelector((state) => state.post);
what i want is that when i press LookforReply i want to pass itemm data to
ReplyComment component but if i press LookforReply, i can't get itemm data
Immediately
(ReplyComment.js)
const ReplyComment = ({route}) => {
const {itemm} = route.params;
console.log("itemm",itemm);
return (
<Container>
<TodoListView parenteitem={itemm} />
<AddTodo item={itemm} />
</Container>
);
};
I think that the LookforReply function is executed asynchronously and navigate is executed before the onecomment data comes in, and it doesn't seem to be able to deliver the itemm.
so how can i fix my code?....

How to update the FlatList dynamically in react native?

Initially loading data from API to FlatList using setState and it loaded perfectly. But I have to perform some actions like create, update & delete of FlatList row. When I try to add new data to the FlatList, the data is not rendered in FlatList with an updated one, but In API it's updated.
How to re-render the flatlist after updating to the API and load the new data to FLatList?
Here is my code:
constructor(props) {
super(props);
this.state = {
faqs: [],
}
this.loadFaq();
};
To load the data to FlatList from the API:
loadFaq = async () => {
let resp = await this.props.getFaqGroup();
if (resp.faqs) {
console.log(resp.faqs)
this.setState({
faqs: resp.faqs,
// refresh: !this.state.refresh
})
}
};
To add new data to API:
createFaqGroup = async (name) => {
let resp = await this.props.createFaqGroup(name);
// console.log("resp", resp)
// this.setState({
// refresh: !this.state.refresh
// })
// this.forceUpdate();
this.closePanel();
}
FlatList code:
{this.state.faqs && <FlatList
extraData={this.state.faqs}
horizontal={false}
data={this.state.faqs}
contentContainerStyle={{ paddingBottom: 75 }}
renderItem={({ item: faqs }) => {
return <Card gotoQuestionList={this.gotoQuestionList} key={faqs._id} faqs={faqs} openPanel={(selectedFaq) => this.openPanel({ name: selectedFaq.name, id: selectedFaq._id })} deletePanel={(selectedFaq) => this.deletePanel({ name: selectedFaq.name, id: selectedFaq._id, isPublished: selectedFaq.isPublished })}></Card>
}
}
keyExtractor={(item) => item._id}
/>}
this.props.createFaqGroup function code:
export const createFaqGroup = (name) => {
const options = {
method: 'POST',
data: { "name": name },
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${store.getState().auth.info.token}`
}
};
return async (dispatch) => {
console.log('url::', options)
try {
let url = `${config.baseUrl}${config.faqUrl}`;
let resp = await axios(url, options);
console.log(resp.data)
return resp && resp.data ? resp.data : null;
} catch (error) {
alert(error)
if (error.response && error.response.status === 401) {
dispatch({
type: type.ERROR,
data: error.response.data
});
} else {
dispatch({
type: type.CREATE_FAQ_GROUP_ERROR,
error: error.message
});
}
}
};
}
Any help much appreciated pls...
Flatlist will update automatically when you set your state i.e by using this.setState() function, it means whenever any changes made to your state variable it will rerender your flatlist. if you still face the same problem remove your this.state.faqs && part, this looks unnecessary because there is no need to check if you are passing the empty array to faltlist or not, flatlist allows you to pas empty array as well, it will not give you any error.
I think you should load data again, after you add them, so you can modify your function createFaqGroup like this:
createFaqGroup = async (name) => {
let resp = await this.props.createFaqGroup(name);
this.loadFaq();
this.closePanel();
}
Try this:
createFaqGroup = async (name) => {
let resp = await this.props.createFaqGroup(name);
this.setState({faqs: [...this.state.faqs, name]})
this.closePanel();
}

Categories

Resources