I will try to explain to the best where you can understand the issue.
If you see there is a button in both AddContact.js
and EditContact.js
, such as, Add
button and Update
button. They are wrapped by <Link to="/"></Link>
. However, if I click on the button the event is not happening. If I comment the <Link>
the event is being executed. I require both of my event handler
and <Link>
should work.
If you are going to comment or suggest me to put a event handler on button instead of onSubmit
could you please explain why it is and why not it will work being the present way of code.
App.js
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Route, Routes, useNavigate, useLocation } from 'react-router-dom';
import { v4 as uuid } from 'uuid';
import api from "../api/contacts";
import './App.css';
import Header from './Header';
import AddContact from './AddContact';
import EditContact from "./EditContact";
import ContactList from './ContactList';
import ContactDetail from './ContactDetail';
function App() {
const [contacts, setContacts] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState("");
const [editContactDetail, setEditContactDetail] = useState("");
//Retrieve Contacts
const retrieveContacts = async () => {
const response = await api.get("contacts");
return response.data;
};
const addContactHandler = async (singleContact) => {
const addContact = {
id: uuid(),
...singleContact
};
const addContactDone = await api.post("/contacts", addContact);
setContacts([...contacts, addContactDone.data]);
};
const removeContactHandler = async (id) => {
await api.delete(`/contacts/${id}`);
const newContactList = contacts.filter((deleteContact) => {
return deleteContact.id !== id;
});
setContacts(newContactList);
};
const searchHandler = (searchTerm) => {
setSearchTerm(searchTerm);
if (searchTerm !== "") {
const newContactList = contacts.filter((contact) => {
return Object.values(contact)
.join(" ")
.toLowerCase()
.includes(searchTerm.toLowerCase());
});
setSearchResults(newContactList);
} else {
setSearchResults(contacts);
}
};
const editChosenContact = (id) => {
const newContactList = contacts.filter((editRecord) => {
return editRecord.id === id;
});
setEditContactDetail(newContactList);
};
const updateContactPerson = async (selectedContactEdit) => {
const editResponse = await api.put(`/contacts/${selectedContactEdit.id}`, selectedContactEdit);
const {id, name, email} = editResponse.data;
setContacts( contacts.map(contact => {
return contact.id === id ? {...editResponse.data} : contact;
})
);
};
useEffect(() => {
const getAllContacts = async () => {
const allContacts = await retrieveContacts();
if(allContacts) setContacts(allContacts);
}
getAllContacts();
}, []);
useEffect(() => {
console.log("useEffect happening!");
}, [contacts]);
return (
<div>
<Router>
<Header/>
<Routes>
<Route exact path="/" element={ <ContactList contacts={ searchTerm.length < 1 ? contacts : searchResults } getContactId={ removeContactHandler }
getEditContact={editChosenContact} term={ searchTerm } searchKeyword={ searchHandler } /> }/>
<Route exact path="/add" element={ <AddContact addContactAction={ addContactHandler } /> }/>
<Route exact path="/edit/:id" element={ <EditContact editContactPerson={ editContactDetail } updateContactPerson={ updateContactPerson } /> }/>
<Route exact path="/contact/:id" element={ <ContactDetail /> }/>
</Routes>
</Router>
</div>
);
}
export default App;
AddContact.js
import React from "react";
import { Link } from "react-router-dom";
class AddContact extends React.Component {
state = {
name: "",
email: ""
}
add = (e) => {
e.preventDefault();
if (this.state.name === "" || this.state.email === "") {
alert("Enter name and email!");
return;
}
this.props.addContactAction(this.state);
this.setState({ name: "", email: ""});
};
render() {
return (
<div className="container">
<form onSubmit={ this.add }>
<div className="row">
<div className="col-sm-12 mt-5">
<h2>Add Contact</h2>
</div>
<div className="col-sm-6">
<label for="name">Name</label>
<input type="text" id="name" className="form-control" placeholder="name" aria-label="name" value={this.state.name} onChange={ (e) => this.setState({name: e.target.value}) }/>
</div>
<div className="col-sm-6">
<label for="email">Email</label>
<input type="text" id="email" className="form-control" placeholder="email" aria-label="email" value={this.state.email } onChange={ (e) => this.setState({email: e.target.value}) }/>
</div>
<div className="col-sm-12 mt-3">
<Link to="/">
<button className="btn btn-primary">Add</button>
</Link>
</div>
</div>
</form>
</div>
);
}
}
export default AddContact;
EditContact.js
import React from "react";
import { Link, useLocation } from 'react-router-dom';
import ContactCard from "./ContactCard";
import ContactDetail from "./ContactDetail";
class EditContact extends React.Component {
constructor(props){
super(props);
this.state = {
id: props.editContactPerson[0].id,
name: props.editContactPerson[0].name,
email: props.editContactPerson[0].email
};
}
update = (e) => {
e.preventDefault();
if(this.state.name !== "" && this.state.email !== "") {
this.props.updateContactPerson(this.state);
} else {
alert("All fields are mandatory!");
}
};
render() {
return (
<div className="container">
<form onSubmit={ this.update }>
<div className="row">
<div className="col-sm-12 mt-5">
<h2>Edit Contact</h2>
</div>
<div className="col-sm-6">
<label for="name">Name</label>
<input type="text" id="name" className="form-control" placeholder="name" aria-label="name" value={this.state.name} onChange={ (e) => this.setState({name: e.target.value}) }/>
</div>
<div className="col-sm-6">
<label for="email">Email</label>
<input type="text" id="email" className="form-control" placeholder="email" aria-label="email" value={this.state.email } onChange={ (e) => this.setState({email: e.target.value}) }/>
</div>
<div className="col-sm-12 mt-3">
<Link to="/">
<button className="btn btn-primary">Update</button>
</Link>
</div>
</div>
</form>
</div>
);
}
}
export default EditContact;
CodePudding user response:
Regarding:
If you are going to comment or suggest me to put a event handler on button instead of onSubmit could you please explain why it is and why not it will work being the present way of code.
As an event bubbles then there is a following sequence:
button onClick => Link onClick (navigation occurs) => form submit
When a navigation occurs DOM elements from prev page are removed with its event listeners and onSubmit is not called.
CodePudding user response:
The issue as I see it is that the click event from the button
element propagates up to the Link
component and that navigation to "/"
effectively kills anything the current page/component is processing. This means the form
element's onSubmit
handler isn't called.
You've a couple options:
- Add an
onClick
event handler to thebutton
and callstopPropagation
on the click event object to prevent it from bubbling up to theLink
. - Add an
onClick
event handler to theLink
component and callpreventDefault
on the click event object.
In either case the goal here is to prevent the immediate navigation from occurring, so the add
and update
handlers will need to issue an imperative redirect.
An example:
import { Link, useNavigate } from "react-router-dom";
const AddContact = ({ addContactAction }) => {
const navigate = useNavigate();
const [{ email, name }, setState] = React.useState({
name: "",
email: ""
});
const changeHandler = (e) => {
const { name, value } = e.target;
setState(state => ({
...state,
[name]: value,
}));
};
const add = (e) => {
e.preventDefault(); // <-- prevents default form action
if (name === "" || email === "") {
alert("Enter name and email!");
return;
}
addContactAction(state);
navigate("/", { replace: true }); // <-- navigate upon successful submit
};
return (
<div className="container">
<form onSubmit={add}>
<div className="row">
<div className="col-sm-12 mt-5">
<h2>Add Contact</h2>
</div>
<div className="col-sm-6">
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
name="name"
className="form-control"
placeholder="name"
aria-label="name"
value={name}
onChange={changeHandler}
/>
</div>
<div className="col-sm-6">
<label htmlFor="email">Email</label>
<input
type="text"
id="email"
name="email"
className="form-control"
placeholder="email"
aria-label="email"
value={email}
onChange={changeHandler}
/>
</div>
<div className="col-sm-12 mt-3">
<Link
to="/"
onClick={e => e.preventDefault()} // <-- prevent default link action
>
<button className="btn btn-primary">Add</button>
</Link>
</div>
</div>
</form>
</div>
);
};