React JS: A component is changing a controlled input to be uncontrolled - javascript

My code
UserEdit.jsx
import useForm from "../../utils/useForm";
import LoadingBtn from "../../utils/loadingButton";
import { getUser } from "../../store/users/userActions";
const UserEdit = () => {
//declare form data and scmena
const formInput = {
name: "",
};
const schema = {
name: Joi.string().required().min(3).max(191).label("Name"),
};
//dispatch on first mount
const dispatch = useDispatch();
const params = useParams();
useEffect(() => {
setErrors({});
dispatch(getUser(params.id));
}, []);
const {
handleChange,
handleSubmit,
formData,
setFormData,
errors,
setErrors,
} = useForm(formInput, doSubmit, schema);
function doSubmit() {
console.log("handle Submit", formData);
}
const isSubmitting = useSelector((state) => state.users.isSubmitting);
const user = useSelector((state) => state.users.user);
useEffect(() => {
console.log("useEffect user >>", user);
setFormData({
...formData,
name: user.name,
});
}, [user]);
return (
<input
className={`form-control ${
errors["name"]
? "is-invalid"
: ""
}`}
type="text"
id="name"
name="name"
required=""
placeholder="Enter your name"
onChange={handleChange}
value={formData.name}
/>
);
};
export default UserEdit;
UseForm.jsx
const useForm = (formInput, callback, schema = {}) => {
const [formData, setFormData] = useState(formInput);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setFormData({
...formData,
[name]: value,
});
};
const handleSubmit = (e) => {
e.preventDefault();
//handle error
setErrors(validate(formData));
setIsSubmitting(true);
};
const validate = (formData) => {
const { error } = Joi.validate(formData, schema, {
abortEarly: false,
});
if (!error) return {};
const validataionErrors = {};
for (let item of error.details) {
validataionErrors[item.path[0]] = item.message;
}
return validataionErrors;
};
useEffect(() => {
//check if there are any errors
if (Object.keys(errors).length === 0 && isSubmitting) {
callback();
setIsSubmitting(true);
}
}, [errors]);
return {
handleChange,
handleSubmit,
formData,
setFormData,
errors,
setErrors,
};
};
export default useForm;
I googled about the error and it mentioned that state need to be initialized at first with the field. However, I have already defined initial state as
const formInput = {
name: "",
};
I could not find how could I fix this, I am open to restructure the useForm hooks if that is the one which causing trouble.
currently, if I uncomment the following line on userEdit.jsx, the warning will be gone, but also the edit form becomes empty as well
setFormData({
...formData,
name: user.name,
});

Related

Connecting Term to a Redux dispatch Action

