I have a situation where I tried to fetch data when user login , and my structure is I have two redux slice one is userData other one is UserCartData and
when user login ,if login success ,
then I will dispatch data into UserData ,
3, then I write a useEffect to check if there's userData ,
I will then fetch UserCartData with UserData
But the thing is I can't get useGetxxxQuery work under useEffect,here's my code
const Login = () => {
const user = useAppSelector(state=> state.auth);
const dispatch = useAppDispatch();
const [login] = useLoginMutation();
const [showPassword,setShowPassword] = useState<boolean>(false);
useEffect(() => {
if (user!==null){ //fecth userCart data with userData
const {data ,isLoading,isFetching }= useGetCartByIDQuery(user.user?._id!)
}
}, [dispatch])
return (
<Container>
<Wrapper>
<Title>SIGN IN</Title>
<Formik
initialValues={{ email: "", password: "" }}
validationSchema={Yup.object({
password: Yup.string()
.min(8, 'Must be 8 characters or higher')
.required(),
email: Yup.string().email('Invalid email address').required(),
})}
onSubmit = { async (values, actions) => {
try{
const result = await login(values);
if("data" in result){
//console.log(result.data.data)
dispatch(setCredentials({user:result.data.data.findUser,token:result.data.data.cookie}))
}else{
const err = (result.error as RequestError).data.message
if(err.includes("password")){
actions.setErrors({password:err})
}else if(err.includes("Facebook")){
actions.setErrors({email:err})
}
}
}catch(err){
console.log(err)
}
}}>
//....unrelevant code
You don't need a useEffect here, you can simply use skipToken:
import {skipToken} from '#reduxjs/toolkit/query'
const {data, isLoading, isFetching } = useGetCartByIDQuery(user.user ? user.user._id : skipToken)
You can not use hooks inside other hooks. It is a rule!!! Source: https://reactjs.org/docs/hooks-rules.html Instead of this you can change useGetCartByIDQuery hook and use only a function from there: ex: const { startFetch } = useGetCartByIDQuery
useEffect(() => {
startFetch('your arguments')
}, [])
Related
The scenario is forgotpassword.I have done everything from my backend. The only problem is React.I have manage to read header token.And I would like to send token and new password to my back-end with api. As you see down below I can send password easly. But I dont know how send token from URLSearchParams. Is there any usefull idea for that?
const initialValues = {
password: '',
changepassword: '',
}
export function PasswordConfirm() {
const [loading, setLoading] = useState(false)
const [hasErrors, setHasErrors] = useState<boolean | undefined>(undefined)
const {search} = useLocation()
useEffect(() => {
const query = new URLSearchParams(search)
const token = query.get('token')
console.log(token)
},)
const formik = useFormik({
initialValues,
validationSchema: PasswordConfirmSchema,
onSubmit: (values, {setStatus, setSubmitting}) => {
setLoading(true)
setHasErrors(undefined)
setTimeout(() => {
requestPasswordConfirm(values.changepassword,values.password)
.then(({data: {result}}) => {
setHasErrors(false)
setLoading(false)
})
.catch(() => {
setHasErrors(true)
setLoading(false)
setSubmitting(false)
setStatus('The login detail is incorrect')
})
}, 1000)
},
})
export function requestPasswordConfirm(token:string,password:string) {
return axios.post<{result: boolean}>(REQUEST_PASSWORD_URL, {
token,
password
})
}
I am using React Firebase hook to log in to my website. when trying to log in with the wrong email or password in the login form, an error message will be returned from the React firebase hook. But even after giving the wrong input, an error message is not returning
const Login = () => {
const [signInWithEmailAndPassword, error] =
useSignInWithEmailAndPassword(auth);
const location = useLocation();
const navigate = useNavigate();
const from = location?.state?.from?.pathname || '/';
if (error) {
return (
<div>
<p>Error: {error.message}</p>
</div>
);
}
const handleLogIn = (e) => {
e.preventDefault();
const email = e.target.email.value;
const password = e.target.password.value;
signInWithEmailAndPassword(email, password)
e.target.reset();
navigate(from, { replace: true })
}
You are using signInWithEmailAndPassword hook incorrectly.
signInWithEmailAndPassword returns an array & 3th index is of error message.
You can follow this: https://github.com/CSFrequency/react-firebase-hooks/blob/master/auth/README.md#usesigninwithemailandpassword
const [
signInWithEmailAndPassword,
user,
loading,
error,
] = useSignInWithEmailAndPassword(auth);
Since, useSignInWithEmailAndPassword returns an Array, We need to extract/destructure the value from respective index.
Apart from that, You must also use loading to display whether firebase is still authorizing the request or not (Loading State).
The signInWithEmailAndPassword appears to be an async function and your code isn't waiting for the returned Promise to resolve. I'm guessing you are seeing the navigate("/"); called and the app is navigating to the home page.
const handleLogIn = (e) => {
e.preventDefault();
const email = e.target.email.value;
const password = e.target.password.value;
signInWithEmailAndPassword(email, password); // <-- no waiting for promise
e.target.reset();
navigate(from, { replace: true }); // <-- navigate away
};
useSignInWithEmailAndPassword
export default (auth: Auth): EmailAndPasswordActionHook => {
const [error, setError] = useState<AuthError>();
const [loggedInUser, setLoggedInUser] = useState<UserCredential>();
const [loading, setLoading] = useState<boolean>(false);
const signInWithEmailAndPassword = async (
email: string,
password: string
) => {
setLoading(true);
setError(undefined);
try {
const user = await firebaseSignInWithEmailAndPassword(
auth,
email,
password
);
setLoggedInUser(user);
} catch (err) {
setError(err as AuthError);
} finally {
setLoading(false);
}
};
const resArray: EmailAndPasswordActionHook = [
signInWithEmailAndPassword,
loggedInUser,
loading,
error,
];
return useMemo<EmailAndPasswordActionHook>(() => resArray, resArray);
};
The handleLogin handler should probably wait for the Promise to settle so any errors can be returned by the hook. It turns out though that signInWithEmailAndPassword also doesn't return any resolve/rejected values, so there's no way to know the authentication was successful from within the handleLogIn callback function, the component will need to use the hook's returned loading and loggedInUser states to determine if it is safe to navigate.
Example:
const Login = () => {
const [
signInWithEmailAndPassword,
loggedInUser,
loading,
error,
] = useSignInWithEmailAndPassword(auth);
const location = useLocation();
const navigate = useNavigate();
const from = location?.state?.from?.pathname || '/';
useEffect(() => {
if (!loading && loggedInUser) {
navigate(from, { replace: true });
}, [loggedInUser, loading, navigate, from]);
if (error) {
return (
<div>
<p>Error: {error.message}</p>
</div>
);
}
const handleLogIn = (e) => {
e.preventDefault();
const email = e.target.email.value;
const password = e.target.password.value;
signInWithEmailAndPassword(email, password)
e.target.reset();
}
...
I am trying to do backend for my website and I have login, signup, reset password etc. all working. What I am trying to do now is when user log in or sign up, AuthContext to check for file that match his UID and if exist store it to variable or if not exist create it and store it to variable. Just cant get it work. Best i got so far is code at bottom but problem there is when I log out user I am getting all sort errors because user not exists anymore.
my context file look like this so far and everything is working:
import { createContext, useContext, useEffect, useState } from "react";
import { auth, db } from "../firebase";
export const AuthContext = createContext();
export const useAuth = () => {
return useContext(AuthContext);
};
const AuthContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [currentUserDoc, setCurrentUserDoc] = useState(null);
const [loading, setLoading] = useState(true);
const signup = (email, password) => {
return auth.createUserWithEmailAndPassword(email, password);
};
const login = (email, password) => {
return auth.signInWithEmailAndPassword(email, password);
};
const logout = () => {
return auth.signOut();
};
const resetPassword = (email) => {
return auth.sendPasswordResetEmail(email);
};
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = { currentUser, currentUserDoc, signup, login, logout, resetPassword };
return <AuthContext.Provider value={value}>{!loading && children}</AuthContext.Provider>;
};
export default AuthContextProvider;
I tryed to change useEffect hook to this but can't get it done right:
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async (user) => {
setCurrentUser(user);
const userDoc = db.collection("users").doc(user.uid);
await userDoc.get().then((doc) => {
if (doc.exists) {
setCurrentUserDoc(doc.data());
} else {
doc.set({
email: user.email,
first_name: "",
last_name: "",
country: "",
organization: "",
});
}
});
setLoading(false);
});
return unsubscribe;
}, []);
This is error when there is no user logged in:
Code where I am trying to use it:
import styles from "./Header.module.scss";
import { useAuth } from "../../contexts/AuthContext";
const Header = () => {
const { logout, currentUserDoc } = useAuth();
return (
<header className={styles.header}>
<div></div>
<div className={styles.header__user}>
{currentUserDoc.email}
<button onClick={logout}>Log out</button>
</div>
</header>
);
};
export default Header;
The onAuthStateChanged observer will trigger when the user logs in or logs out. In case the user has logged out, the user object will be null and hence you will get an error "TypeError: Cannot read property 'uid' of null". You should check if the user is still logged in inside of the auth observer.
const unsubscribe = auth.onAuthStateChanged(async (user) => {
// Check if user is present (logged in) or absent (logged out)
if (!user) {
// user has logged out
console.log("No User")
} else {
// Add the required documents
setCurrentUser(user);
const userDoc = db.collection("users").doc(user.uid);
const doc = await userDoc.get()
if (doc.exists) {
setCurrentUserDoc(doc.data());
} else {
await userDoc.set({
email: user.email,
first_name: "",
last_name: "",
country: "",
organization: "",
});
}
}
})
I am trying to make an update user page with previous information to be rendered inside the input fields. Console.log returns the correct value but its not showing up as the initial value inside of the useState.
Getting previous user bio
function EditProfile(props) {
const user = useSelector(state => state.user);
const [profile, setProfile] = useState([])
const userId = props.match.params.userId
const userVariable = {
userId: userId
}
useEffect(() => {
axios.post('/api/users/getProfile', userVariable)
.then(response => {
if (response.data.success) {
console.log(response.data)
setProfile(response.data.user)
} else {
alert('Failed to get user info')
}
})
}, [])
console.log(profile.bio);
Heres what I am currently using to display the input field. (edited for brevity)
const [bio, setBio] = useState("");
const handleChangeBio = (event) => {
console.log(event.currentTarget.value);
setBio(event.currentTarget.value);
}
return (
<label>Bio</label>
<TextArea
id="bio"
onChange={handleChangeBio}
value={bio}
/>
)
Was trying to do this before but object was not showing up as the useState initial value
const [bio, setBio] = useState(User.bio);
Back-end - I know that $set overrides all information, so was trying to render the previous information inside of the input fields so it would not be overrided with blank values.
router.post('/edit', auth, (req, res)=> {
console.log(req.body.education)
User.updateMany(
{ _id: req.user._id },
[ {$set: { bio: req.body.bio}},
{$set: { industry: req.body.industry}},
{$set: { jobTitle: req.body.jobTitle}},
],
(err)=>{
if (err) return res.json({success: false, err});
return res.status(200).send({
success: true
});
});
});
Create some custom component and put User as props and you will see that you get data.
const [User, setUser] = useState([])
better to change to
const [user, setUser] = useState('')
You can get some issues because components starts with capital letter
And array as default value may error after first render
You can move it to separate component:
<Example user={user} />
const Example = (props) => {
const [bio, setBio] = useState(props.user.bio);
const handleChangeBio = (event) => {
console.log(event.currentTarget.value);
setBio(event.currentTarget.value);
}
return (
<label>Bio</label>
<TextArea
id="bio"
onChange={handleChangeBio}
value={bio}
/>
)
}
I'm trying to sign a user in, and update my global context with the user data. To keep the user signed in I'm storing their data in local storage.
I'm using react-hooks to take care of the state, hence I have defined a state: let [userData, setUserData] = useState({});.
Since I wan't to keep the user signed in I store their data in local storage during sign in. This works and the data does in fact get stored in local storage.
My problem is however that I can't set the initial userData state equal to the current data from local storage. In other words the userData state gets reset to default on reload.
I thought that getting the initial data from local storage and assigning it to state inside the useEffect hook would work. But the state does not update when calling setUserData inside useEffect.
AuthContext.js:
import React, { createContext, useState, useEffect } from 'react';
export const AuthContext = createContext();
const AuthContextProvider = props => {
let [userData, setUserData] = useState({});
const loginUser = (data) => {
localStorage.setItem('userData', JSON.stringify({
key: data.key,
id: data.id,
email: data.email,
first_name: data.first_name,
last_name: data.last_name
})); // Save the user object in local storage
setUserData({
key: data.key,
id: data.id,
email: data.email,
first_name: data.first_name,
last_name: data.last_name
}); // Set user data
};
const logoutUser = () => {
localStorage.removeItem('userData');
setUserData({}); // Empty user data state
newToast('Successfully signed out');
};
useEffect(() => {
const localUser = JSON.parse(localStorage.getItem('userData'));
if (localUser && localUser.key) {
setUserData({
key: localUser.key,
id: localUser.id,
email: localUser.email,
first_name: localUser.first_name,
last_name: localUser.last_name
}); // Set user data
}
}, [])
return (
<AuthContext.Provider value={{ userData, loginUser, logoutUser, newToast }}>
{props.children}
</AuthContext.Provider>
)
}
export default AuthContextProvider;
Signin.js:
const Signin = props => {
let [loading, setLoading] = useState(false);
let [formError, setFormError] = useState(false);
const { userData, loginUser, newToast } = useContext(AuthContext);
const { register, handleSubmit, errors, setError, clearError } = useForm();
const onSubmit = e => {
setLoading(true);
setFormError(false);
clearError(); // Clear all erros on form
axios
.post('users/auth/login/', {
headers: { 'Content-Type': 'application/json' },
email: `${e.email}`,
password: `${e.password}`,
})
.then(res => {
const { data } = res
loginUser(data);
newToast('Successfully signed in');
})
.catch((error) => {
const { data } = error.response;
console.log(data);
data.email && setError("email", "", data.email);
data.password1 && setError("password", "", data.password1);
setFormError(true);
})
setLoading(false);
};
return ( ... );
}
Updated answer (Aug. 15, 2022):
Since accessing the local storage on every render is expensive, it is preferred to only access it during the initial render (see Wayne Ellery's comment).
So quoting Erol's solution:
const [user, setUser] = useState([], () => {
const localData = localStorage.getItem('userData');
return localData ? JSON.parse(localData) : [];
});
Original answer:
So I figured out a solution!
In AuthContext.js i didn't need to assign the state in useEffect.
Instead I get the initial data directly when defining the state hooks:
const localUser = JSON.parse(localStorage.getItem('userData')) || {};
let [userData, setUserData] = useState(localUser);
That way I don't need the useEffect hook at all.
I hope this is the recommended way of doing it.
If I understand your question, you could do the following:
const [user, setUser] = useState([], () => {
const localData = localStorage.getItem('userData');
return localData ? JSON.parse(localData) : [];
});
How about to use useReducer like this?
const [user, setUser] = useReducer((prev, cur) => {
localStorage.setItem('userData', JSON.stringify(cur));
return cur;
}, JSON.parse(localStorage.getItem('userData')));
You can call
setUser({ key: '1', ... });