I can't make the edit option work, the add option works and the UI is working fine but when i select the option edit it sends me where im going to edit but it doesn't let me write or delete anything.
I can't make the Edit option work, im using a hook called useForm.
import React, { useState } from "react";
export const useForm = (initialState = {}) => {
const [values, setValues] = useState(initialState);
const reset = () => {
setValues(initialState);
};
const handleInputChange = (target) => {
console.log(target.value);
setValues({
...values,
[target.name]: target.value,
});
};
return {
values,
handleInputChange,
reset,
};
};
Then i called it in the file where it was suposed to edit but it doesn't let me to write anything.
import React, { useState, useContext, useEffect } from "react";
import { GlobalContext } from "./context/GlobalState";
import { Link, useNavigate, useParams } from "react-router-dom";
import { useForm } from "./context/useForm";
const EditUser = () => {
const { values, handleInputChange } = useForm({
name: "",
lastName: "",
email: "",
description: "",
location: "",
services: "",
price: "",
schedule: "",
});
const [selectedUser, setSelectedUser] = useState({
name: values.name,
lastName: values.lastName,
email: values.email,
description: values.description,
location: values.location,
services: values.services,
price: values.price,
schedule: values.schedule,
});
const { users, editUser } = useContext(GlobalContext);
const history = useNavigate();
const { id } = useParams();
const currentUserId = id;
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find((user) => user.id === userId);
setSelectedUser(selectedUser);
}, [currentUserId, users]);
const onSubmit = () => {
editUser(selectedUser);
history("/customers");
};
return (
<>
<div
onSubmit={onSubmit}
className="w-screen h-screen flex justify-center items-center"
>
<div className="box-border shadow-md bg-white border-current border-1 h-4/5 w-4/12 p-4 rounded-xl flex justify-center place-items-center flex-col text-black">
<p className="text-lg font-semibold">
Actualiza los datos del usuario
</p>
<div>
<input
type="text"
name="name"
value={selectedUser.name}
// onChange={onChangeName}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Nombre"
required
/>
</div>
<div>
<input
type="text"
name="lastName"
value={selectedUser.lastName}
// onChange={onChangeLastName}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Apellidos"
required
/>
</div>
<div>
<input
type="text"
name="email"
value={selectedUser.email}
// onChange={onChangeEmail}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Correo electronico"
required
/>
</div>
<div>
<input
type="text"
name="description"
value={selectedUser.description}
// onChange={onChangeDescription}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Descripcion"
required
/>
</div>
<div>
<input
type="text"
name="location"
value={selectedUser.location}
// onChange={onChangeLocation}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Ubicacion"
required
/>
</div>
<div>
<input
type="text"
name="services"
value={selectedUser.services}
// onChange={onChangeServices}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Servicios"
required
/>
</div>
<div>
<input
type="text"
name="price"
value={selectedUser.price}
// onChange={onChangePrice}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Precio"
required
/>
</div>
<div>
<input
type="text"
name="schedule"
value={selectedUser.schedule}
// onChange={onChangeEmail}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Horarios"
required
/>
</div>
<button
type="addUser"
className="normal-button"
onClick={() => onSubmit()}
>
Actualizar
</button>
<Link to="/customers">
<button type="cancel" className="normal-button">
Cancelar
</button>
</Link>
</div>
</div>
</>
);
};
export default EditUser;
Related
it updates only the lastly typed input box value in the state and other are undefined
i get this in console
Object { Name: undefined, Age: "123", City: undefined }
second time
Object { Name: undefined, Age: undefined, City: "city" }
Form.jsx
import React, {useState} from 'react';
const Form = (props) => {
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
[e.target.name]: e.target.value,
})
}
const infoSubmit = e =>{
e.preventDefault();
let data={
Name:formData.Name,
Age:formData.Age,
City:formData.City
}
props.myData(data);
}
return (
<div className="">
<form onSubmit={infoSubmit} autoComplete="off">
<div className="form-group mb-6">
<label className="">Name:</label>
<input type="text" onChange={infoChange} name="Name" value={formData.Name} className=""placeholder="Enter Name" />
</div>
<div className="form-group mb-6">
<label className="">City:</label>
<input type="text" onChange={infoChange} name="City" value={formData.City} className=""
placeholder="Enter Age" />
</div>
<button type="submit" className="">Submit</button>
</form>
</div>
);
};
export default Form;
App.jsx
this is App.jsx file, here i get the data prop and display it in console.log
import React from 'react';
import Form from './components/Form';
import Table from './components/Table';
const App = () => {
const create = (data) => {
console.log(data);
}
return (
<div className='flex w-full'>
<div className=''>
<Form myData={create} />
</div>
<div className=''>
<Table />
</div>
</div>
);
};
export default App;
You're stomping the previous state with the most recent change. If you want to preserve the existing state you have to include it in the update.
setFormData({
...formData,
[e.target.name]: e.target.value,
})
with react-hooks you need to set the entire object again.
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
// spread the current values here
...formData,
// update the current changed input
[name]: value,
})
or, even better IMHO. You have one state for each prop
const [name, setName] = useState('');
const [age, setAge] = useState('');
const [city, setCity] = useState('');
// ...
<input onChange={({target: {value}}) => setName(value)} />
<input onChange={({target: {value}}) => setAge(value)} />
<input onChange={({target: {value}}) => setCity(value)} />
Change this
const infoChange = e => {
const { name,value} = e.target;
setFormData({...formData
[e.target.name]: e.target.value,
})
}
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;
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 the button and call stopPropagation on the click event object to prevent it from bubbling up to the Link.
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>
);
};
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.
In the User model I have set avatar to be not required
avatar: {
public_id: {
type: String,
},
url: {
type: String,
},
},
but still if I register without image it shows error POST http://localhost:3000/api/v1/register 500 (Internal Server Error) and REGISTER_USER_FAIL constant state is returned
Here is my UserController.jsx, Register Route
exports.registerUser = catchAsyncErrors(async (req, res, next) => {
const myCloud = await cloudinary.v2.uploader.upload(req.body.avatar, {
folder: "avatars",
width: 150,
crop: "scale",
});
const { name, email, password } = req.body;
if(req.body.avatar){
const user = await User.create({
name,
email,
password,
avatar: {
public_id: myCloud.public_id,
url: myCloud.secure_url,
},
});
sendToken(user, 201, res);
} else {
const user = await User.create({
name,
email,
password
});
sendToken(user, 201, res);
}
});
SignUp Component
import React, {Fragment,useRef,useState, useEffect} from "react";
import "./LoginSignUp.css";
import Loader from "../layout/Loader/Loader";
import { Link, useNavigate } from "react-router-dom";
import LockOpenIcon from '#mui/icons-material/LockOpen';
import EmailIcon from '#mui/icons-material/Email';
import PersonIcon from '#mui/icons-material/Person';
import img from "../../images/Profile.png";
import {useSelector, useDispatch} from "react-redux";
import {clearErrors, login , register} from "../../actions/userAction.jsx";
import {useAlert} from "react-alert";
export default function LoginSignUp() {
const dispatch = useDispatch();
const alert = useAlert();
const navigate = useNavigate();
const { error, loading, isAuthenticated } = useSelector(
(state) => state.user
);
const loginTab = useRef(null);
const registerTab = useRef(null);
const switcherTab = useRef(null);
const [loginEmail, setLoginEmail] = useState("");
const [loginPassword, setLoginPassword] = useState("");
const [user, setUser] = useState({
name: "",
email: "",
password: "",
});
const { name, email, password } = user;
const [avatar, setAvatar] = useState(img);
const [avatarPreview, setAvatarPreview] = useState(img);
const loginSubmit = (e) => {
e.preventDefault();
dispatch(login(loginEmail, loginPassword));
};
const registerSubmit = (e) => {
e.preventDefault();
const myForm = new FormData();
myForm.set("name", name);
myForm.set("email", email);
myForm.set("password", password);
if(avatar!==img){
myForm.set("avatar", avatar);
}
dispatch(register(myForm));
};
const registerDataChange = (e) => {
if (e.target.name === "avatar") {
const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === 2) {
setAvatarPreview(reader.result);
setAvatar(reader.result);
}
};
if(reader){
reader.readAsDataURL(e.target.files[0]);
}
} else {
setUser({ ...user, [e.target.name]: e.target.value });
}
}
useEffect(() => {
if (error) {
alert.error(error);
dispatch(clearErrors());
}
if (isAuthenticated) {
navigate("/");
}
}, [dispatch, error, alert, navigate, isAuthenticated]);
const switchTabs = (e, tab) => {
if (tab === "login") {
switcherTab.current.classList.add("shiftToNeutral");
switcherTab.current.classList.remove("shiftToRight");
registerTab.current.classList.remove("shiftToNeutralForm");
loginTab.current.classList.remove("shiftToLeft");
}
if (tab === "register") {
switcherTab.current.classList.add("shiftToRight");
switcherTab.current.classList.remove("shiftToNeutral");
registerTab.current.classList.add("shiftToNeutralForm");
loginTab.current.classList.add("shiftToLeft");
}
};
return (
<Fragment>
{loading ? (
<Loader />
) : (
<Fragment>
<div className="LoginSignUpContainer">
<div className="LoginSignUpBox">
<div>
<div className="login_signUp_toggle">
<p onClick={(e) => switchTabs(e, "login")}>LOGIN</p>
<p onClick={(e) => switchTabs(e, "register")}>REGISTER</p>
</div>
<button ref={switcherTab}></button>
</div>
<form className="loginForm" ref={loginTab} onSubmit={loginSubmit}>
<div className="loginEmail">
<EmailIcon />
<input
type="email"
placeholder="Email"
required
value={loginEmail}
onChange={(e) => setLoginEmail(e.target.value)}
/>
</div>
<div className="loginPassword">
<LockOpenIcon />
<input
type="password"
placeholder="Password"
required
value={loginPassword}
onChange={(e) => setLoginPassword(e.target.value)}
/>
</div>
<Link to="/password/forgot">Forget Password ?</Link>
<input type="submit" value="Login" className="loginBtn" />
</form>
<form
className="signUpForm"
ref={registerTab}
encType="multipart/form-data"
onSubmit={registerSubmit}
>
<div className="signUpName">
<PersonIcon />
<input
type="text"
placeholder="Name"
required
name="name"
value={name}
onChange={registerDataChange}
/>
</div>
<div className="signUpEmail">
<EmailIcon />
<input
type="email"
placeholder="Email"
required
name="email"
value={email}
onChange={registerDataChange}
/>
</div>
<div className="signUpPassword">
<LockOpenIcon />
<input
type="password"
placeholder="Password"
required
name="password"
value={password}
onChange={registerDataChange}
/>
</div>
<div id="registerImage">
<img src={avatarPreview} alt="Avatar Preview" />
<input
type="file"
name="avatar"
accept="image/*"
onChange={registerDataChange}
/>
<span>*image size less than 500KB</span>
</div>
<input type="submit" value="Register" className="signUpBtn" />
</form>
</div>
</div>
</Fragment>
)}
</Fragment>
);
};
Register route works fine only if I use image while registering.
When registering without image
// v-- seems you are using avatar before checking if it's in the body
const myCloud = await cloudinary.v2.uploader.upload(req.body.avatar, {
folder: "avatars",
width: 150,
crop: "scale",
});
const { name, email, password } = req.body;
if(req.body.avatar){
When I try to register a new user all data are passed except "avatar", even if that avatar is regularly uploaded on my Cloudinary.
Don't even know what is the problem in and tried to find answer on google but unsuccessfully, so any help is very welcome :D
Here is some code, if need more tell me in a comment and I'll send you
Cannot finish my site, don't know what to do...
// frontend Register component
import React, { Fragment, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import MetaData from '../layout/MetaData';
import { useAlert } from 'react-alert';
import { useDispatch, useSelector } from 'react-redux';
import { register, clearErrors } from '../../actions/userActions';
const Register = () => {
const navigate = useNavigate();
const [user, setUser] = useState({
name: '',
email: '',
password: '',
});
const { name, email, password } = user;
const [avatar, setAvatar] = useState('');
const [avatarPreview, setAvatarPreview] = useState(
'/images/default_avatar.jpg'
);
const alert = useAlert();
const dispatch = useDispatch();
const { isAuthenticated, error, loading } = useSelector(
(state) => state.auth
);
useEffect(() => {
if (isAuthenticated) {
navigate('/');
}
if (error) {
alert.error(error);
dispatch(clearErrors());
}
}, [dispatch, alert, isAuthenticated, error, navigate]);
const submitHandler = (e) => {
e.preventDefault();
const formData = new FormData();
formData.set('name', name);
formData.set('email', email);
formData.set('password', password);
formData.set('avatar', avatar);
dispatch(register(formData));
};
const onChange = (e) => {
if (e.target.name === 'avatar') {
const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === 2) {
setAvatarPreview(reader.result);
setAvatar(reader.result);
}
};
reader.readAsDataURL(e.target.files[0]);
} else {
setUser({ ...user, [e.target.name]: e.target.value });
}
};
return (
<Fragment>
<MetaData title={'Register User'} />
<div className='row wrapper'>
<div className='col-10 col-lg-5'>
<form
className='shadow-lg'
onSubmit={submitHandler}
encType='multipart/form-data'
>
<h1 className='mb-3'>Register</h1>
<div className='form-group'>
<label htmlFor='email_field'>Name</label>
<input
type='name'
id='name_field'
className='form-control'
name='name'
value={name}
onChange={onChange}
/>
</div>
<div className='form-group'>
<label htmlFor='email_field'>Email</label>
<input
type='email'
id='email_field'
className='form-control'
name='email'
value={email}
onChange={onChange}
/>
</div>
<div className='form-group'>
<label htmlFor='password_field'>Password</label>
<input
type='password'
id='password_field'
className='form-control'
name='password'
value={password}
onChange={onChange}
/>
</div>
<div className='form-group'>
<label htmlFor='avatar_upload'>Avatar</label>
<div className='d-flex align-items-center'>
<div>
<figure className='avatar mr-3 item-rtl'>
<img
src={avatarPreview}
className='rounded-circle'
alt='Avatar Preview'
/>
</figure>
</div>
<div className='custom-file'>
<input
type='file'
name='avatar'
className='custom-file-input'
id='customFile'
accept='iamges/*'
onChange={onChange}
/>
<label className='custom-file-label' htmlFor='customFile'>
Choose Avatar
</label>
</div>
</div>
</div>
<button
id='register_button'
type='submit'
className='btn btn-block py-3'
disabled={loading ? true : false}
>
REGISTER
</button>
</form>
</div>
</div>
</Fragment>
);
};
export default Register;
// backend Register a user => /api/v1/register
exports.registerUser = catchAsyncErrors(async (req, res, next) => {
const result = await cloudinary.v2.uploader.upload(req.body.avatar, {
folder: 'avatars',
width: 150,
crop: 'scale',
});
const { name, email, password } = req.body;
const user = await User.create({
name,
email,
password,
avatar: {
public_id: result.public_id,
url: result.secure_url,
},
});
sendToken(user, 200, res);
});
I have a react app (a sort of twitter clone) that uses firestore for storing posts and comments on posts. Each post is rendered as an element from an array using array.map(). Each post has a comment button that opens a form to take in a comment and add it to the post. When I enter a comment and submit it, the topmost post is always the one commented on no matter which post contained the comment button that was clicked(docId for the most recently saved firestore document is always submitted by the comment button instead of the docId corresponding to that instance of the component).
The map of the posts (called "howls"):
<div className="timeline">
{sortedHowls &&
sortedHowls.map((howl) => (
<Howl
key={howl.id}
image={howl.image}
text={howl.text}
time={howl.time}
userId={howl.userId}
docId={howl.id}
comments={howl.comments}
likes={howl.likes}
/>
))}
</div>
The Howl Component looks like this:
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useFirestoreConnect } from "react-redux-firebase";
import { firestore } from "../../../firebase-store";
// styles
import "./Howl.scss";
// components
import Avatar from "../Avatar/Avatar";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
// functions
import timeCalc from "./timeCalc";
// icons
import { faStar, faComment } from "#fortawesome/free-solid-svg-icons";
const Howl = ({ docId, userId, text, image, time, comments, likes }) => {
useFirestoreConnect([{ collection: "users" }]);
const [commenting, toggleCommenting] = useState(false);
const [newComment, setNewComment] = useState("");
const [users, setUsers] = useState(null);
const [user, setUser] = useState(null);
const getUsers = useSelector((state) => state.firestore.ordered.users);
useEffect(() => {
if (!users) {
setUsers(getUsers);
} else {
setUser(users.find((doc) => doc.uid === userId));
}
}, [users, user, userId, getUsers]);
const handleLike = () => {
const newLikesTotal = likes + 1;
firestore.collection("howls").doc(docId).update({ likes: newLikesTotal });
};
const handleComment = () => {
toggleCommenting(!commenting);
};
const handleChange = (event) => {
setNewComment(event.currentTarget.value);
};
const submitComment = (event) => {
event.preventDefault();
const { id } = event.currentTarget;
console.log(event.currentTarget);
const resetComment = () => {
toggleCommenting(!commenting);
setNewComment("");
};
if (comments) {
firestore
.collection("howls")
.doc(id)
.update({
comments: [...comments, newComment],
})
.then(() => resetComment());
} else {
firestore
.collection("howls")
.doc(id)
.update({ comments: [newComment] })
.then(() => resetComment());
}
};
return (
<div className="howl">
<div className="avatar-container">
<Avatar
photoURL={user ? user.photoURL : ""}
displayName={user ? user.displayName : ""}
/>
</div>
<div className="name-text-img-container">
<p className="userName">
{user && user.displayName} - {timeCalc(Date.now(), time)}
</p>
<p className="howl-text">{text}</p>
<div className="img-container">
{image ? (
<img src={image} alt="user uploaded" className="img" />
) : null}
</div>
<div className="buttons-container">
<form action="" className="buttons">
<label htmlFor="comment-button">
<FontAwesomeIcon icon={faComment} className="image-icon" />
</label>
<input
id="comment-button"
type="checkbox"
onClick={handleComment}
style={{ display: "none" }}
/>
<label htmlFor="like-button">
<FontAwesomeIcon icon={faStar} className="image-icon" />
</label>
<input
id="like-button"
type="checkbox"
onClick={handleLike}
style={{ display: "none" }}
/>
<label htmlFor="like-button">{likes > 0 && likes}</label>
</form>
</div>
{commenting && (
<div className="comment-form">
<form action="submit" onSubmit={submitComment} id={docId}>
<input
type="text"
name="comment-input"
className="comment-input"
maxLength={128}
onChange={handleChange}
value={newComment}
placeholder="Enter comment"
/>
<div className="buttons">
<button type="submit">Submit</button>
<button onClick={() => toggleCommenting(!commenting)}>
Cancel
</button>
</div>
</form>
</div>
)}
<div className="comments">
{comments
? comments.map((comment, index) => {
return (
<p key={index} className="comment">
{comment}
</p>
);
})
: null}
</div>
</div>
</div>
);
};
export default Howl;
How can I get the comment button to specify the correct document to update?
Link to my full repo.
It turns out that the problem is here:
<form action="" className="buttons">
<label htmlFor="comment-button">
<FontAwesomeIcon icon={faComment} className="image-icon" />
</label>
<input
id="comment-button"
type="checkbox"
onClick={handleComment}
style={{ display: "none" }}
/>
<label htmlFor="like-button">
<FontAwesomeIcon icon={faStar} className="image-icon" />
</label>
<input
id="like-button"
type="checkbox"
onClick={handleLike}
style={{ display: "none" }}
/>
<label htmlFor="like-button">{likes > 0 && likes}</label>
</form>
By using a form and inputs as the buttons instead of using <button /> elements it somehow confused react as to which instance of <Howl /> was opening the comment form and therefore which docId was sent to submitComment. Corrected <Howl /> component:
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useFirestoreConnect } from "react-redux-firebase";
import { firestore } from "../../../firebase-store";
// components
import Avatar from "../Avatar/Avatar";
import CommentInput from "./CommentInput";
import Comment from "./Comment";
import ViewProfile from "../ViewProfile/ViewProfile";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
// functions
import timeCalc from "./timeCalc";
// styles
import "./Howl.scss";
// icons
import { faStar, faComment } from "#fortawesome/free-solid-svg-icons";
const Howl = ({ howl }) => {
useFirestoreConnect([{ collection: "users" }]);
const [commenting, toggleCommenting] = useState(false);
const [newComment, setNewComment] = useState("");
const [users, setUsers] = useState(null);
const [op, setOp] = useState(null);
const [showProfile, setShowProfile] = useState(false);
const { docId, userId, text, likes, comments, time, image } = howl;
const getUsers = useSelector((state) => state.firestore.ordered.users);
const currentUser = useSelector((state) => state.firebase.auth);
// establish user that posted this howl (op = original poster)
useEffect(() => {
users ? setOp(users.find((doc) => doc.uid === userId)) : setUsers(getUsers);
}, [users, op, userId, getUsers]);
const handleLike = () => {
const index = likes.indexOf(currentUser.uid);
let newLikes = [...likes];
if (index > 0) {
newLikes.splice(index, 1);
} else if (index === 0) {
if (likes.length > 1) {
newLikes.splice(index, 1);
} else {
newLikes = [];
}
} else {
newLikes = [...newLikes, currentUser.uid];
}
firestore.collection("howls").doc(docId).update({ likes: newLikes });
};
const handleChange = (event) => {
setNewComment(event.currentTarget.value);
};
const submitComment = (event) => {
event.preventDefault();
const { id } = event.currentTarget;
const { uid, photoURL } = currentUser;
const resetComment = () => {
toggleCommenting(!commenting);
setNewComment("");
};
firestore
.collection("howls")
.doc(id)
.update({
comments: [
...comments,
{ uid: uid, photoURL: photoURL, text: newComment },
],
})
.then(() => resetComment());
};
return (
<div className="howl">
<div className="avatar-container">
<button className="show-profile" onClick={() => setShowProfile(true)}>
<Avatar
photoURL={op ? op.photoURL : ""}
displayName={op ? op.displayName : ""}
/>
</button>
</div>
<div className="name-text-img-container">
<p className="userName">
{op && op.displayName} - {timeCalc(Date.now(), time)}
</p>
<p className="howl-text">{text}</p>
<div className="img-container">
{image && <img src={image} alt="user uploaded" className="img" />}
</div>
<div className="buttons-container">
<div className="buttons">
<button className="comment-button">
<FontAwesomeIcon
icon={faComment}
className="image-icon"
onClick={() => toggleCommenting(!commenting)}
/>
</button>
<button className="like-button" onClick={handleLike}>
<FontAwesomeIcon
icon={faStar}
className={
currentUser && likes.includes(currentUser.uid)
? "image-icon liked"
: "image-icon"
}
/>
</button>
<p>{likes.length > 0 && likes.length}</p>
</div>
</div>
{commenting && (
<CommentInput
submitComment={submitComment}
docId={docId}
toggleCommenting={toggleCommenting}
commenting={commenting}
handleChange={handleChange}
newComment={newComment}
/>
)}
{showProfile && (
<ViewProfile
user={op}
close={() => setShowProfile(false)}
update={false}
/>
)}
<div className="comments">
{comments &&
comments.map((comment, index) => {
return <Comment key={`comment${index}`} comment={comment} />;
})}
</div>
</div>
</div>
);
};
export default Howl;