I have created an input field with a search term which creates a request to a backend API. To summarise, two issues:
It fetches data from my API, but it fetches ALL roles, not just ones filtered by the term.
It does not commit to the redux store.
Please see my app, it contains simply:
This is my frontend component, which is making an action dispatch based on a search term.
export function SearchBarTrialRedux(props) {
const [isExpanded, setExpanded] = useState(false);
const [parentRef, isClickedOutside ] = useClickOutside();
const inputRef = useRef();
const [searchQuery, setSearchQuery] = useState("");
const [isLoading, setLoading] = useState(false);
const [jobPostings, setjobPostings] = useState([]);
const [noRoles, setNoRoles] = useState(false)
const isEmpty = !jobPostings || jobPostings.length === 0;
const expandedContainer = () => {
setExpanded(true);
}
const collapseContainer = () => {
setExpanded(false);
setSearchQuery("");
setLoading(false);
setNoRoles(false);
if (inputRef.current) inputRef.current.value = "";
};
useEffect(()=> {
if(isClickedOutside)
collapseContainer();
}, [isClickedOutside])
const [term, setTerm] = useState("")
const dispatch = useDispatch();
const changeHandler = (e) => {
e.preventDefault();
fetchAsyncRoles(dispatch, {term});
}
return (
<SearchBarContainer animate = {isExpanded ? "expanded" : "collapsed"}
variants={containerVariants} transition={containerTransition} ref={parentRef}>
<SearchInputContainer>
<SearchIconSpan>
<SearchIcon/>
</SearchIconSpan>
<form onSubmit={changeHandler}>
<SearchInput placeholder = "Search for Roles"
onFocus={expandedContainer}
ref={inputRef}
value={term}
onChange={(e)=> setTerm(e.target.value)}
/>
</form>
</SearchBarContainer>
And my jobsearchSlice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { publicRequest } from "../requestMethods";
export const fetchAsyncRoles = async (dispatch, term) => {
dispatch(searchStart());
try {
const res = await publicRequest.get(`http://localhost:5000/api/role/titlerole?title=${term}`);
dispatch(searchSuccess(res.data));
console.log(res.data)
} catch (err) {
dispatch(searchFailure());
}
};
const jobsearchSlice = createSlice({
name: "jobsearchSlice",
initialState: {
isFetching: false,
roles: [],
error: false,
},
reducers: {
searchStart: (state) => {
state.isFetching = true;
},
searchSuccess: (state, action) => {
state.isFetching = false;
state.roles = action.payload;
},
searchFailure: (state) => {
state.isFetching = false;
state.error = true;
},
},
});
export const { searchStart, searchSuccess, searchFailure } = jobsearchSlice.actions;
export default jobsearchSlice.reducer;
As stated, it does create and fetch this data. This does commit it to the store under the roles key, which is great! That's what I want, however it is not filtering. E.g If we look at a role specifically like Data Scientist:
https://gyazo.com/ca4c2b142771edd060a7563b4200adf8
I should be getting just 1 key, Data Scientist.
Looking at the backend of the console.log(res), I can see that it appears my term isn't properly coming through and filtering my roles :
responseURL: "http://localhost:5000/api/role/titlerole?title=[object%20Object]"
But if I log the term, it does come through exactly as input.
What's wrong, what am I doing and how should I solve this term flowing through to filter my req?
I can confirm that if I do this on postman it works...
https://gyazo.com/10f2946c1a3807370b4792c06292b557

React js show notification after redirecting to the new route

So I have two form components and one notification component in a react project. When submitting the first form, the route is redirected to the second form's route, where the notification that the action of the first form was done successfully must appear. How do I achieve that using my current components.
My AddEmployee.js form (where the action takes place)
const AddEmployee = () => {
const { id } = useParams();
let history = useHistory();
const [notify, setNotify] = useState({ isOpen: false, message: '', type: '' })
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
async function handleUpdate() {
let item = {
firstName: firstName,
lastName: lastName,
}
//Update API Call Result
if (result.httpStatus === 200) {
history.push("/user/" + employeeId); // The route of the second form
setNotify({
isOpen: true,
message: 'Added Successfully',
type: 'success'
})
}
}
return (
<>
<input onChange={e => setLastName(e.target.value)} name="lastName" type="text" />
<input onChange={e => setFirstName(e.target.value)} name="firstName" type="text" />
</>
);
}
export default AddEmployee;
My SingleEmployee.js form (where the notification must appear)
const SingleEmployee = () => {
const { id } = useParams();
const [notify, setNotify] = useState({ isOpen: false, message: '', type: '' })
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
async function getSingleUser() {
//API to get user Info
if (result.httpStatus === 200) {
setFirstName(result.result[0].firstName);
setLastName(result.result[0].lastName);
}
}
useEffect(() => {
getSingleUser();
}, []);
return (
<>
<label>{firstName}</label><br />
<label>{lastName}</label>
<Notification
notify={notify}
setNotify={setNotify}
/>
</>
);
}
export default SingleEmployee;
And finally, my notification.js
export default function Notification(props) {
const { notify, setNotify } = props;
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setNotify({
...notify,
isOpen: false
})
}
return (
<Snackbar
open={notify.isOpen}
autoHideDuration={2000}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
onClose={handleClose}>
<Alert
severity={notify.type}
onClose={handleClose}>
{notify.message}
</Alert>
</Snackbar>
)
}
So the process is when I add a new employee, I should be redirected to the SingleEemployee page, where the notification that says created successfully should appear but only after the page is redirected. How to do that?
In your App.js, you can detect route change, maybe u can make use of this to trigger notification
import React from 'react';
import { useLocation, Switch } from 'react-router-dom';
const App = () => {
const location = useLocation();
React.useEffect(() => {
console.log('route changed');
}, [location]);
return (
<Switch>
{/* Routes go here */}
</Switch>
);
};
You need to set the notification state in SingleEmployee.js file not in the addEmployee file.
We need to pass a state parameter when we are pushing in history.
In AddEmployee.js
const AddEmployee = () => {
const { id } = useParams();
let history = useHistory();
const [notify, setNotify] = useState({ isOpen: false, message: '', type: '' })
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
async function handleUpdate() {
let item = {
firstName: firstName,
lastName: lastName,
}
//Update API Call Result
if (result.httpStatus === 200) {
history.push("/user/" + employeeId, {fromCreate: true}); // The route of the second form
}
}
return (
<>
<input onChange={e => setLastName(e.target.value)} name="lastName" type="text" />
<input onChange={e => setFirstName(e.target.value)} name="firstName" type="text" />
</>
);
}
export default AddEmployee;
const SingleEmployee = () => {
const { id } = useParams();
const location = useLocation();
const [notify, setNotify] = useState({ isOpen: false, message: '', type: '' })
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
async function getSingleUser() {
//API to get user Info
if (result.httpStatus === 200) {
setFirstName(result.result[0].firstName);
setLastName(result.result[0].lastName);
if(location.state && location.state.fromCreate){
setNotify({
isOpen: true,
message: 'Added Successfully',
type: 'success'
})}
}
}
useEffect(() => {
getSingleUser();
}, []);
return (
<>
<label>{firstName}</label><br />
<label>{lastName}</label>
{ notify.isOpen && <Notification
notify={notify}
setNotify={setNotify}
/> }
</>
);
}
export default SingleEmployee;

Object property is undefined when i try to show it or console.log - React & Hooks

I'm a little new to react and i can't understand why my object property is undefined but when i console.log my object is appearing okay see this screenshot:
This is my custom hook useForm:
const useForm = (callback, validateRegister) => {
const [values, setValues] = useState({
name: '',
email: '',
password: '',
confirmPass: '',
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setValues({
...values,
[name]: value,
});
};
const handleSubmit = (event) => {
event.preventDefault();
setErrors(validateRegister(values)); // validateReister is another function that returns and object with these properties.
setIsSubmitting(true);
};
useEffect(() => {
if (Object.keys(errors).length === 0 && isSubmitting) {
callback();
}
}, [errors]);
return {
handleChange,
handleSubmit,
values,
errors,
};
};
export default useForm;
Component:
const { handleChange, handleSubmit, values, errors } = useForm(
submit,
validateRegister
);
Problem:
{errors.nameError}
Is not showing up, is not appearing on console.log either. Any idea?
I think your validateRegister(values) returns a Promise. Try changing your implementation to the below :-
const handleSubmit = (event) => {
event.preventDefault();
validateRegister(values).then(data => setErrors(data)).catch(err => console.log(err));
setIsSubmitting(true);
};
Replace setErrors(validateRegister(values)); with
validateRegister(values).then(data => setErrors(data)).catch(e => console.log(e));

EmailJs and Form Validation problem, React

I've got a problem with sending emails in React by EmailJs. When i validate form and all the errors desapears, form is sending email only after second click and i dont really know why this is happening why. Please help
const useForm = (callback, validate) => {
const [values, setValues] = useState({
title: "",
email: "",
message: "",
});
const [errors, setErrors] = useState({});
const [send, setSend] = useState(false);
const [isSubmiting, setIsSubmiting] = useState(false);
useEffect(() => {
if (Object.keys(errors).length === 0) {
if (isSubmiting) {
setSend(true);
}
}
}, [errors]);
const handleChange = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
};
const handleSubmit = (e) => {
e.preventDefault();
setErrors(validate(values));
setIsSubmiting(true);
if (send) {
emailjs
.sendForm(
"service",
"templatekey",
e.target,
"userkey"
)
.then(
(result) => {
console.log(result.text);
},
(error) => {
console.log(error.text);
}
);
e.target.reset();
}
};
return { handleChange, values, handleSubmit, errors };
};
export default useForm;
After moving setErrors(validate(values)) and setIsSubmiting(true) to handleChange it works fine for me :)
const handleChange = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
setErrors(validate(values));
setIsSubmiting(true);
};
const handleSubmit = (e) => {
e.preventDefault();
if (send) {
console.log("WYSYƁAM");
emailjs
.sendForm(
"service",
"template",
e.target,
"user"
)
.then(
(result) => {
console.log(result.text);
},
(error) => {
console.log(error.text);
}
);
e.target.reset();
}
};
return { handleChange, values, handleSubmit, errors };
};
I ran into the same problem and found that adding an 'onClick' event handler to your form button will prevent the user from needing to double click.
<button onClick={handleClick}> Submit </button>
in useForm.js I moved setErrors(validate(values)); and setIsSubmitting(true); into handleClick
function handleClick() {
setErrors(validate(values));
setIsSubmitting(true);
};
const handleSubmit = e => {
e.preventDefault();
if (send) {
emailjs.sendForm('service', 'templateKey', e.target, 'userKey')
.then((result) => {
console.log(result.text);
}, (error) => {
console.log(error.text);
});
callback();
}
};

