trying to delete from an API using axios and React hooks - reactjs

Hello im trying to delete a Booking from an api using an input with the ID . obviously something is wrong. I tried to convert my class to a HOC and i just cant get it to work. Right now i cant even type in the textbox .
I know i have severals errors but i dont know how to solve them would appreciate some help. the only relevant parts in the HTML is the form.
const DeleteBooking = () => {
const [ModalIsOpen, SetModalIsOpen] = useState(false); // set false if closen when open
const [booking, setDelete] = useState([]);
const handleChange = (e) => {
setDelete({ [e.target.name]: e.target.value });
};
useEffect((UserIdInput) => {
const bookingId = UserIdInput.target.elements.bookingId.value;
Axios.delete(`https://localhost:44366/api/Products/${bookingId}`) // change api key
.then((response) => {
console.log(response);
setDelete(response.data);
});
}, []);
return (
<>
<div className="App-header">
<button onClick={() => SetModalIsOpen(true)}>Radera bokning</button>
</div>
<Modal
isOpen={ModalIsOpen}
onRequestClose={() => SetModalIsOpen(false)}
style={{
overlay: {
background:
"linear-gradient(-500deg, #ee7752, #6e1b3b, #0c495f, #000000)",
},
content: {
color: "black",
textAlign: "center",
},
}}
>
<div>
<h1>Radera Bokning</h1>
<p style={{ marginTop: "20px" }}>
Vänligen ange ditt bokningsNummer för att radera din bokning
</p>
<form onSubmit={() => setDelete}>
<input
onChange={handleChange}
type="text"
name=" bookingId"
placeholder="BokningsID"
value="bookingId"
></input>
<button type="submit"></button>
</form>
<button
style={{ marginTop: "100px" }}
onClick={() => SetModalIsOpen(false)}
>
Tillbaka
</button>
</div>
</Modal>
</>
);
};
export default DeleteBooking;

Here is an incredibly simple example (working sandbox) that you can build upon:
import Axios from "axios";
import React, { useState } from "react";
// => Component Code
// -> This component will be used to delete posts
export default function App() {
// => State
const [readPostId, writePostId] = useState("");
const [readStatus, writeStatus] = useState("");
// => Handlers
const updatePostId = (e) => writePostId(e.target.value);
const deletePost = async (e) => {
e.preventDefault();
try {
await Axios.delete(`${API_ENDPOINT}/${readPostId}`);
writeStatus("Post successfully deleted");
setTimeout(() => writeStatus(""), 3000);
} catch (err) {
writeStatus("Post deletion failed");
}
};
return (
<div>
<h1>Delete Posts Page</h1>
<h2>Enter your Post ID:</h2>
<em>Press 'Delete' without entering a number to cause an error</em>
<form onSubmit={deletePost}>
<input onChange={updatePostId} value={readPostId} />
<input type="submit" value="Delete" />
</form>
{readStatus && <p>{readStatus}</p>}
</div>
);
}
// => Support Code
const API_ENDPOINT = "https://jsonplaceholder.typicode.com/posts";

Related

useEffect doesn't re-render on state change or infinite looping issue

