I need to use a DELETE request with axios to delete a book from a list. I use this.state.data[index].number to get the number of the book and pass it to the URL but the console prints an error "index is not defined". How can I solve that error?
Otherwise, when I replace index by a specific index like 1, I get the number of the book add to the URL, but my variable cible, which also needs that number to remove the book, prints null...
This is my code :
export default class ListBooks extends React.Component {
constructor(props) {
super(props);
this.state = { error: null, data: [], number: "" }
}
componentDidMount() {
Axios.get(process.env.REACT_APP_API_PATH_BOOKS)
.then(res => {
this.setState({ data: res.data });
})
.catch(errorThrown => {
this.setState({ error: errorThrown });
})
}
/**
* Use to delete a book by the id.
*/
handleDelete = () => {
const id = this.state.data[index].number
Axios.delete(process.env.REACT_APP_API_PATH_BOOKS + id)
.then(res => {
console.log(res);
console.log(res.data);
let cible = document.getElementById("book-admin" + id);
console.log(cible);
cible.remove();
})
.catch(errorThrown => {
this.setState({ error: errorThrown });
})
}
render() {
const { data } = this.state;
return (
<div>
<Container>
{data.map((books, index) =>
<div key={books.number}>
<ListGroup>
<ListGroup.Item disabled id={"book-admin" + data.number}>{books.number}. {books.name} {books.author}
</ListGroup.Item>
</ListGroup>
<Button variant="outline-warning" size="sm" className="btn-admin-change" id={data.number} onClick={this.props.handleUpdate}>Modifier</Button>
<Button variant="outline-danger" size="sm" className="btn-admin-change" onClick={this.handleDelete}>Supprimer</Button>
</div>
)}
</Container>
</div>
)
}
}
You're not passing the index.
Try this:
onClick={() => this.handleDelete(index)}
and
handleDelete = (index) => {
After sending the delete request if you want to remove that item from your state array you can use this:
handleDelete = (index) => {
const id = this.state.data[index].number
Axios.delete(process.env.REACT_APP_API_PATH_BOOKS + id)
.then(res => {
console.log(res);
console.log(res.data);
// let cible = document.getElementById("book-admin" + id);
// console.log(cible);
// cible.remove();
this.setState(prevState => ({ ...prevState, data: prevState.data.filter((book) => book.number !== id) }))
})
.catch(errorThrown => {
this.setState({ error: errorThrown });
})
}
Pass the index as an argument in handleDelete function:
handleDelete = (index) => {
const id = this.state.data[index].number
Axios.delete(process.env.REACT_APP_API_PATH_BOOKS + id)
.then(res => {
console.log(res);
console.log(res.data);
let cible = document.getElementById("book-admin" + id);
console.log(cible);
cible.remove();
})
.catch(errorThrown => {
this.setState({ error: errorThrown });
})
}
Change your second Button onClick function to:
onClick={() => this.handleDelete(index)}
The console is right, index is not defined in the scope you need it
Do this
<Button variant="outline-danger" size="sm" className="btn-admin-change" onClick={() => this.handleDelete(index)}>Supprimer</Button>
And receive the parameter in the function
handleDelete = (index) => {
Related
I am building a chat app and using graphQl-ws and Im having issues with a function firing multiple times. My problem is why does doSubscriptionAction fire repeatedly and how to prevent that.
component
const ChatRooms = ({ selectConvo }: SelectConvoProp) => {
const {
data: { _id },
} = auth.getUser();
const { subscribeToMore, data } = useQuery(QUERY_CONVOS);
return (
<div className="sb-convos-wrapper">
<h3 className="sb-title">Chat Rooms - {data?.convos?.length}</h3>
<Convos
convos={data?.convos}
selectConvo={selectConvo}
subscribeToConvos={() => {
subscribeToMore({
document: SUBSCRIBE_CONVOS,
variables: { _id: _id },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
return doSubscriptionAction(prev, { subscriptionData });
},
});
}}
/>
</div>
);
};
subscribeToMoreActions function
/// this function take the subscription payload and adds it to the prev query cache
const addToPrev = (prev: any, { subscriptionData }: any) => {
const newConvo = subscriptionData.data.convo;
const updatedConvos = Object.assign(
{},
{
convos: [...prev.convos, newConvo],
}
);
return updatedConvos;
};
const removeFromPrev = (prev: any, { subscriptionData }: any) => {
const deletedConvo = subscriptionData.data.convo;
const filteredConvos = prev.convos.filter(
(convo: any) => convo._id === deletedConvo._id
);
const updatedConvos = Object.assign(
{},
{
convos: [...filteredConvos],
}
);
console.log(updatedConvos);
return updatedConvos;
};
/// this function delegates which function to use based off action
export const doSubscriptionAction = (prev: any, { subscriptionData }: any) => {
if (subscriptionData.data.convo.action === "create") {
return addToPrev(prev, { subscriptionData });
}
if (subscriptionData.data.convo.action === "delete") {
return removeFromPrev(prev, { subscriptionData });
}
};
convos component
const Convos = ({ convos, selectConvo, subscribeToConvos }: ConvosProp) => {
useEffect(() => {
subscribeToConvos();
}, [subscribeToConvos]);
const [deleteConvo] = useMutation(DELETE_CONVO, {
refetchQueries: [{ query: QUERY_CONVOS }, "Convos"],
});
const handleDelete = async (_id: string) => {
await deleteConvo({ variables: { _id } });
};
return (
<div className="sb-chat-container">
{convos?.map((convo: ConvoType, i: number) => {
return (
<div className="sb-chat-item-container" key={`chatRoom ${i}`}>
<div
className="sb-chat-content-container"
onClick={() => selectConvo(convo)}
>
<div className={"sb-chat-content-container-top"}>
<h5>{convo.roomName}</h5>
</div>
<LastMessage convo={convo} />
</div>
<button onClick={() => handleDelete(convo._id)}>X</button>
</div>
);
})}
</div>
);
};
TLDR: doSubscriptionAction fires multiple times adding multiple instances of created room to other user.
I have a state which I need to update with the ID returned from an endpoint call so I can call another another endpoint using that ID, I've made a state in the parent component and I use it in my first form to set the ID. I pass that id as a prop to the component that needs it but when I console.log the state, it doesn't change.
How can I pass the ID through the components?
I've added comments on the main places to look at
Here is my first form where I need the ID from -
const AddWebAppTypeForm = (props: any, ref: any) => {
const { setWebAppTypes, setNewAppValues}: AddWebAppTypeFormProps =
props;
const {
handleSubmit,
control,
reset,
formState: { isDirty },
} = useForm();
const onSubmit = (data: any) => {
let formData = new FormData();
formData.append("name", data.Title);
formData.append("description", data.Description);
formData.append("uploaded_file", data.Image[0]);
if (isDirty) {
createWebAppType(formData);
}
};
const createWebAppType = async (body: any) => {
await fetch(`${process.env.REACT_APP_API_URL}/webapptype`, {
method: "POST",
body: body,
})
.then((response) => response.json())
.then((data: IWebAppType) => {
const model: IWebAppType = {
id: data.id,
name: data.name,
image: data.image,
description: data.description,
image_url: data.image_url,
};
setNewAppValues(model.id); // <--- Set the state here
setWebAppTypes((prev) =>
prev.map((item) => (item.id === 0 ? model : item))
);
enqueueSnackbar(`Created App Succesfully`, {
variant: "success",
});
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<button hidden={true} ref={ref} type="submit" />
</form>
);
};
export default forwardRef(AddWebAppTypeForm);
My parent component with the states -
function WebAppTypeAccordion({ a, setWebAppTypes }: WebAppTypeAccordionProps) {
const [formEl, setFormEl] = useState(null);
const [addFormEl, setAddFormEl] = useState(null);
const [newAppValues, setNewAppValues] = useState<number>(0); // <--- state with 0 as initialised value
const handleRef = (el: any) => {
if (el !== null) {
setFormEl(el);
}
};
const handleAddRef = (el: any) => {
if (el !== null) {
setAddFormEl(el);
}
};
return (
<Accordion defaultExpanded={a.id === 0}>
<AccordionSummary
// onClick={(e) => handleOnClick(a, e)}
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>{a.name}</Typography>
</AccordionSummary>
<AccordionDetails>
{a.id === 0 ? (
<AddWebAppTypeForm
setWebAppTypes={setWebAppTypes}
ref={handleAddRef}
setNewAppValues={setNewAppValues} // <--- Passing setState to set id
/>
) : (
null
)}
<MappedAccordion
waobj={a}
key={a.id}
setWebAppTypes={setWebAppTypes}
editRef={formEl}
addRef={addFormEl}
newAppValues={newAppValues} // <--- Passing ID
/>
</AccordionDetails>
</Accordion>
);
}
export default WebAppTypeAccordion;
Here is where I am trying to use the ID to call another endpoint
function MappedAccordion({
waobj,
setWebAppTypes,
editRef,
addRef,
newAppValues,
}: MappedAccordionProps) {
const handleCreate = (data: FieldT) => {
let wtype: string = String(waobj.id);
if (addRef !== null) {
if (newAppValues !== 0) {
wtype = String(newAppValues); // <--- Try to use the ID but get default value
createFetch(data, wtype); // <--- Try to use the ID but get default value
}
}
createFetch(data, wtype);
};
const createFetch = (data: FieldT, wtype: string) => {
let formData = new FormData();
formData.append("name", data.name);
formData.append("link", data.link);
formData.append("wtype", wtype);
fetch(`${process.env.REACT_APP_API_URL}/webapp/`, {
method: "POST",
body: formData,
})
.then((response) => {
if (!response.ok) {
let err = new Error("HTTP status code: " + response.status);
enqueueSnackbar(`Environment already exists`, {
variant: "error",
});
throw err;
}
return response.json();
})
.then((data: IWebApp) => {
const model: FieldT = {
wid: data.id,
name: data.name,
link: data.link,
};
enqueueSnackbar(`Created Environment ${model.wid}`, {
variant: "success",
});
});
};
const onSubmit = (data: FormFields) => {
if (addRef !== null) addRef?.click(); // <--- Submit AddWebAppTypeForm form, set the ID
else editRef?.click();
let onSubmitData: FieldT[] = data.myFieldValues;
onSubmitData.map((a: FieldT, index) => {
let originalField: FieldT = initialFields[index];
if (a.wid === undefined) {
handleCreate(a);
} else {
if (JSON.stringify(a) !== JSON.stringify(originalField)) {
handleEdit(a);
}
}
});
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} id="environment-form">
<div style={{ paddingTop: 10 }}>
<Button
type="submit" // <--- Submit form
variant="outlined"
size="small"
sx={{ marginRight: 1 }}
>
Save
</Button>
<Button
variant="outlined"
size="small"
onClick={handleAppDelete}
disabled={waobj.id === 0 ? true : false}
>
Delete
</Button>
</div>
</form>
</div>
);
}
export default MappedAccordion;
Thanks for taking a look, I appreciate any help!
i cannot delete a product, although it seems that everything looks fine and i see in console that action of type: REMOVE_SELECTED_PRODUCT occurs, the product is still in array and seems like the state is not updated??
So lets start with productActions.js
export const removeSelectedProduct = (id) => {
return {
type: REMOVE_SELECTED_PRODUCT,
payload: id,
};
};
export const removeProduct = (id) => {
return async (dispatch) => {
dispatch(removeSelectedProduct(id));
try {
console.log(id); // this is for test, and i see in console that proper id is printed
await axios.delete(`https://fakestoreapi.com/products/${id}`);
} catch (err) {
console.log(err);
}
};
};
now the productsReducer.js
const intialState = {
products: [],
};
export const productsReducer = (state = intialState, { type, payload }) => {
switch (type) {
case SET_PRODUCTS:
return { ...state, products: payload };
case ADD_PRODUCT:
return { ...state, products: [...state.products, payload] };
case REMOVE_SELECTED_PRODUCT:
return {
...state,
products: state.products.filter((el) => el.id !== payload),
};
default:
return state;
}
};
And i use it in ProductDetails.js as an button so there is a whole code of this component:
const ProductDetails = ({ removeProduct, product }) => {
const { productId } = useParams();
const { image, title, price, category, description } = product;
const dispatch = useDispatch();
const fetchProductDetail = async (id) => {
const response = await axios
.get(`https://fakestoreapi.com/products/${id}`)
.catch((err) => {
console.log("Err: ", err);
});
dispatch(selectedProduct(response.data));
};
useEffect(() => {
if (productId && productId !== "") fetchProductDetail(productId);
}, [productId]);
return (
<div>
{Object.keys(product).length === 0 ? (
<div>...Loading</div>
) : (
<div>
<img alt={productId} src={image} />
<div>
<h1>{title}</h1>
<h2>
<p>${price}</p>
</h2>
<h3>{category}</h3>
<p>{description}</p>
<button>Add to Cart</button>
<button onClick={() => removeProduct(productId)}>UsuĊ</button>
</div>
</div>
)}
</div>
);
};
const mapStateToProps = (state) => {
return {
products: state.allProducts.products,
product: state.product,
};
};
const mapDispatchToProps = {
removeProduct,
selectedProduct,
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductDetails);
If anyone cant find the problem, i would be glad.
As an i said the acction and the id is printed to the console:
console output on clicking the button
Maybe there is some problem with state, and it dont update the state when i delete? Idk, please help me :(
As product has number type id and you're providing string i.e
2 !== "2"
Do this:
dispatch(removeSelectedProduct(+id));//change string to number
...
...
await axios.delete(`https://fakestoreapi.com/products/${+id}`);//similarly here
I am fairly new to react JS and I've implemented 2 dropdown boxes whose options are displayed by hitting an API. I want to obtain the selected value but I am getting the following error:
TypeError: Cannot read property 'value' of undefined.
As of now I just tried to obtain the value from one dropdown.
This is my code,
import React from 'react';
import Select from 'react-select';
import './Search.css';
class SearchForm extends React.Component {
constructor(props){
super(props);
this.state={
filtered :[],
values1 :[],
values2 :[],
selectedCategory:''
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
try{
this.setState({selectedCategory: event.target.value});
} catch (err) {
console.error('err', err);}}
componentDidMount() {
this.fetchData1()
this.fetchData2()
}
fetchData1 = async () => {
await fetch('/category/all')
.then(res => res.json())
.then(res =>
this.setState({
values1: res,
}),
)
.catch(error => console.log(error))
}
fetchData2 = async () => {
await fetch('/loc/all')
.then(res => res.json())
.then(res =>
this.setState({
values2: res,
}),
)
.catch(error => console.log(error))
}
async handleSubmit(event){
event.preventDefault();
try{
const url ='/jobs/all/'
const Response = await fetch((url),{
method: `GET`,
mode: 'cors',
headers: {
'Accept': 'application/json'
}});
const filtered = [];
const res = await Response.json();
const Location = this.menu2.value
const Category = this.menu1.value
console.log(Location)
console.log(Category)
Object.keys( res ).forEach( function( key ) {
if( res[key].location === Location && res[key].category === Category ) {
filtered[key] = res[key];}
});
this.setState({filtered})
console.log(this.state.filtered)
}
catch (err) {
console.error('err', err);}
};
render() {
let option1 = []
if (this.state.values1) {
this.state.values1.forEach(eachCategory => {
let Category = {}
Category.value = eachCategory.id
Category.label = eachCategory.category
option1.push(Category)
})
}
console.log(option1)
let option2 = []
if (this.state.values2) {
this.state.values2.forEach(eachLocation => {
let Location = {}
Location.value = eachLocation.id
Location.label = eachLocation.location
option2.push(Location)
})
}
console.log(option2)
return (
<div>
<form action="/search" onSubmit={this.handleSubmit.bind(this)}>
<Select options={option1} value={this.state.selectedCategory} placeholder='Category' onChange={this.handleChange}>
</Select>
<Select options={option2} placeholder='Location'/>
<button>Find</button>
</form>
{this.state.filtered.map((data)=>{
// return <div>{data.location}</div> // you can render here list items
return (
<div className="flex-container">
<div key={data.id}>
<div>Job Title: {data.category}</div>
<div>Location: {data.location}</div>
<div>Position: {data.position}</div>
<div>Duration: {data.duration}</div>
<div>Skills Required: {data.skills_req}</div>
<div>Apply By: {data.apply_by}</div>
<div>Starting Date: {data.starting_date}</div>
<div>Stipend: {data.stipend}</div>
<div>About Work: {data.about_work}</div>
<div>Perks: {data.perks}</div>
</div>
</div>)
})}
</div>
);
}
}
export default SearchForm;
Please point out where am I wrong.
Well, according to the react-select documentation you're handling onChange in a wrong way. It should just be like this.
handleChange = selectedOption => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
};
https://www.npmjs.com/package/react-select
So in your case, you just have to change this.setState({selectedCategory: event.target.value}); to this.setState({selectedCategory: event}); :
handleChange(event) { //give it a proper name, say selectedValue instead ofevent'
try{
this.setState({selectedCategory: event}); //no need of event.target.vaue; in fact that will be undefined
} catch (err) {
console.error('err', err);}}
Please note that this Select is different from normal select where you get the value using e.target.value in the handleChange method. The Select comes in with react-select package and hence you need to follow the usage accordingly.
You didn't send event to handleChange method.
Try:
onChange={e => this.handleChange(e)}
I am working on a hacker news clone I am trying to get the ids of the top stories from their api using axios in componentDidMount and then making another axios call to get the stories and push them in a state array but when I try to map over and render that array nothing shows up
class App extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.state.posts.push(value)
})
.catch(err =>{
console.log(err)
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
<Header title="Hacker News" />
{this.state.posts.map( (element, index) => <Post key={element.data.id} serialNum={index} postTitle={element.data.title} postVotes={element.data.score} postAuthor={element.data.by} />) }
</div>
)
}
}
Try setting the state like this:
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({
posts: [value, ...this.state.posts]
})
})
.catch(err =>{
console.log(err)
})
})
This way you're using setState and appending every new value to the existing state.
As stated in the comments, don't use push for set state. In your code when you make the second request you must change the setState method to spread out the new value.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState(prevState => ({posts: [ value.data, ...prevState.posts]}))
})
.catch(err =>{
console.log("err");
console.log(err);
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
{this.state.posts && this.state.posts.map( (element, index) =>
<div key={element.id}>
{element.title}
</div>
)}
</div>
);
}
}
componentDidMount() is called after Render() only once. React doesn't know about the state changes unless you use setState().
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({posts: [value, ...this.state.posts]})
})
})
})
}
Use this.setState({posts : [value, ...this.state.posts]}) instead of this.state.posts.push(value). using ... (spread operator) appends the value to the original posts array.