React: updating state after deletion - reactjs

I'm trying to update elements after deletion, without refreshing a page. Currently, if delete a record, need to refresh a page to see the result. As I understand, need to update useState, but I do not understand how to do it. If I loop useEffect it works but slowly, but I think it's not the best idea to loop get response.
Get all records from a database.
const PostsGetUtill = () => {
const [posts, setPosts] = useState([]);
const fetchPosts = () => {
axios.get("api/v1.0/post/get").then(response => {
console.log(response.data);
setPosts(response.data);
}).catch(function (error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
});
};
useEffect(() => {
fetchPosts();
}, []); // }, [fetchPosts]); <--- working well with loop
return (
<section className="container-post">
<PostMansonry posts={posts} columns={3} />
</section>
);
};
export default PostsGetUtill;
Sort and map records
export default function PostMansonry({ posts, columns }) {
return (
<section className="masonry" style={{ gridTemplateColumns: `repeat(${columns}, minmax(275px, 1fr))` }}>
{posts.sort((a, b) => a.zonedDateTime < b.zonedDateTime ? 1 : -1).map((posts, index) =>
<MasonryPost {...{ posts, index, key: index }} />)
}
</section>
)
}
Put data to the card
export default function MasonryPost({ posts, index }) {
return (
<div key={index} className="masonry-post">
<div className="card">
<div className="card-body">
<h5 className="card-title">{posts.title}</h5>
<p className="card-text">{posts.description}</p>
<p className="card-text"><small className="text-muted"> {posts.zonedDateTime}</small></p>
<div><button type="button" onClick={(e) => PostsDeleteUtill(posts.post_Id)} className="btn btn-danger">Delete</button></div>
</div>
</div>
</div>
)
}
Deleting
const PostsDeleteUtill = async (post_Id) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
console.log(response);
}).catch((error) => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log('error config', error.config);
});
};
export default PostsDeleteUtill;

Basically what you need to do is, in your PostsDeleteUtill function, in the promise return of your axios.delete, you need to update your posts state, which is set in PostsGetUtill.
In order to do that, you have 2 options:
Use a global state (React Context, Redux, etc)
Pass your setPosts handle all the way to your PostsDeleteUtill
I think option 1 is a bit cleaner for your specific case, but if you don't need global state anywhere else in your project, maybe it is fine to have a not so clean solution instead of implementing the whole global state structure for only one thing.
Option 1 pseudo code:
Your PostsGetUtill component would use a global state instead of local state:
const PostsGetUtill = () => {
// Remove this:
// const [posts, setPosts] = useState([]);
const fetchPosts = () => {
axios.get("api/v1.0/post/get").then(response => {
console.log(response.data);
// Instead of a local "setPosts" you would have a global
// "setPosts" (in Redux, this would be a dispatch)
dispatch({type: "PUT_POSTS", action: response.data})
}).catch(function (error) {
// No changes here...
});
};
// This runs only the first time you load this component
useEffect(() => {
fetchPosts();
}, []);
// Use your global state here as well:
return (
<section className="container-post">
<PostMansonry posts={globalState.posts} columns={3} />
</section>
);
};
export default PostsGetUtill;
In your PostsDeleteUtill function:
const PostsDeleteUtill = async (post_Id) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
// Update global state here. Probably filter the data to remove
// the deleted record
const updatedPosts = globalState.posts.filter(post => post.id !== response.data.id)
}).catch((error) => {
// No changes here
});
};
export default PostsDeleteUtill;
Option 2 pseudo code:
In your PostsGetUtill component, create and pass on a handleRemovePost:
// Your existing code ...
const handleRemovePost = (postID) => {
const filteredPosts = posts.filter(post => post.id !=== postID)
setPosts(filteredPosts)
}
return (
<section className="container-post">
<PostMansonry posts={posts} columns={3} handleRemovePost={handleRemovePost} />
</section>
);
In your PostMansonry, pass on again your handleRemovePost
export default function PostMansonry({ posts, columns, handleRemovePost }) {
return (
// Your existing code ...
<MasonryPost {...{ posts, index, key: index, handleRemovePost }} />)
)
}
Again in your MasonryPost
export default function MasonryPost({ posts, index, handleRemovePost }) {
return (
// Your existing code ...
<button type="button" onClick={(e) => PostsDeleteUtill(posts.post_Id, handleRemovePost)} className="btn btn-danger">Delete</button>
)
}
And finally:
const PostsDeleteUtill = async (post_Id, handleRemovePost) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
handleRemovePost(response);
})
};
PS: Please note that I only added a pseudo-code as a reference, trying to point out specific parts of the code that needs to be updated. If you need more information about global state, you can check React Context and Redux