I have a component which contains a form and a list. When user adds an item to the list though the form, the item should display immediately in the list. I try to use useEffect to fetch data, useEffect without dependency causes an infinite request loop. I added empty array as dependency to prevent looping but in this case new item which is added doesn't display in the list until refreshing the page. How can I solve this issue? (I use antd and antd-form-builder to create the component)
here is my code:
function FieldSetting() {
const [form] = Form.useForm()
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [])
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta} />
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting
Just add a state that will refretch (trigger useEffect) after you have submitted the form. Be aware that it will refetch all the data from the API. This might bring scalability issues when the data grows.
function FieldSetting() {
const [form] = Form.useForm()
const [refetch, setRefetch] = useState(false) // <----- add this state
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
setRefetch(!refetch) // <----- set the refetch to change the state
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [refetch]) // <----- add the refetch here to trigger the effect
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta}
/>
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting```
Whenever you manipulate your array just add a dummy state and change it
add this state
const [extra, setExtra] = useState(0)
when you change the state of your array like add or remove just add this line below
setExtra(extra+1)
what happens is that adding or removing data in an array don't count as a state change in react as per my understanding it need to be something different like true to false or in this case 0 to 1

How to render form after submission in react?

Given the following form, I need whenever the form is submitted, the new post to be listed/rendered without having to refresh the page.
const PostCreate = () => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I tried adding this.forceUpdate() and this.setState(this.state), neither works, and I still have to refresh the page for the new post to show.
Here's how the posts are rendered:
const PostList = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
This is what App.js looks like
const App = () => {
return <div>
<h1>Create Post</h1>
<PostCreate></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList></PostList>
</div>;
};
export default App;
and is eventually rendered using:
ReactDOM.render(
<App></App>,
document.getElementById('root')
)
In your PostList, useEffect called once when you first load your component, so when you create new post, it will not be re-rendered
You should bring your fetchPost logic to your App component, and add function props onPostCreated to PostCreate component, trigger it after you finish creating your new post
The code should be:
const App = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
return <div>
<h1>Create Post</h1>
<PostCreate onCreatePost={() => fetchPost()}></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList posts={posts}></PostList>
</div>;
};
export default App;
const PostList = ({ posts }) => {
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
const PostCreate = ({ onCreatePost }) => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
onCreatePost && onCreatePost();
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I think the problem you are having is not in the code you have displayed. The component is indeed rerendering after you change its state and also when you forceUpdate() it. I assume the posts you are trying to display are taken from the same API that you post to. Even if this component is being rerendered, your GET request which gives the data to the component who renders it is not called again so the data doesn't update. You need to refetch it. This can be done by many different ways (useEffect(), callbacks, reactQuery refetch) depending on the rest of your code. I would need the component that renders the data and the API call to help you further.
Another thing that you didn't ask but is good practice. In your PostCreate component you don't need to manage the state of fields that are in the form, because it already does it for you. Just give a name to your inputs and use the form data. I've given an example below.
import { useState } from "react";
const PostCreate = () => {
const onSubmit = async (event) => {
event.preventDefault();
console.log(event.target.elements.title.value);
};
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input name="title" className="form-control" />
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>
);
};
export default PostCreate;

How to modify when using GraphQL and Apollo

I have a project and it is an e-commerce project and through this project I have an interface that displays all the collections on the website, and I want to edit a collection,
And the collection only has one element, which is the name of the collection, and when you click on the edit icon, a dialog appears asking to edit the name of the specified collection.
My problem is that when I press the "Save" button, the value to be modified is not modified and no action occurs.
And this is a picture of the shape of the request from backend:
This file expresses the dialog of the modification:
import { React, useState } from "react";
import {
Input,
Modal,
ModalBody,
ModalFooter,
ModalHeader,
Button,
} from "reactstrap";
import { gql, useMutation } from "#apollo/client";
import { GET_PRODUCTS } from "./dashboard";
const UPDATE_COLLECTION = gql`
mutation updateCollection($name: String!) {
updateCollection(updateCollection: { name: $name }) {
name
}
}
`;
const EditCollection = (id, { isPublic = false }) => {
let input;
const [modal, setModal] = useState(false);
const togglePopup = () => setModal(!modal);
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const [collectionInput, setCollectionInput] = useState("");
const updateCache = (cache, { data }) => {
// If this is for the public feed, do nothing
if (isPublic) {
return null;
}
// Fetch the todos from the cache
const existingTodos = cache.readQuery({
query: GET_PRODUCTS,
});
// Add the new todo to the cache
const newCollection = data.insert_todos.returning[0];
cache.writeQuery({
query: GET_PRODUCTS,
data: { todos: [newCollection, ...existingTodos.todos] },
});
};
const resetInput = () => {
setCollectionInput("");
};
const [updateCollection] = useMutation(UPDATE_COLLECTION, {
update: updateCache,
onCompleted: resetInput,
});
return (
<div>
<Button onClick={togglePopup} style={{ backgroundColor: "none" }}>
<i
className="fa fa-pencil-square-o mr-1"
onclick={togglePopup}
aria-hidden="true"
></i>
</Button>
<Modal
isOpen={modal}
toggle={togglePopup}
centered
style={{ padding: "1rem" }}
>
<ModalHeader toggle={togglePopup} style={{ padding: "1rem" }}>
<h3>Edit Collection</h3>
</ModalHeader>
<ModalBody style={{ padding: "2rem" }}>
<Input
// value={name}
// onChange={(e) => setName(e.target.value)}
// style={{ padding}}
id="collectionName"
name="name"
placeholder="update collection name"
type="text"
/>
</ModalBody>
<ModalFooter style={{ paddingRight: "2rem" }}>
<Button
color="primary"
onclick={togglePopup}
onSubmit={(e) => {
e.preventDefault();
console.log("I am inside th function: ", e.target.value);
updateCollection({ variables: { name, id } });
message(e);
}}
>
Save
</Button>{" "}
<Button onclick={togglePopup}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
};
export default EditCollection;
And through this file, I called the file for the modification
dashboard.js:
const AllCollections = ({ id, name, img }) => {
return (
<tr>
<th scope="row">
<Media src={img} className="blur-up lazyloaded" />
</th>
<td>{id}</td>
<td>{name}</td>
<td>
<EditCollection id={id} />
<i className="fa fa-trash-o ml-1" aria-hidden="true"></i>
</td>
</tr>
);
};
You are defining only a name parameter, and then giving name and id. Change your GraphQl query to supply the id as well.
const UPDATE_COLLECTION = gql`
mutation updateCollection($name: String!, $id: ID!) {
updateCollection(updateCollection: { name: $name, id:$id }) {
name
}
}
`;
Also, to see whether the update has happened or get an error, add some console logs. And for that get them while calling useMutation:
const [updateCollection, { data, loading, error }] = useMutation(UPDATE_COLLECTION, {
update: updateCache,
onCompleted: resetInput,
});
console.log(data, loading, error);
An finally change the save button with the code below, as you are listening to onSubmit, which only works on form element.
<Button
color="primary"
onClick={(e) => {
e.preventDefault();
console.log("I am inside th function: ", e.target.value);
updateCollection({ variables: { name, id } });
message(e);
togglePopup();
}}
>
Save
</Button>

Reat Js navigate with json object to another page

Below is my code, I am fetching the data from api and on success I am setting the the response of state in set_ProductDetails. I want to pass the response state to different component and different page with the result and bind the data. I am using "react-router-dom": "^5.2.0".
Product_info.jsx
function GetProductDetails(products) {
const history = useHistory();
useEffect(() => {
console.log("render", history.location.state);
}, []);
return (
<>
<div>
<h1>Transaction Info</h1>
</div>
</>
);
}
export default GetProductDetails
Product_query.jsx
function ProductSearch() {
const [product_id, setProduct_id] = useState();
const [product_search, set_ProductSearch] = useState({ product_id: "" });
const [product_deatils, set_ProductDetails] = useState({ product_id: "" });
const history = useHistory();
//Handle the onSubmit
function handleSubmit() {
try {
set_ProductSearch({ address: product_id });
} catch (e) {
alert(e.message);
}
}
function onAPISuccess(data) {
history.push("/product_info/GetProductDetails", { data });
//here render blank screen
}
useEffect(() => {
const fetchData = async (product_id) => {
try {
const resp = await axios.post(
config.SERVER_URL + "/api/getProductInfo/",
product_id
);
set_ProductDetails(resp.data);
onAPISuccess(data)
} catch (err) {
console.error(err);
fetchData(product_search)
.catch(console.error);
}
}, [product_search]);
return (
<>
<div class="input-group mb-3">
<input
type="text"
class="form-control"
aria-describedby="button-addon2"
id="txt_address"
name="address"
placeholder="Address/Tx hash"
onChange={(e) => setProduct_id(e.target.value)}
></input>
<div class="input-group-append" style={{ color: "white" }}>
<button
class="btn btn-outline-success"
type="button"
id="button-addon2"
onClick={() => handleSubmit()}
>
Search
</button>
</div>
</div>
</>
);
}
export default ProductSearch
Home page
export default function Home() {
return (
<>
<main>
<div
className="col-md-12"
style={{
background: "#fff",
backgroundImage: `url(${Image})`,
height: "245px",
}}
>
<Container className="container-sm">
<Row>
<Col xs lg="5" className="justify-content-md-center">
<div>
<ProductSearch></ProductSearch>
</div>
</Col>
</Row>
</Container>
</div>
</main>
<>
)
}
Do history.push("/your_path",{..object you want to send}). Then in the component where this history.push redirects, access that object by saying history.location.state (this will return the object you passed while redirecting).

Lost input focus on hooks function state change

When i define the hooks state in the parent function i lost input field focus on first key press. I need the state definition in the root function.
import React, { useState } from 'react'
function Test1(props) {
const [test, setTest] = useState({value1: "", value2:""});
const Test = () => {
const handleChange= (e) => {
const _test = {...test, [e.target.name]: e.target.value}
setTest(_test)
}
return (
<div style={{ margin: "200px" }}>
<input name="value1" value={test["value1"]} onChange={handleChange}></input>
<input name="value2" value={test["value2"]} onChange={handleChange}></input>
<button onClick={() => console.log(test)}>Console.Log</button>
</div>
)
}
return (
<Test />
);
}
export default Test1;
But if I move the state definition in to the child function it works.
import React, { useState } from 'react'
function Test1(props) {
const Test = () => {
const [test, setTest] = useState({value1: "", value2:""});
const handleChange= (e) => {
const _test = {...test, [e.target.name]: e.target.value}
setTest(_test)
}
return (
<div style={{ margin: "200px" }}>
<input name="value1" value={test["value1"]} onChange={handleChange}></input>
<input name="value2" value={test["value2"]} onChange={handleChange}></input>
<button onClick={() => console.log(test)}>Console.Log</button>
</div>
)
}
return (
<Test />
);
}
export default Test1;
So! Why is this happening and how can I get over it?
I have been seeing this pattern a lot where people nest components in methods in components. It may be an opinion, but I feel like this may not be a great pattern.
I would abstract the one component function and pass the props down to the 2nd. something like this
const Test = ({test, setTest}) => {
const handleChange= (e) => {
const _test = {...test, [e.target.name]: e.target.value}
setTest(_test)
}
return (
<div style={{ margin: "200px" }}>
<input name="value1" value={test["value1"]} onChange={handleChange}></input>
<input name="value2" value={test["value2"]} onChange={handleChange}></input>
<button onClick={() => console.log(test)}>Console.Log</button>
</div>
)
}
function Test1(props) {
const [test, setTest] = useState({value1: "", value2:""});
return (
<Test test={test} setTest={setTest} />
);
}
export default Test1;

Resources