How can i sent limited fields while updating form in react

user.js
import React, { useState, useEffect } from 'react';
const Users = () => {
const [user, setUser] = useState({
name: '',
email: '',
phoneNumber: '',
role: '',
status: ''
}); ======>>>> I want to sent above fields only however while fetching user it fetchs extra fields that I don't want to send while handle submit
const { name, email, phoneNumber, role, status } = user;
const handleChange = e => {
setUser({ ...user, [e.target.name]: e.target.value });
};
const loadUser = async () => {
const res = await Axios.get(`http://localhost:3001/user/fetch/${id}`)
setUser(res.data.response.info)
};
useEffect(() => {
loadUser();
}, []);
const handleSubmit = async e => {
e.preventDefault();
await Axios.patch(`http://localhost:3001/user/${id}`, user)
}; ====>>> I want to sent limited user field here
return (
<form onSubmit={e => handleSubmit(e)} >
<div className="form-group">
<label htmlFor='name'>Name:</label>
<input type="text" name="name" id="name" className="form-control" value={name || ''} onChange={e => handleChange(e)} />
</div>
)
}
I am updating user it is working fine but what i get error it is fetching extra field in user state how can i handle limited fields to sent in handle submit while submitting form like I don't want extra fields to sent to backend. I want only fields which is in state
Why don't you set the fields what you want to set and ignore the rest.
const loadUser = async () => {
const res = await Axios.get(`http://localhost:3001/user/fetch/${id}`);
const _user = res.data.response.info;
setUser({
email: _user.email,
//// rest,
})
};
Or else just allowKeys which you want before patching
function allowKeys(obj, keysAllowed) {
const newObj = {}
for (const key in obj) {
if (keysAllowed.includes[key]) {
newObj[key] = obj[key]
}
}
return newObj
}
// at the time of patching
const toBePatchedUser = allowKeys(user, ["email"... /** only allowed keys here */])

Categories