So I am doing a form validation check and I have taken 'formErrors' and set the errors in this object. However it is initially {} and in my code I am checking for Object.keys(formErrors).length===0 which returns true for even {}
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const url = '/collectdetails';
const handleSubmit = (e) => {
e.preventDefault();
setFormErrors(validate(formValues));
// setIsSubmit(true);
console.log(noErrors);
if (noErrors) {
const { fullName, phoneNumber, emailAddress, role, lookingFor, company } =
formValues;
const data = {
Name: fullName,
MobileNumber: phoneNumber,
Email: emailAddress,
Role: role,
LookingFor: lookingFor,
CompanyName: company,
};
getDetails(url, data).then((user) => {
const { Response } = user;
if (Response === 'OK') {
setCurrentUser(phoneNumber);
navigate('/');
}
});
}
};
useEffect(() => {
if (Object.keys(formErrors).length === 0) {
console.log(formErrors);
setNoErrors(true);
}
}, [formErrors]);
So When I submit the handleSubmit() method is run and it has 2 nested checks. The first one is for noErrors which is a bool state that checks if my object is empty. I have console logged it and it returns true when the component loads as the object is {} in the beginning. Is there any way for me to put a check so that I can see if there are some keys present in the object?
useEffect will run every time your formErrors object changes. This includes the first render.
It would probably be better for you to put your useEffect logic inside your submit handler. The formErrors state object just seems to function as a temporary store for you as you immediately call setNoErrors(true) if it is populated:
const [formValues, setFormValues] = useState(initialValues);
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const url = '/collectdetails';
const handleSubmit = (e) => {
e.preventDefault();
// just store in a normal variable
const errors = validate(formValues);
// setIsSubmit(true);
console.log(noErrors); // this isn't defined in your code
// just check the errors object for keys
if (Object.keys(errors).length === 0) {
// errors object is empty
console.log(errors);
setNoErrors(true);
const { fullName, phoneNumber, emailAddress, role, lookingFor, company }
= formValues;
const data = {
Name: fullName,
MobileNumber: phoneNumber,
Email: emailAddress,
Role: role,
LookingFor: lookingFor,
CompanyName: company,
};
getDetails(url, data).then((user) => {
const { Response } = user;
if (Response === 'OK') {
setCurrentUser(phoneNumber);
navigate('/');
}
});
}
};
Related
I'm trying to keep session stayed logged in after refreshing the browser. The user data that is being fetched is not rendering after being fetched. The console is saying "Cannot read properties of undefined (reading 'user'). This is my code for the login/sign up page.
The data I'm trying to access is in the picture below:
(Auth.js)
const Auth = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const [isSignup, setIsSignup] = useState(false);
const [inputs, setInputs] = useState({
name: "",
username: "",
email: "",
password: ""
})
const handleChange = (e) => {
setInputs(prevState => {
return {
...prevState,
[e.target.name]: e.target.value
}
})
}
const sendRequest = async (type = '') => {
const res = await axios.post(`/user/${type}`, {
name: inputs.name,
email: inputs.email,
username: inputs.username,
password: inputs.password,
}).catch(error => console.log(error))
const data = await res.data;
console.log(data)
return data;
}
const handleSubmit = (e) => {
e.preventDefault()
console.log(inputs)
if (isSignup) {
sendRequest("signup")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
} else {
sendRequest("login")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
}
}
Redux store file
const authSlice = createSlice({
name: "auth",
initialState: { isLoggedIn: false },
reducers: {
login(state) {
state.isLoggedIn = true
},
logout(state) {
state.isLoggedIn = false
}
}
})
export const authActions = authSlice.actions
export const store = configureStore({
reducer: authSlice.reducer
})
Chaining promises using .then() passes the resolved value from one to the next. With this code...
sendRequest("...")
.then(() => dispatch(authActions.login()))
.then(() => navigate("/posts"))
.then(data => localStorage.setItem('token', data.user))
You're passing the returned / resolved value from navigate("/posts") to the next .then() callback. The navigate() function returns void therefore data will be undefined.
Also, your redux action doesn't return the user so you can't chain from that either.
To access the user data, you need to return it from sendRequest()...
const sendRequest = async (type = "") => {
try {
const { data } = await axios.post(`/user/${type}`, { ...inputs });
console.log("sendRequest", type, data);
return data;
} catch (err) {
console.error("sendRequest", type, err.toJSON());
throw new Error(`sendRequest(${type}) failed`);
}
};
After that, all you really need is this...
sendRequest("...")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
Since you're using redux, I would highly recommend moving the localStorage part out of your component and into your store as a side-effect.
I have the empty field ModifiedBy. I need to populate it with a username stored in session storage, userInfo.name.
const [details, setDetails] = useState("");
const handleCreateData = (e) => {
setDetails((prev) => {
return { ...prev, ModifiedBy: userInfo.name };
});
}
This method only works when ModifiedBy is already populated in data. I need to be able to populate ModifiedBy when it is empty, and update it is populated.
A quick example of my JSON
{
"ModifiedBy": "Ciaran Crowley"
}
const handleCreateData = (e) => {
setDetails((prev) => {
prev.ModifiedBy = userInfo.name || ''
return prev;
});
}
Basically, before uploading an image to the firebase, I'm trying to control the input as:
export const controlThumbnail = (selectedThumbnail, setThumbnailError) => {
if (!selectedThumbnail) {
setThumbnailError('Please select a thumbnail!');
return;
}
if (!selectedThumbnail.type.includes('image')) {
setThumbnailError('Please select an image!');
return;
}
if (selectedThumbnail.size > 1000000) {
setThumbnailError('Image size must be less than 1MB!');
return;
}
setThumbnailError(null);
};
which I call the above method from /lib/controlThumbnail.js to:
import { controlThumbnail } from '../../lib/controlThumbnail';
const Signup = () => {
const [userInfo, setUserInfo] = useState({
name: '',
email: '',
password: '',
thumbnail: null
});
const [thumbnailError, setThumbnailError] = useState(null);
const userInputHandler = (e) => {
setUserInfo((prevUserInfo) => {
if (e.target.name === 'thumbnail') {
const thumbnail = e.target.files[0];
controlThumbnail(thumbnail, setThumbnailError);
return { ...prevUserInfo, thumbnail };
} else {
return { ...prevUserInfo, [e.target.name]: e.target.value };
}
});
};
...
so, this is now works correctly, but I wonder if this is the good way of doing it? Or should I put the control method inside the component and never give setState as parameter?
It is subjective. Personally, I think the controlThumbnail function is not the right place to make that abstraction. In here, you are really only providing validation. You don't need to give it the responsibility to validate AND set some state.
You could create a pure validation function, and just use the return of this to update the state in your Signup component:
const validateThumbnail = (thumbnail) => {
if (!thumbnail) {
return 'Please select a thumbnail!';
}
if (!thumbnail.type.includes('image')) {
return 'Please select an image!'
}
if (thumbnail.size > 1000000) {
return 'Image size must be less than 1MB!'
}
return null
}
const Signup = () => {
const [userInfo, setUserInfo] = useState({
name: '',
email: '',
password: '',
thumbnail: null
});
const [thumbnailError, setThumbnailError] = useState(null);
const userInputHandler = (e) => {
setUserInfo((prevUserInfo) => {
if (e.target.name === 'thumbnail') {
const thumbnail = e.target.files[0];
setThumbnailError(validateThumbnail(thumbnail));
return { ...prevUserInfo, thumbnail };
}
return { ...prevUserInfo, [e.target.name]: e.target.value };
});
}
}
I have a custom hook:
import React, { useState, useEffect } from 'react';
import { Alert } from 'react-native';
import firebase from "../../firebase";
export default function useCurrentUserDetails() {
const uid = firebase.auth().currentUser?.uid;
const [data, setData] = useState(null);
useEffect(() => {
if (!uid) {
setData(null);
return;
}
const unsubscribe = firebase.firestore().collection("users").doc(uid).onSnapshot((snapshot) => {
if (snapshot.exists) {
setData(snapshot.data());
} else {
setData(null);
}
})
return unsubscribe;
}, [uid]);
const updateCurrentUserData = (newData) =>
firebase.firestore().collection("users").doc(uid).set(newData).then(() => {
Alert.alert(
'Profile Updated',
'Nice work!',
[
{ text: 'OK' }
],
{ cancelable: false }
);
}).catch((error) => {
Alert.alert(
'Something went wrong',
'Give it another go',
[
{ text: 'OK' }
],
{ cancelable: false }
);
});
return [data, updateCurrentUserData];
}
In my view im calling the following:
const [currentUserDetails, setCurrentUserDetails] = useCurrentUserDetails();
And on a button press, the values are updated:
const handleSubmit = async () => {
setLoading(true)
const url = await uploadImage(profileURL, firebase.auth().currentUser?.uid)
await setCurrentUserDetails(
{ ...currentUserDetails,
username,
age: Number(age),
height,
country,
occupation,
profileURL: url,
}
)
setLoading(false)
}
The issue im having is that for the first time the user try's to edit the data, it won't update unless I have all the fields present. How could I make each value optional?
For example lets say the person only updated the occupation it will update it and leave the rest and not create any fields for this in the db.
I don't know if how I did this is a correct approach.
y is destructured into separate fields.
const updateUserProfile = (y) => {
const { Age, Hobby, UserName, Job, Country, Name } = y;
return firestore.collection("users").doc(`${currentUser.uid}`).update({
UserName: UserName,
Hobby: Hobby,
Name: Name,
Age: Age,
Country: Country,
Job: Job,
});
};
This way I can update any field I want. But in my case all of these fields already have
default data written, like UserName : UserName, Hobby: hobby. And then I update it with what user changed.
This is how I get "y"
userData is what I get from firestore.
const [data, setData] = useState({ [name]: value });
const x = userData;
const y = Object.assign(x, data);
Sorry if that is a mess of a answer.
I want to display alert message if user type name into input field and checks if the value is same as the object value present in the array of object.
I'm using ReactJS. Here's my fake state :
const [persons, setPersons] = useState([
{ name: "Steve" },
{ name: "Tim" },
{ name: "Dan" },]);
const [newName, setNewName] = useState(""); //this state is for input value.
Form onSubmit is :
const handleSubmit = (e) => {
e.preventDefault();
const newPerson = {
name: newName,
};
setPersons(persons.concat(newPerson));
setNewName("");
If I'm understanding correctly, on submit, you want to see if the new name is already taken? You can use .some to see if that object already exists
const handleSubmit = (e) => {
e.preventDefault();
// will return true once it finds a matching entry, otherwise will return false
const exists = persons.some(person => person.name === newName);
if (exists) {
// code to show alert, probably something like `setError(true)`
} else {
const newPerson = { name: newName };
// probably want to clear the error, like `setError(false)`
setPersons(persons.concat(newPerson));
setNewName("");
}
}