how to delete a single item using axios in react - reactjs

I have looked into many articles and posts like this but it does not work in my case.I simply need to delete an item from my post list in my application using axios. In the axios docs it says you need to pass in the params to the delete method. Also I have sen in most apps they use ids without having ids in their state. But i cannot get it working. Please see my entire code. I know that my delete method is wrong please help me fix it:
// The individual post component
const Post = props => (
<article className="post">
<h2 className="post-title">{props.title}</h2>
<hr />
<p className="post-content">{props.content}</p>
<button onClick={props.delete}>Delete this post</button>
</article>
);
// The seperate form component to be written later
class Form extends React.Component {}
// The posts loop component
class Posts extends React.Component {
state = {
posts: [],
post: {
title: "",
content: ""
}
// error:false
};
componentDidMount() {
const { posts } = this.state;
axios
.get("url")
.then(response => {
const data = Object.values(response.data);
this.setState({ posts : data });
});
}
handleChange = event => {
const [name , value] = [event.target.name, event.target.value];
// const value = event.target.value;
const { post } = this.state;
const newPost = {
...post,
[name]: value
};
this.setState({ post: newPost });
};
handleSubmit = event => {
event.preventDefault();
const {post} = this.state;
const {posts} = this.state;
axios
.post("url", post)
.then(response => {
// console.log(response);
const newPost = Object.values(response.data);
this.setState({ post: newPost });
const updatedPosts = [...posts, {title:post.title,content:post.content}];
this.setState({ posts: updatedPosts});
// console.log(post);
console.log(updatedPosts);
console.log(this.state.posts);
});
};
handleDelete = () => {
const { post } = this.state;
axios.delete("url",{params: {id: post.id}})
.then(response => {
console.log(response);
});
};
render() {
let posts = <p>No posts yet</p>;
if (this.state.posts !== null) {
posts = this.state.posts.map(post => {
return <Post
key={post.id}
{...post}
delete={this.handleDelete}/>;
});
}
return (
<React.Fragment>
{posts}
<form className="new-post-form" onSubmit={this.handleSubmit}>
<label>
Post title
<input
className="title-input"
type="text"
name="title"
onChange={this.handleChange}
/>
</label>
<label>
Post content
<input
className="content-input"
type="text"
name="content"
onChange={this.handleChange}
/>
</label>
<input className="submit-button" type="submit" value="submit" />
</form>
</React.Fragment>
);
}
}
I also see this Error in console:
Uncaught (in promise) TypeError: Cannot convert undefined or null to object
at Function.values which is in get method.
Thanks again.

