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.
Related
I want to revalidate the date when on change. This is what I tried:
const fetchData = async() => {
const {
data
} = await axios.get(`/api/admin/orders${criteria}`, {
params: {
name: debouncedValue,
},
});
return data;
};
const {
data,
error: err,
mutate,
} = useSWR(`/api/admin/orders${criteria}/${nameSearch}`, fetchData);
const handleChange = (e: React.ChangeEvent < HTMLInputElement > ) => {
mutate();
setNameSearch(e.target.value);
};
But the data is no revalidate, I have to use the onTabFocus revalidation.
I have created a react hook to work on with multiple get request using axios
const useAxiosGetMultiple = (urls,{preventCall = false} = {}) => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState(()=>{
const temp = {}
Object.keys(urls).forEach(key => temp[key] = [])
return temp
})
const [reloadToken, setReloadToken] = useState(false)
const urlObj = useRef({...urls})
const unmountedOnReload = useRef(false)
useEffect(() => {
if(preventCall === true){
return null
}
let unmounted = false;
const source = axios.CancelToken.source();
setLoading(true)
const requests = []
Object.values(urlObj.current).forEach(url => {
requests.push(
axios.get(url, {
cancelToken: source.token,
})
);
});
const result = {}
const errors = {}
console.log(requests)
Promise.allSettled(requests)
.then(resArray => {
if(!unmounted){
console.log('from promise allsettled')
console.log(resArray)
console.log(urlObj.current)
Object.keys(urlObj.current).forEach((key,i) =>{
if(resArray[i].status === 'fulfilled'){
result[key] = resArray[i].value.data.responseData
}
if(resArray[i].status === 'rejected'){
errors[key] = resArray[i].reason
result[key] = []
}
})
setError(errors)
setLoading(false)
setResponse(result)
}
})
.catch(err => {
if (!unmounted) {
setError(err);
setLoading(false);
setResponse([])
if (axios.isCancel(err)) {
console.log(`request cancelled:${err.message}`);
} else {
console.log("another error happened:" + err.message);
}
}
})
return () => {
unmounted = true;
unmountedOnReload.current = true
source.cancel("Api call cancelled on unmount");
};
}, [reloadToken,preventCall]);
const reFetchAll = () => {
setReloadToken((token) => !token);
};
const reload = (urlKey) =>{
unmountedOnReload.current = false
setLoading(true)
axios.get(urls[urlKey])
.then(res =>{
if(!unmountedOnReload.current){
setLoading(false)
setResponse({...response,[urlKey]: res.data.responseData})
}
})
.catch(err=>{
if(!unmountedOnReload.current){
setLoading(false)
setError({...error, [urlKey]: err})
setResponse({...response,[urlKey]: []})
}
})
}
return {response, loading, error, reFetchAll, reload, setLoading};
};
I call this hook as follows..
const {response,loading,setLoading,reload} = useAxiosGetMultiple({
stateCodes: StateCode.api,
countryCodes: CountryCode.api,
districts: District.api,
})
Rather than getting variable stateCodes containing state codes or countryCodes containing country codes it's returning in wrong order or returning same data in multiple variable. Every time the call happens every time it changes. I also tried axios.all method instead of Promise.all but problem remains same.
Even in chrome's network panel the response data is improper.
What's the possible cause for this error and how to fix it ?
Thanks in advance
I want store a new group as an object into the groups entity in the store. Everything works perfectly but the new group is stored as an object not as a string. I am using Mockoon to mock an API and the data type is set to be application/json. Can someone explain to me what might be the possible cause of this behavior? I am quite new on using redux so some input would be really appreciated too.
Thank you
const dispatch = useDispatch();
const initialGroupState = {
id: null,
name: "",
description: "",
members: []
}
const [group, setGroup] = useState(initialGroupState)
const [submitted, setSubmitted] = useState(false);
const handleInputChange = event => {
const { name, value } = event.target;
setGroup({ ...group, [name]: value });
};
const saveGroup = (e) => {
e.preventDefault();
const {name, description} = group;
dispatch(createGroup(name, description))
.then(data => {
setGroup({
id: Math.floor(Math.random() * 10000),
name: data.name,
description: data.description,
});
setSubmitted(true);
})
.catch(e => {
console.log(e);
});
}
const newGroup = () => {
setSubmitted(false);
};
My reducer:
const initialState = []
function groupsReducer(groups = initialState, action) {
const { type, payload } = action;
console.log([...groups]);
switch (type) {
case CREATE_GROUP:
return [...groups, payload];
case RETRIEVE_GROUPS:
return payload;
default:
return groups;
}
};
My actions:
export const createGroup = (name, description) => async (dispatch) => {
try {
const res = await GroupDataService.create({ name, description });
dispatch({
type: CREATE_GROUP,
payload: res.data,
});
console.log(res.data)
return Promise.resolve(res.data);
} catch (err) {
console.log(err)
return Promise.reject(err);
}
};
I'm working a side project using react, redux and firebase as backend.
Right now I was looking a way to store the URL of the image I was uploading into my firestore database along side other data.
This is what is working at the moment:
Action addProduct.js
export const addProduct = (product) => {
return (dispatch, getState, { getFireStore }) => {
const db = firestore;
const uploadTask = storage.ref(`images/${product.image.name}`).put(product.image);
uploadTask.on(
"state_changed",
snapshot => {},
error => {
console.log(error);
},
() => {
storage
.ref("images")
.child(product.image.name)
.getDownloadURL()
.then(url => {
let updatedProduct = {
...product,
image: url
}
db.collection('products').add({
...updatedProduct,
}).then(() => {
dispatch({
type: ADD_PRODUCT,
updatedProduct
})
}).catch((err) => {
dispatch({ type: ADD_PRODUCT_ERR, err })
})
})
}
)
}
Couple of things bugging me here:
While it successfully uploads the image and stores its url in the database, this code looks really messy from my perspective. Can an action file be this complex? Is there a more cleaner way to do this?
My reducer is returning an undefined payload for some reason I can't find.
Reducer productReducer.js
import { ADD_PRODUCT, ADD_PRODUCT_ERR } from "../actions/types"
const initialState = {
product: null
}
export default (state = initialState, action) => {
switch(action.type) {
case ADD_PRODUCT:
console.log('Product Added', action.product)
return state
case ADD_PRODUCT_ERR:
console.log('Product Failed', action.err)
return state
default:
return state
}
}
I am using React+Redux+Redux Thunk + Firebase authentication. Writing code in Typescript.
My action is:
//Type for redux-thunk. return type for rdux-thunk action creators
type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
IStoreState, //my store state
null,
Action<userActionTypes>
>
export const signInWithEmailAndPasword =(email:string, pasword:string): AppThunk=>{
return async (dispatch)=>{
auth.signInWithEmailAndPassword(email, pasword).then(response=>{
if(response.user){
const docRef = db.collection("users").doc(response.user.uid);
docRef.get().then(function(doc) {
if (doc.exists) {
const userData = doc.data(); //user data from firebase DB
//if user exists in DB, dispatch
dispatch({
type: userActionTypes.SIGN_IN_USER,
payload: userData
})
return userData;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
}
})
.catch(err=> dispatch(setUserError(err.message)))
}
}
My SignIn component, where i am dispatching this above action:
import React, { useState } from 'react'
//some other imports
//...
//
import { useDispatch, useSelector } from 'react-redux';
import { signInWithEmailAndPasword } from '../../redux/actions/userActions';
interface ISignInState {
email: string;
password: string;
}
const SignIn = (props:any) => {
const [values, setValues] = useState<ISignInState>({ email: '', password: '' })
const dispatch = useDispatch();
const handleInputChange = (e: React.FormEvent<HTMLInputElement>): void => {
const { name, value } = e.currentTarget;
setValues({ ...values, [name]: value })
}
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault()
const { email, password } = values;
dispatch(signInWithEmailAndPasword(email, password))
//// -> gives error: Property 'then' does not exist on
//// type 'ThunkAction<void, IStoreState, null, Action<userActionTypes>>'
.then(()=>{
props.history.push('/');
setValues({ email: '', password: '' })
})
}
return (<div>Sign in UI JSX stuff</div>)
So when i try to use .then() after dispatch(signInWithEmailAndPasword(email, password)) it gives an error Property 'then' does not exist on type 'ThunkAction<void, IStoreState, null, Action<userActionTypes>>'
So how can i return promise from redux action and chain a .then() on it? I always assumed that thunk actions return promises by default.
Thanks for your help
Edit:
Temporary soluton was to use any as return type of above action:
export const signInWithEmailAndPasword = (email:string, pasword:string):any =>{
return async (dispatch: any)=>{
try {
const response = await auth.signInWithEmailAndPassword(email, pasword)
if(response.user){
const userInDb = await getUserFromDB(response.user)
dispatch(userSignIn(userInDb))
return userInDb
}
} catch (error) {
dispatch(setUserError(error.message))
}
}
}
But I don't want to use any
Just add return before this line:
auth.signInWithEmailAndPassword(email, pasword).then(response=>{
So it would be:
export const signInWithEmailAndPasword =(email:string, pasword:string): AppThunk=>{
return async (dispatch)=>{
return auth.signInWithEmailAndPassword(email, pasword).then(response=>{
It should work.
AppThunk<Promise<void>>
You need to explicitly declare the AppThunks return type, which in this case should be a Promise containing nothing. You have already made it async so just make sure to enter the correct AppThunk return type
export const signInWithEmailAndPassword = (email: string, password: string): AppThunk<Promise<void>> => {
return async (dispatch) => {
// do stuff
}
}
Thunks return functions, not promises. For this you could look at redux-promise. But to be honest if your doing something this complex you would be much better off using redux-saga.
Another approach would be to use the concepts behind redux-api-middleware to create your own custom redux middleware. I have done this in the past to connect a message queue to redux.