Related

data.map is not a function react js

I'm new to react and trying to connect firestore for my project.
I followed the example from the Internet and everything works, the data is deleted and written to the database, also when I change the data they change in the database, but I get errors in the console and a white screen.
Uncaught TypeError: data.map is not a function
If you need any more files or code, I will correct my question, please write which ones I need to add
Also, when loading the page, I get the following error in the console:
Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist. at wrappedSendMessageCallback
Here is the code that throws the error:
export default function Saved({ data, setData }) {
function editData(id, newWord, newTranslate, newNote) {
const editedDataList = async (card) => {
if (id === card.id) {
return {
...card,
word: newWord,
translate: newTranslate,
note: newNote,
};
}
let newFields = {
word: newWord,
translate: newTranslate,
note: newNote,
}
await updateDoc(doc(db, "db-name", id), newFields);
console.log(newFields)
return card;
};
setData(editedDataList);
}
const deletePost = async (id) => {
await deleteDoc(doc(db, "db-name", id));
};
const dataList = data.map((card) => (
<SavedData
id={card.id}
key={card.id}
word={card.word}
translate={card.translate}
note={card.note}
editData={editData}
del={deletePost}
/>
));
return (
<div>
<div className="sec-menu"></div>
<div className="saved-inner">
{data.length >= 1 ? (
<div className="saved-list">{dataList}</div>
) : (
<Link className="main-btn" to="/addcard">
Add
</Link>
)}
</div>
</div>
);
}
Here Menu.js code:
function Menu() {
const [data, setData] = useState([]);
useEffect(() => {
const q = query(collection(db, "db-name"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
let wordsArr = [];
querySnapshot.forEach((doc) => {
wordsArr.push({ ...doc.data(), id: doc.id });
});
setData(wordsArr);
});
return () => unsubscribe();
}, []);
return (
<div className="content">
<AuthContextProvider>
<Routes>
<Route
path="saved"
element={<Saved data={data} setData={setData} />}
/>
</Route>
</Routes>
</AuthContextProvider>
</div>
);
}
export default Menu;
On second glance, the issue is where you call setData(editedDataList). You're passing in a function into this method which is in turn updating data to be a function instead of an array. Try changing, editData() to be something like this:
const editData = async (id, newWord, newTranslate, newNote) => {
const editedDataList = await Promise.all(data.map(async (card) => {
let newFields = {
word: newWord,
translate: newTranslate,
note: newNote,
};
if (id === card.id) {
return { ...card, ...newFields };
}
await updateDoc(doc(db, "db-name", id), newFields);
console.log(newFields);
return card;
}));
setData(editedDataList);
};
editedDataList will be an array of the modified cards in the original and setData() should work as expected.
maybe the error occurs because "data" object is not an array.
And check what are you setting on "setData(editedDataList);" instruction

Fetch fires every second "onChange"