You are not specifying what your Post component should delete. In other words, the props.delete is not receiving an id to pass up to your parent component. In order to do that, you can change that to () => props.delete(props.id) and then in your parent component you need to have the handleDelete method receive the id of the item you want to target which is the id we passed up earlier from Post.
I don't know how your server is set up but using the axios request you originally have in your question your code would look like this:
handleDelete = (itemId) => {
// Whatever you want to do with that item
axios.delete("url", { params: { id: itemId } }).then(response => {
console.log(response);
});
Here's a CodeSandbox (using some dummy data in the constructor) displaying the item being passed in a console.log() (axios statement is commented out).
EDIT: How to make axios delete requests using Firebase REST API
Oh sorry, I did not see that you were using Firebase. Direct REST requests are a bit different with Firebase. In your configuration the requests should look like this:
axios.delete(`${url}/${firebasePostId}.json`).then(response => {
console.log(response)
})
This is assuming your Firebase rules allow unauthorized requests (which I strongly advise against, seeing as anyone could send this request).
Please note that firebasePostId is the push key provided by Firebase when you send POST requests to them, and are in fact a great choice of id for your posts. An example of one is -LOLok8zH3B8RonrWdZs which you mentioned in the comments.
For more information on Firebase REST API syntax, check out their documentation.

Thanks #FranklinFarahani. I had to write an answer as it is too long. I have changed my get and post method and managed to fix the delete method. I use the unique key that firebase is creating per post to delete each item. I get that inget method. This is the entire code.
// The individual post component
const Post = props => (
// use the key as an id here
<article id={props.id} className="post">
<h2 className="post-title">{props.title}</h2>
<hr />
<p className="post-content">{props.content}</p>
<button onClick={props.delete}>Delete this post</button>
</article>
);
// The Post lists component
class Posts extends React.Component {
state = {
posts: [],
post: {
id: "",
title: "",
content: ""
},
indexes: []
};
componentDidMount() {
const { posts } = this.state;
axios
.get("firebaseURL/posts.json")
.then(response => {
// create an array to hold th unique id as key and post as value using Object.entries
const retrievedPosts = [];
for (const [key, value] of Object.entries(response.data)) {
const post = {
id: key,
title: value.title,
content: value.content
};
// add allposts to the array here
retrievedPosts.push(post);
}
// update state
this.setState({ posts: retrievedPosts });
console.log(retrievedPosts);
});
}
handleChange = event => {
const [name, value] = [event.target.name, event.target.value];
// const value = event.target.value;
const { post } = this.state;
const newPost = {
...post,
[name]: value
};
this.setState({ post: newPost });
};
handleSubmit = event => {
event.preventDefault();
const { posts } = this.state;
// use this as a temporary id for post method
const postIndex = posts.length + 1;
const post = {
id: postIndex,
title: this.state.post.title,
content: this.state.post.content
};
axios
.post("firebaseURL/posts.json", post)
.then(response => {
const updatedPosts = [
...posts,
{ id: post.id, title: post.title, content: post.content }
];
// update state
this.setState({ posts: updatedPosts });
console.log(posts);
});
};
handleDelete = postId => {
event.preventDefault();
// get a copy of the posts
const posts = [...this.state.posts];
// in delete method use postId to create a unique url for the post to be deleted
axios
.delete(
"firebaseURL/posts/" + postId + ".json"
)
.then(response => {
//update state
this.setState({ posts: posts });
});
};
render() {
let posts = <p>No posts yet</p>;
if (this.state.posts !== null) {
posts = this.state.posts.map(post => {
return (
<Post
id={post.id}
key={post.id}
{...post}
delete={() => this.handleDelete(post.id)}
/>
);
});
}
return (
<React.Fragment>
{posts}
<form className="new-post-form" onSubmit={this.handleSubmit}>
<label>
Post title
<input
className="title-input"
type="text"
name="title"
onChange={this.handleChange}
/>
</label>
<label>
Post content
<textarea
className="content-input"
rows="7"
type="text"
name="content"
onChange={this.handleChange}
/>
</label>
<input className="submit-button" type="submit" value="submit" />
</form>
</React.Fragment>
);
}
}
The problem is my state is not updated after delete so although the post has been deleted from my database it is still in the DOM.
And more importantly if submit a new post cannot delete it after a get request or refresh being done. The reason is in post request the key will be created after the request is done and therefor I will not have the key to update state and DOM until after the next get request or refresh. And the id will be the temporary one which I assign during post method which cannot be used to delete a post.

Related

useState in parent component not keep state, when parent component's callback function is called from child component

Update
This maybe a Formik bug, and I have switched to https://react-hook-form.com, as Formik has not been updated for a while.
https://github.com/jaredpalmer/formik/issues/3716
Context
I'm using React, Formik, and google-map-react to allow store owner edit their store address with google map place autocomplete.
I have three components:
EditStoreInfoPage is the page component, which contains EditStoreInfoForm.
EditStoreInfoForm is the form component, which contains FormikAddressField. I uses Formik here.
FormikAddressField is the one form field that supports google place autocomplete.
Store information will be fetched from backend in EditStoreInfoPage, and passed down to EditStoreInfoForm and FormikAddressField. Whenever a new address is typed in FormikAddressField, it calls a callback function handleStoreLocationUpdate passed down from EditStoreInfoPage.
Issue
Render the page without any issue. I see that formValues are populated corrected with the data fetched from backend.
However, once I finished typing the address, the form get cleared except the store address is still there.
From the console output of above screenshot, I can see that function handleStoreLocationUpdate get called, however, console.log(formValues); in function handleStoreLocationUpdate of EditStoreInfoPage contains empty value for store fields. I was expecting that the formValues here still kept the value fetched from backend, not sure why these values get wiped out as I use React useState.
Any idea what went wrong?
Code
EditStoreInfoPage
This is the React component that first call backend API to get the store information based on storeIdentifier. formValues will be populated with these information, as you can see that setFormValues is being called. formValues is passed down to child component EditStoreInfoForm as props.
type EditStoreInfoPageProps = {
storeIdentifier: string;
};
const EditStoreInfoPage = (props: EditStoreInfoPageProps) => {
let navigate = useNavigate();
const [formValues, setFormValues] = React.useState<StoreAttributes>({
storeName: "",
storeLocation: "",
storeLocationLongitude: 0,
storeLocationLatitude: 0,
});
// Get store info.
React.useEffect(() => {
const user: CognitoUser | null = getCurrentBusinessAccountUser();
if (!user) {
Toast("Store Not Found!", "Failed to get store information!", "danger");
} else {
const storeIdentifier: string = user?.getUsername();
getStoreInfo(storeIdentifier)
.then((response) => {
setFormValues({
storeName: response?.storeName || "",
storeLocation: response?.storeLocation || "",
storeLocationLatitude: response?.storeLocationLatitude!,
storeLocationLongitude: response?.storeLocationLongitude!,
});
})
.catch((error) =>
Toast(
"Store Not Found!",
"Failed to get store information!",
"danger"
)
);
}
}, []);
const handleStoreLocationUpdate = (newStoreLocation: string) => {
const geocoder = new window.google.maps.Geocoder();
console.log("handleStoreLocationUpdate");
console.log(newStoreLocation);
console.log(formValues);
const geocodeRequest = { address: newStoreLocation };
const geocodeCallback = (
results: google.maps.GeocoderResult[] | null,
status: google.maps.GeocoderStatus
) => {
if (status === "OK") {
if (results && results[0]) {
const formValuesClone: StoreAttributes = structuredClone(formValues);
formValuesClone.storeLocation = newStoreLocation;
formValuesClone.storeLocationLatitude =
results[0].geometry.location.lat();
formValuesClone.storeLocationLongitude =
results[0].geometry.location.lng();
setFormValues(formValuesClone);
} else {
Toast("Not valid address!", "Please input a valid address", "danger");
}
} else {
Toast("Not valid address!", "Please input a valid address", "danger");
}
};
geocoder.geocode(geocodeRequest, geocodeCallback);
};
const handleSubmit = (data: StoreAttributes) => {
updateStore(props.storeIdentifier, JSON.stringify(data, null, 2))
.then((response) => {
if (response.status == 200) {
Toast(
"Updated!",
"The store information has been updated. Redirect to store page...",
"success"
);
navigate("/stores/" + props.storeIdentifier);
} else {
Toast(
"Updated failed!",
"Failed to update store information.",
"danger"
);
}
})
.catch((error) => {
Toast("Updated failed!!", error.message, "danger");
});
};
const handleUpdate = (data: StoreAttributes) => {
// make a deep clone here, as formValues here is an object.
console.log("handleUpdate");
const copy = structuredClone(data);
setFormValues(copy);
};
return (
<EditStoreInfoForm
formValues={formValues}
handleStoreLocationUpdate={handleStoreLocationUpdate}
handleUpdate={handleUpdate}
handleSubmit={handleSubmit}
/>
);
};
export default EditStoreInfoPage;
EditStoreInfoForm
EditStoreInfoForm is the form component. I use Formik here. It renders the form with props.formValues. It contains a child component FormikAddressField which will be used to support google place auto complete.
export type EditStoreInfoFormProps = {
formValues: StoreAttributes;
handleStoreLocationUpdate: any;
handleUpdate: any;
handleSubmit: any;
};
const EditStoreInfoForm = (props: EditStoreInfoFormProps) => {
console.log("EditStoreInfoForm");
const onBlur = () => {
console.log(props.formValues);
}
return (
<div className="flex justify-center items-center">
<Formik.Formik
initialValues={props.formValues}
enableReinitialize={true}
validationSchema={validationSchema}
validateOnChange={false}
validateOnBlur={false}
onSubmit={(values) => {
props.handleSubmit(values);
}}
>
{({ }) => (
<Formik.Form className="w-1/3">
<div className="form-group">
<div>
<FormikTextField
label="Store Name"
name="storeName"
placeholder={props.formValues?.storeName}
/>
</div>
<div className="form-group">
<FormikAddressField
label="Store Location"
name="storeLocation"
onAddressUpdate={props.handleStoreLocationUpdate}
placeholder={props.formValues?.storeLocation}
/>
</div>
<div className="w-full h-60">
{/* <GoogleMapLocationPin latitude={10} longitude={10} text="store"/> */}
<StoresGoogleMapLocation
googleMapCenter={{
lat: props.formValues.storeLocationLatitude,
lng: props.formValues.storeLocationLongitude,
}}
storeAddress={props.formValues?.storeLocation}
storeLocationLongitude={
props.formValues?.storeLocationLongitude
}
storeLocationLatitude={props.formValues?.storeLocationLatitude}
/>
</div>
<div className="form-group">
<button type="submit" className="form-button m-2 w-20 h-10">
Update
</button>
</div>
</Formik.Form>
)}
</Formik.Formik>
</div>
);
};
export default EditStoreInfoForm;
FormikAddressField
FormikAddressField is the field for autocomplete. See https://developers.google.com/maps/documentation/javascript/place-autocomplete to know what it is.
const FormikAddressField = ({ label, onAddressUpdate, ...props }: any) => {
const [field, meta] = useField(props);
const loader = new Loader({
apiKey: process.env.REACT_APP_GOOGLE_MAP_API_KEY!,
libraries: ["places", "geometry"],
});
const locationInputId = "locationInputId";
let searchInput: HTMLInputElement;
const autoCompleteInstanceRef = React.useRef<any>(null);
React.useEffect(() => {
loader.load().then(() => {
let searchInput = document.getElementById(
locationInputId
) as HTMLInputElement;
//console.log(searchInput);
autoCompleteInstanceRef.current = new google.maps.places.Autocomplete(
searchInput!,
{
// restrict your search to a specific type of resultmap
//types: ["address"],
// restrict your search to a specific country, or an array of countries
// componentRestrictions: { country: ['gb', 'us'] },
}
);
autoCompleteInstanceRef.current.addListener(
"place_changed",
onPlaceChanged
);
});
// returned function will be called on component unmount
return () => {
google.maps.event.clearInstanceListeners(searchInput!);
};
}, []);
const onPlaceChanged = () => {
const place: google.maps.places.PlaceResult =
autoCompleteInstanceRef.current.getPlace();
if (!place) return;
onAddressUpdate(place.formatted_address);
};
return (
<>
<label htmlFor={props.id || props.name} className="form-label">
{label}
</label>
<Field
id={locationInputId}
className="text-md w-full h-full m-0 p-0"
type="text"
{...field}
{...props}
/>
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
};
export default FormikAddressField;
CodeSandbox
Here is a simplified version: https://nv1m89.csb.app/
The EditStoreInfoPage is above the EditStoreInfoForm. The formikValues in EditStoreInfoPage appears to be a copy, which is not updated every time the actual real-time formik values in EditStoreInfoForm are changed. Your real problem here is that you shouldn't have the clone in the first place.
Just pass the real store values up to the handler:
<FormikAddressField
label="Store Location"
name="storeLocation"
onAddressUpdate={(newAddress) => props.handleStoreLocationUpdate(newAddress, formValues)}
placeholder={props.formValues?.storeLocation}
/>
Now change:
const handleStoreLocationUpdate = (newStoreLocation: string) => {
To:
const handleStoreLocationUpdate = (newStoreLocation: string, formValues: StoreAttributes) => {
And use that argument.
As mentioned there are other issues here. Really you should refactor to get rid of this completely:
const [formValues, setFormValues] = React.useState<StoreAttributes>({
storeName: "",
storeLocation: "",
storeLocationLongitude: 0,
storeLocationLatitude: 0,
});
You'd do it by making the actual form state accessible to that component. Probably by changing to the useFormik pattern and loading that hook in the parent.

Need to Update State Synchronously after Fetch API Request

I have a search component that fetches a single profile from a JSON file (currently local, but will be remote in the future) and displays the information of the matching profile beneath the input field.
Currently, on my first submit of my search query, I've found that all of my state variables return undefined because, if I understand correctly, state does not update until the full chain of promises has resolved. And it's only on my second submit of my search query that my state variables return the correct data of the filtered search result.
On the first submit, it appears that an empty array is being initialized, as my conditional render of {props.activeChart && `OPENED CHART : ${props.firstName} ${props.lastName} (DOB: ${props.DOB})`} becomes truthy and renders out empty values for firstName, lastName, and DOB.
EDIT: I came across this recent post (React state gets updated only after I submit the form twice), which seems to address this same issue resulting from asynchronous fetch and setting state, except with axios. I've tried modifying my code accordingly (edited below), but I'm still not able to update state after my fetch result has resolved. Any advice would be appreciated. Thanks.
import { useState } from 'react';
import StyledSearchForm from './SearchForm.styled';
const SearchForm = props => {
const [queryFirstName, setQueryFirstName] = useState('');
const [queryLastName, setQueryLastName] = useState('');
const [queryDOB, setQueryDOB] = useState('');
const handleQuery = async (e) => {
e.preventDefault();
const result = await fetchRecord();
console.log(result[0]) // fetched object successfully logged
if (result[0]) {
setActiveChart(result[0]);
console.log(activeChart) // activeChart still undefined
setFirstName(activeChart.firstName);
setLastName(activeChart.lastName);
setDOB(activeChart.dob);
}
};
const fetchRecord = () => (
fetch('http://localhost:8000/patients')
.then(resp => { return resp.json(); })
.then(data => {
const result = data.filter(patient => (
(patient.dob === queryDOB.trim() &&
patient.lastName.toLowerCase() ===
queryLastName.toLowerCase().trim()) ||
(patient.lastName.toLowerCase() ===
queryLastName.toLowerCase().trim() &&
patient.firstName.toLowerCase() ===
queryFirstName.toLowerCase().trim())
));
return {...result};
})
);
return (
<StyledSearchForm>
<form onSubmit={handleQuery}>
<label className="first-name" htmlFor="first-name">
First Name:
</label>
<input
type="text"
id="first-name"
className="form-fields"
name="fname"
value={queryFirstName}
onChange={e => setQueryFirstName(e.target.value)}
/>
<label className="last-name" htmlFor="last-name">
Last Name:
</label>
<input
type="text"
id="last-name"
className="form-fields"
name="lname"
value={queryLastName}
onChange={e => setQueryLastName(e.target.value)}
/>
<label className="dob" htmlFor="dob">
DOB:
</label>
<input
type="text"
id="dob"
className="form-fields"
name="dob"
value={queryDOB}
onChange={e => setQueryDOB(e.target.value)}
/>
<button className="submit-btn" type="submit" onClick={e => handleQuery}>Open Chart</button>
</form>
<div className="active-patient">
{props.activeChart && `OPENED CHART : ${props.firstName} ${props.lastName} (DOB: ${props.DOB})`}
</div>
</StyledSearchForm>
);
};
export default SearchForm;
It looks like you're expecting your data filter to return an object, but Array.prototype.filter (docs) returns an array. Arrays, even if empty, are truthy.
You need to handle an array, not an object, in this chain:
const fetchRecord = () =>
fetch("http://localhost:8000/patients")
.then((resp) => {
return resp.json();
})
.then((data) => {
// results is an array!
const results = data.filter(...);
if (results.length === 0) {
// no match - do something about it?
return {};
} else {
// return the first result?
return results[0];
}
})
.then((record) => {
props.setActiveChart(...record);
})
.then(() => {
props.setFirstName(props.activeChart.firstName);
props.setLastName(props.activeChart.lastName);
props.setDOB(props.activeChart.dob);
});
It seems the issue resulted from trying to set all of my state variables in the same async function that was fetching my search result, and by moving the if(results[0]) statement out of the handleQuery function while leaving just the setActiveChart() inside the handleQuery function resolved my issue.

How to do bring predictions from rest api using fetch in react js

I am building an app where the flask rest API takes two strings and gives a floating value as a prediction. Now I am trying to connect to the react app so that the predictions can be shown on a webpage.
Goal: takes two strings from the frontend and does inference using restapi and gives the values of the prediction on the front end.
Below is the code used to fetch the rest API predictions in react app.
function App() {
const [state, setState] = useState([{}]);
useEffect(() => {
fetch("/predict?solute=CC(C)(C)Br&solvent=CC(C)(C)O")
.then((res) => res.json())
.then((data) => {
setState(data);
console.log(data);
});
}, []);
In fetch /predict?solute=CC(C)(C)Br&solvent=CC(C)(C)O here solute=CC(C)(C)Br and solvent=CC(C)(C)O are the inputs for the flask rest API to give predictions.
But I want to give it from the frontend rather than mentioned in the URL. How to do it?
Modified code to fetch results and display
function App() {
const [state, setState] = useState([{}]);
const [uri, setUri] = useState([{}]);
const [resultstate, setResultState] = useState([{}]);
function HandleSubmit() {
const uri = "http://127.0.0.1:3000/?${form.one}&${form.two}";
setUri(uri);
useEffect(() => {
fetch(uri)
.then((response) => {
if (response.status === 200) {
return response.json();
}
})
.then((data) => {
setResultState(data);
console.log(data);
});
});
}
function handleChange(e) {
const { nodeName, name, value } = e.target;
if (nodeName === "INPUT") {
setState({ ...FormData, [name]: value });
}
}
return (
<div className="App">
<state onChange={handleChange}>
<fieldset>
<legend>Solute</legend>
<input name="one" value={state.one} />
</fieldset>
<fieldset>
<legend>Solvent</legend>
<input name="two" value={state.two} />
</fieldset>
<button type="button" onClick={HandleSubmit}>
Submit
</button>
</state>
<Deploy />
</div>
);
}
Running the modified code I am getting this error
Create a new form state.
Create a form with some input elements, and a button to submit the form.
When the input elements are changed update the form state with their values.
When the form is submitted create a new URI with the information in form, and then do the fetch.
const { useState } = React;
function Example() {
const [ form, setForm ] = useState({});
function handleSubmit() {
const uri = `http://example.com/?${form.one}&${form.two}`;
console.log(`Current state: ${JSON.stringify(form)}`);
console.log(`Fetch URI: ${uri}`);
// fetch(uri)... etc
}
// Because the listener is attached to the form element
// (see "Additional documentation" below)
// check that the element that's been changed is an input
// and then update the state object using the name as a key, and
// adding the value of the input as the object value for that key
function handleChange(e) {
const { nodeName, name, value } = e.target;
if (nodeName === 'INPUT') {
setForm({ ...form, [name]: value });
}
}
// Each input has a name, and maintains its
// value from the form state
return (
<form onChange={handleChange}>
<fieldset>
<legend>Input one</legend>
<input name="one" value={form.one} />
</fieldset>
<fieldset>
<legend>Input two</legend>
<input name="two" value={form.two} />
</fieldset>
<button type="button" onClick={handleSubmit}>Submit</button>
</form>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Additional documentation
Event delegation - that's what's happening when we attach one listener to the form rather than listeners to all the inputs. The form catches the events from the inputs as they bubble up the DOM. But it's also why we need to identify what element they are, hence the need for nodeName.
Destructuring assignment

How to delete from Firebase with React?

I'm trying to add a delete button to a post so that I can delete individual posts from a channel. I'm using Firebase as my database and the posts collections are nested within channels collection. I added a button to each post, created an onClick event called handleDel, I've tried finding the post inside the channel collection and deleting it but I can't delete my post.
const db = firebaseApp.firestore()
Post.js
function Post({ user, post, timestamp }) {
const channelId = useSelector(selectChannelId)
const handleDel = () => {
db.collection('channels')
.doc(channelId)
.collection('posts')
.doc(post).delete().then(() => {
console.log("Document successfully deleted!");
}).catch((error) => {
console.error("Error removing document: ", error);
});
}
return (
<div className='post'>
<Avatar src={user.photo}
/>
<div className="post__content">
<h4>{user.displayName}
<span className='post__timestamp'>
<ClearIcon
className='clearIcon'
onClick={handleDel}
id={post.id}
/>
{new Date(timestamp?.toDate()).toUTCString()}
</span>
</h4>
<p className='post__text'>
{post}
</p>
</div>
</div>
)
}
Posts.js for more context
function Posts() {
const user = useSelector(selectUser)
const channelId = useSelector(selectChannelId)
const channelName = useSelector(selectChannelName)
const [input, setInput] = useState('')
const [posts, setPosts] = useState([])
useEffect(() => {
if (channelId) {
db.collection('channels')
.doc(channelId)
.collection('posts')
.orderBy('timestamp', 'asc')
.onSnapshot((snapshot) =>
setPosts(snapshot.docs.map(doc => ({...doc.data(), id: doc.id})))
)
}
}, [channelId])
const sendPost = e => {
e.preventDefault()
db.collection('channels')
.doc(channelId)
.collection('posts')
.add({
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
post: input,
user: user,
})
setInput('')
}
return (
<div className='posts__container'>
<ChannelTitle channelName={channelName} />
<div className="posts">
{posts.map((post) => (
<Post
timestamp={post.timestamp}
post={post.post}
user={post.user}
id={post.id}
key={post.id}
/>
))}
</div>
<div className="post__input">
<AddCircleIcon />
<form>
<input
type="text"
placeholder={`Post to #${channelName}`}
value={input}
disabled={!channelId}
onChange={e => setInput(e.target.value)}/>
<button
type='submit'
className='post__inputButton'
onClick={sendPost}>
Send</button>
</form>
<div className="post__inputIcons">
<GifIcon />
<EmojiEmotionsIcon />
</div>
</div>
</div>
)
}
I've also tried
const handleDel = () => {
db.collection('posts')
.doc(post).delete().then(() => {
console.log("Document successfully deleted!");
}).catch((error) => {
console.error("Error removing document: ", error);
});
}
and
const handleDel = () => {
db.collection('channels')
.doc(channelId)
.collection('posts')
.doc(post.id).delete().then(() => {
console.log("Document successfully deleted!");
}).catch((error) => {
console.error("Error removing document: ", error);
});
}
My console says "Document successfully deleted!" but the post is still in the database and still shows. Any insight would be appreciated.
Take a look at these lines:
(<Post
timestamp={post.timestamp}
post={post.post}
user={post.user}
id={post.id}
key={post.id}
/>)
function Post({ user, post, timestamp }) {
Here, you pass in the properties id, post, timestamp, and user into your component but only pull out post, timestamp, and user. In addition, post here is not the post object (that has id, timestamp, etc inside it) but is the text content of the post, originally the value of input when posted, as a string. This means that your Post component doesn't know it's own ID as post.id in this case would be undefined. Plus, because you are deleting the document using .doc(post), the delete operation succeeds because you end up deleting a document that is named with the content of your message like /channels/${channelId}/posts/"This is my message!" instead of what you are trying to delete, which is /channels/${channelId}/posts/${postId}.
To correct this, you would change it to:
function Post({ id: postId, user, post, timestamp }) {
const channelId = useSelector(selectChannelId)
const handleDel = () => {
return db.collection('channels') // note return of promise chain
.doc(channelId)
.collection('posts')
.doc(postId)
.delete()
.then(
() => {
console.log("Document successfully deleted!");
},
(error) => {
console.error("Error removing document: ", error);
}
);
}
/* ... rest of component ... */
}
Other things of concern:
Rename your document's post field to content or similar to disambiguate between your post as a whole and its content
Use postData instead of just post and postDataList/postDataArray instead of just posts to disambiguate them or make use of TypeScript to help catch these typos
For each post, you are calling useSelector to obtain the channelId. Which is fine, but as there is a clear parent-child relationship here, you could pass in the channelId along with the post's data from your Posts component for improved performance.
The channel listener (useEffect(/* ... */, [channelId])) doesn't clean up after itself
The posts in the channel are not paginated. (Imagine a month-old conversation chain of hundreds of messages with at least 10 users listening to the channel - do all of those users need all of those messages at once? - that's 1000+ document reads when you might only need 10 per user)
The sendPost click handler doesn't wait for confirmation that the message was send before clearing it's input
The sendPost click handler doesn't show an error when channelId is falsy
The sendPost click handler doesn't show an error on failure to add the message to the database
The sendPost click handler allows posting empty messages
The Send button is not disabled when channelId is falsy
The value of user isn't error-checked, it is also unclear if this is a User object, their name or their UID.
Try this.
You need to pass unique id of post/list in API call to delete specific database entry
improved
<ClearIcon
className='clearIcon'
onClick={() => handleDel(post.id)}
id={post.id}
/>
function
const handleDel = (id) => {
db.collection('channels')
.doc(channelId)
.collection('posts')
.doc(id).delete().then(() => {
console.log("Document successfully deleted!");
}).catch((error) => {
console.error("Error removing document: ", error);
});
}

render state after state is updated with response from axios

i wanna render state after i updated state with another response from api
on component did mount i make a request from random user and i store it in state, and i made a button which on click makes another request and stores another user from api in state , my problem is i cant seem to render the new state ( updated after click on button ) on console.log i see the updated state , but when i try to render , i get only the initial value , the rest appear as undefined
state = {
persons : []
}
async componentDidMount( ){
let response = await axios(`https://randomuser.me/api/?results=1`)
this.setState({
persons: response.data.results
})
}
update = async () => {
const response = await axios(`https://randomuser.me/api/?results=1`)
this.setState(prevState => ({
persons: [...prevState.persons, response.data.results]
}))
}
render(){
const test = this.state.persons.map( i => i.cell)
return(
<div>
{test}
<button onClick={this.update}>update</button>
</div>
)
}
You need to set the correct state. the response is an array, so you need to merge both arrays
update = async () => {
const response = await axios(`https://randomuser.me/api/?results=1`)
this.setState(prevState => ({
persons: [...prevState.persons, ...response.data.results]
}))
}
This should work
render(){
const test = this.state.persons.map( i => {
return(
<div>{i.cell}</div>
)
})
return(
<div>
{test}
<button onClick={this.update}>update</button>
</div>
)
}

Resources