1

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;
Common Man
  • 103
  • 2
  • 13
  • Why have you got a button in a link? Don't you just want a link _styled like_ a button? Or if that's to submit the form, _just a button_? – jonrsharpe Aug 25 '22 at 13:28
  • @jonrsharpe - Do you mean wrapping a button with a `` is not a right way of coding? If I do that way, the result is same, the event handler is not being executed but quickly redirected to home page since it is a `/`. – Common Man Aug 25 '22 at 13:32
  • It's not going to lead to valid HTML (as an [`a`nchor](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) shouldn't contain interactive content) and you're asking because it doesn't work, so no. – jonrsharpe Aug 25 '22 at 13:34
  • Yes, I agree, `` is in turn a ``. – Common Man Aug 25 '22 at 13:35

2 Answers2

1

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.

Andrey Smolko
  • 712
  • 3
  • 8
  • I am going to try that way... The above code are followed from a youtube video for DYI. There were some obsolete `router` concepts, which I managed to fix. Now, I am more curious that, how it were working for that youtube guy, with `react-router-dom` v.5.3 – Common Man Aug 26 '22 at 06:12
1

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:

  1. Add an onClick event handler to the button and call stopPropagation on the click event object to prevent it from bubbling up to the Link.
  2. Add an onClick event handler to the Link component and call preventDefault 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>
  );
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Since the tutorial taught me in `class` component and even though, I am aware, class component are not recommended by reactjs official documentation and they suggest us to use, functional components. I tried `window.location.href` instead of using `useNavigate` hook, as we cannot use hooks inside react class component, before I posted the question. I realised, changing the class to functional would be the best practice or we will caught up in such scenarios with no way out. However, going to try with `React.createRef()` to make myself familiarise with classes. I am accepting your answer. – Common Man Aug 26 '22 at 06:51
  • @CommonMan Fair enough. If you need to use class components and you need them to access the `navigate` function you can create a wrapper component or your own custom `withRouter` Higher Order Component. See on of my answers [here](/a/69902006/8690857) with example implementation and usage. – Drew Reese Aug 26 '22 at 06:59
  • I have done this in class itself and I feel it as a hack and not a good quality, ` ` inside class `redirect = React.createRef();` and at end of event handler, `add = (e) => {` I added, `this.redirect.current.click();` . I am grateful to you for making me to think. I will follow your guidance. I am going to learn and do the HOC as well, to see how it behaves and gives the same outcome. Thank you a lot! – Common Man Aug 26 '22 at 07:10
  • 1
    @CommonMan Yeah, trying to use the React ref like that to simulate a click is a bit messy. I'd certainly push back if that was in a pull request. – Drew Reese Aug 26 '22 at 07:12
  • I have followed your [answer: using HOC](https://stackoverflow.com/questions/69899955/problem-in-redirecting-programmatically-to-a-route-in-react-router-v6/69902006?noredirect=1#comment129791568_69902006) but I got `props, useNavigate, withRouter, navigate` are undefined in compiling errors. I did as, `` and inside constructor, `const withRouter = EditContact => props => { const navigate = useNavigate(); };` finally, inside `update = (e) => {` handler, `this.props.navigate("/");`. What's my mistake? – Common Man Aug 26 '22 at 08:11
  • @CommonMan This seems like something a bit off-topic for *this* question-answer pair, and a little more involved than can be resolved in the comments alone. The best option would be to create an entirely new post on SO for *that* specific issue (*you can reference the other SO answer you were trying to implement*) along with your [mcve] for it. If you do this feel free to ping me here in this thread in a comment with a link to your new post and I can take a look when available. A less ideal thing to do would be to create a *running* codesandbox of your code and link it here, and I can inspect. – Drew Reese Aug 26 '22 at 16:03