Somehow my fetch only fires every second time, I select an item from list:
export default function App() {
...
useEffect(() => {
fetchBookData(books).then((payload) => setPayload(payload));
}, [books]);
const fetchBooksBySubject = () => {
const options = {
method: `GET`,
};
fetch(`${server}/books?subjects_like=${selectedValue}`, options)
.then((response) => {
if(response.ok){
return response.json().then(setBookList)
}
throw new Error('Api is not available')
})
.catch(error => {
console.error('Error fetching data: ', error)
setError(error)
})
}
const handleChange = e => {
setSelectedValue(e);
fetchBooksBySubject();
};
if (!payload) {
return <div>Please start the server</div>;
}
console.log(bookList)
return (
<div className="bg-blue-200">
<div className="container mx-auto py-36 bg-blue-200">
<div className="mt-12 px-96">
<SubjectSelector
options={payload}
selectedVal={selectedValue}
handleChange={handleChange}/>
<ShowcaseBooks selectedVal={selectedValue} />
<ul>
{bookList?.map((item) => {
return(
<li>{item.title}</li>
)
}
)}
</ul>
</div>
</div>
</div>
)
}
So fetchBooksBySubject delivers the bookList first as emty array, then logs correctly, howeverso I have to select two times, to see <li>{item.title}</li> updated. Why is this and how to fix it?
Your selectedValue isn't really updated yet (it will happen only at the next rerender) at the point when you call fetchBooksBySubject. It happens because setSelectedValue doesn't update the associated value immediately.
As a result, fetch(`${server}/books?subjects_like=${selectedValue}`, options) gets an old value.
You could fix it with i.e. useEffect hook:
useEffect(() => {
fetchBooksBySubject();
}, [selectedValue])
instead of calling fetchBooksBySubject immediately in the callback

data rendering issue after button is clicked in react

I am having a data rendering issue in react. Somehow, data is not automatically updated after it's updated in the server side. I can't put all the code in here, cuz the code is kind of lengthy. so i pasted/renamed some variables. Even if some variables are missing, please understand. Basically, I have a button on the page and when the button is clicked, the status changes to 'UPLOADING' and the function checkIfDataExists is called to fetch data from the server side and data should be automatically updated without page refresh, but when I test this, data is successfully retrieved from the server side, but the updated data is not rendered. I see 'successful...' on the Console. Is there anything wrong?
const Settings: React.FC<IProps> = props => {
const { orgId } = props
const password = 'dummy'
const { data } = httpCall(`/${orgId}/${userId}/settings`)
return (
<div>
{data && <SettingsForm data={data} password={password} {...props} />}
</div>
)
}
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
data.modeUsername = value.modeUsername
data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{data.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{data.modePassword}</p>
</div>
The problem I see is that after you setInterval an API you didn't set in the state to trigger the component to rerender. You don't need to be explicit to define resData to data because if you define data already useState already it types.
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [resdata,setResData] = useState(data)
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
setResData({
modeUsername: value.modeUsername,
modePassword: value.modePassword,
})
// data.modeUsername = value.modeUsername
// data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{resdata.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{resdata.modePassword}</p>
</div>

pass arrow function to component prop

I am trying to pass the result of the handleRedirectUrl() function to the ShortUrlField component as a prop.
I don't know what I am doing wrong, please help me
const handleRedirectUrl = () => {
urlService
.getShortenedUrl(urls.slice(-1)[0].short_url)
.then((returnedUrl) => {
setRedirectedUrl(returnedUrl);
})
.catch((error) => {
handleCreateErrors(error);
})
.finally(() => {
return redirectedUrl;
});
};
//display shortened url
const shortUrlDisplay = renderShortUrl ? (
<ShortUrlField
originalUrlValue={urls.slice(-1)[0].original_url}
shortUrlValue={urls.slice(-1)[0].short_url}
redirectedUrlValue={handleRedirectUrl()}
/>
) : (
<EmptyField />
);
The urlService function
const getShortenedUrl = (urlToGet) => {
const request = axios.get(redirectShortenedUrl + `${urlToGet}`);
return request.then((response) => response.data);
};
Edit 1:
I was not returning anything with my handleRedirectUrl function. Also, I was not passing it properly to the props. I have changed my code to
const handleRedirectUrl = () => {
return urlService
.getShortenedUrl(urls.slice(-1)[0].short_url)
.then((returnedUrl) => {
setRedirectedUrl(returnedUrl);
})
.catch((error) => {
handleCreateErrors(error);
})
.finally(() => {
return redirectedUrl;
});
};
//display shortened url
const shortUrlDisplay = renderShortUrl ? (
<ShortUrlField
originalUrlValue={urls.slice(-1)[0].original_url}
shortUrlValue={urls.slice(-1)[0].short_url}
redirectedUrlValue={handleRedirectUrl}
/>
) : (
<EmptyField />
);
It does not work. the getShortenedUrl function is never called
Edit 2: Added the ShortUrlField component code
import React from "react";
const ShortUrlField = (props) => {
return (
<div>
<p>
<a href={props.originalUrlValue}>{props.originalUrlValue}</a> became{" "}
<a href={props.redirectUrlValue}>{props.shortUrlValue}</a>
</p>
</div>
);
};
export default ShortUrlField;
Edit 3: I made it work!!
Many thanks to #ZsoltMeszaros for pointing out the right path to me.
I have passed a state variable to my conditional rendered component, and added an effect hook that basically sets the state if the component is rendered.
Much thanks to all of you that commented.
I didn't understand where is this setRedirectURL function, but anyway your handleRedirectUrl function returns nothing
const handleRedirectUrl = () => {
return urlService
.getShortenedUrl(urls.slice(-1)[0].short_url)
.then((returnedUrl) => {
setRedirectedUrl(returnedUrl);
})
.catch((error) => {
handleCreateErrors(error);
})
.finally(() => {
return redirectedUrl;
});
};
May this can work as you can see like your axios request, this is returning result of urlService.

How can I update component whenever typing in input tag in React?

What the below code does is to get data from API, and then render it on the page. searchChange function takes a value from the input tag, and setValue for query state. My api endpoint takes argument to filter the API such as http://127.0.0.1:8000/api/deals/?q=${query}.
I'm very confused how I can update the DealList component with the API updated with query state whenever typing something in the input tag. I'm thinking of that I need to something in searchChange function, but not sure what to do there.
index.js
const useFetch = (url, query, defaultResponse) => {
const [result, setResult] = useState(defaultResponse);
const getDataFromAPI = async url => {
try {
const data = await axios.get(url);
setResult({
isLoading: false,
data
});
} catch (e) {}
};
useEffect(() => {
if (query.length > 0) {
getDataFromAPI(`${url}?q=${query}`);
} else {
getDataFromAPI(url);
}
}, []);
return result;
};
const Index = ({ data }) => {
const query = useInput("");
const apiEndpoint = "http://127.0.0.1:8000/api/deals/";
const dealFetchResponse = useFetch(apiEndpoint, query, {
isLoading: true,
data: null
});
const searchChange = e => {
query.onChange(e);
query.setValue(e.target.value);
};
return (
<Layout>
<Head title="Home" />
<Navigation />
<Container>
<Headline>
<h1>The best lease deal finder</h1>
<h4>See all the lease deals here</h4>
</Headline>
<InputContainer>
<input value={query.value} onChange={searchChange} />
</InputContainer>
{!dealFetchResponse.data || dealFetchResponse.isLoading ? (
<Spinner />
) : (
<DealList dealList={dealFetchResponse.data.data.results} />
)}
</Container>
</Layout>
);
};
export default Index;
The biggest challenge in something like this is detecting when a user has stopped typing.. If someone is searching for 'Milk' - when do you actually fire off the API request? How do you know they aren't searching for 'Milk Duds'? (This is hypothetical, and to demonstrate the 'hard' part in search bars/APIs due to their async nature)..
This is typically solved by debouncing, which has been proven to work, but is not very solid.
In this example, you can search Github repos...but even in this example, there are unnecessary requests being sent - this is simply to be used as a demonstration. This example will need some fine tuning..
const GithubSearcher = () => {
const [repos, setRepos] = React.useState();
const getGithubRepo = q => {
fetch("https://api.github.com/search/repositories?q=" + q)
.then(res => {
return res.json();
})
.then(json => {
let formattedJson = json.items.map(itm => {
return itm.name;
})
setRepos(formattedJson);
});
}
const handleOnChange = event => {
let qry = event.target.value;
if(qry) {
setTimeout(() => {
getGithubRepo(qry);
}, 500);
} else {
setRepos("");
}
};
return (
<div>
<p>Search Github</p>
<input onChange={event => handleOnChange(event)} type="text" />
<pre>
{repos ? "Repo Names:" + JSON.stringify(repos, null, 2) : ""}
</pre>
</div>
);
};
ReactDOM.render(<GithubSearcher />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

Resources