EDIT: I resolved it. The issue was that I was passing the userData object to something else that didn't require all of it. When I narrowed it down I got what I was hoping to achieve.
So I'm trying to make it so that when a user registers on my web app, it'll automatically log them in and then redirect to the home page. The redirect to home page is working, but it's not registering the user or setting the user's details. I've looked at the documentation and believe I'm doing it correctly, but still no results?
authActions.js File:
// Register User
export const registerUser = (userData, history) => dispatch => {
axios.post('/users/register', userData)
//.then(res => history.push('/login'))
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
// Login - Get User Token
export const loginUser = (userData) => dispatch => {
axios.post('/users/login', userData)
.then(res => {
// Save to localStorage
const { token } = res.data;
// Set token to LocalStorage
localStorage.setItem('jwtToken', token);
// Set token to Auth header
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
// Set current user
dispatch(setCurrentUser(decoded));
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
// Set logged in user
export const setCurrentUser = (decoded) => {
return {
type: SET_CURRENT_USER,
payload: decoded
}
}
// Register, Login, Set User all at the same time
export const regLogSetUser = (userData, history) => dispatch => {
axios.all([registerUser(), loginUser(), setCurrentUser()])
.then(res => history.push('/'))
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
In my Register component, I'm also properly calling in the Prop Function (at the bottom) (after importing it at the top).
Register.propTypes = {
regLogSetUser: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired
}
const mapStateToProps = (state) => ({
auth: state.auth,
errors: state.errors
});
export default connect(mapStateToProps, { regLogSetUser })(withRouter(Register));
Here is the link to the documentation:
https://github.com/axios/axios
And here is what it says for Axios.all()
Performing multiple concurrent requests
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// Both requests are now complete
}));
Thanks for the help. Still learning!
Related
I'm using Firebase to authenticate a user, then using the Firebase ID (uid) to setUserData with the user's row in Postgres via a HTTP request. It works as written, but I'm having trouble with the order execution of these functions because the console is returning 'invalid input syntax for type integer: "undefined"'.
The desired order is
Wait for the authentication to return a uid
Execute the HTTP request using uid
Redirect to "/"
Instead, it seems to run 2, 1, 3, 2. On the second HTTP attempt data is populated. This explains why I sometimes need to log out and log back in for certain components to load with the user's data.
What am I missing to ensure correct order of operations?
Login.js
async function handleSubmit(e) {
e.preventDefault()
try {
setError("")
setLoading(true)
await login(emailRef.current.value, passwordRef.current.value)
setLoading(false)
history.push("/")
} catch {
setLoading(false)
setError("Failed to log in")
}
Auth.js
useEffect(() => {
const fetchProfile = (uid) => {
axios.get(`/user/${uid}`)
.then(async (response) => {
setUserData(await response.data)
console.log(response.data)
console.log(uid)
})
.catch(error => console.error(`Error: ${error}`))
}
const unsubscribe = auth.onAuthStateChanged(async user => {
user && fetchProfile(user.uid)
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
userData,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword
}
return (
<AuthContext.Provider value={value}>
{ !loading && children}
</AuthContext.Provider>
)
I would remove fetchProfile as a function and move unsubscribe outside of useEffect and have currentUser as a dependency to useEffect. useEffect would only run when currentUser has changed.
...
const unsubscribe = auth.onAuthStateChanged(async user => {
//only if there is a user setCurrentUser
if(user){
setCurrentUser(user)
setLoading(false)
}
})
useEffect(() => {
if(!currentUser) return
axios.get(`/user/${currentUser.uid}`)
.then(async (response) => {
setUserData(await response.data)
console.log(response.data)
console.log(uid)
})
.catch(error => console.error(`Error: ${error}`))
}, [currentUser])
...
I have a button that dispatches an action to create a post, for some reason the request never proceeds and it fails. This is the action. I have constants that's why types is not on a string
export const createPost = () => async (dispatch, getState) => {
try {
dispatch({
type: POST_CREATE_REQUEST,
});
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.post(
`http://localhost:5000/api/posts`,
config
);
dispatch({
type: POST_CREATE_SUCCESS,
payload: data,
});
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
// if (message === 'Not authorized, token failed') {
// dispatch(logout());
// }
dispatch({
type: POST_CREATE_FAIL,
payload: message,
});
}
};
It continues to the POST_CREATE_REQUEST but always errors out to the POST_CREATE_FAIL.
I tried using postman and it works fine, I think the problem is the createPost action can't seem to receive the token even though im logged in as an admin, I'm not sure.
This is the useSelector of the postCreate
const postCreate = useSelector(state => state.postCreate);
const {
loading: loadingCreate,
error: errorCreate,
success: successCreate,
post: createdPost,
} = postCreate;
and this is the useSelector of the user that is logged in, currently as an admin.
const userLogin = useSelector(state => state.userLogin);
const { userInfo } = userLogin;
Rewrite this code
const { data } = await axios.post(
`http://localhost:5000/api/posts`,
config
);
as
const res = await axios.post(
`http://localhost:5000/api/posts`,
config
);
const data = res && res.data
There is already values on my Controller at the backend and just needed to add brackets in the action
from this
const { data } = await axios.post(
`http://localhost:5000/api/posts`,
config
);
to this
const { data } = await axios.post(
`http://localhost:5000/api/posts`, {},
config
);
I'm trying to update my user profile data from the client-side. The user has to upload a file and the component will catch the actual user-loaded ID, store it into a state, and then use this state to find the user in the database to update the value I need. But I can't figure out how to pass the state to filter the user; in the way you see below the It gives me a PUT: http://localhost:3000/api/users/upgrade/undefined 404 (Not Found). Someone could help me?
Here's my server router:
//SERVER ROUTER
router.put("/upgrade/:id", upgrade.single("userPlus_doc"), (req, res) => {
User.findById(req.params.id)
.then((user) => {
user.userPlus = true;
user.userPlus_doc = req.file.originalname;
user
.save()
.then(() => res.json("User Upgraded!"))
.catch((err) => res.status(404).json({ success: false }));
})
.catch((err) => res.status(404).json({ success: false }));
});
My action and reducer:
//ACTION
export const upgradeUser = (formData, id) => (dispatch, getState) => {
axios
.put(`/api/users/upgrade/${id}`, formData, tokenConfig(getState))
.then((res) =>
dispatch({
type: USER_UPGRADE,
payload: res.data,
})
)
.catch((err) =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
export const setUserUpgradeID = (user_id) => (dispatch) => {
dispatch({
type: SET_USER_UPGRADE_ID,
payload: user_id,
});
};
//REDUCER
const initialState = {
user_id: "",
};
export default function foo(state = initialState, action) {
switch (action.type) {
...
case SET_USER_UPGRADE_ID:
return {
...state,
user_id: action.payload,
};
case USER_UPGRADE:
return {
...state,
user: state.user.filter((user) => user._id !== action.payload),
};
default:
return state;
}
}
And how I pass the information client-side:
class ProfileUpgrade extends Component {
state = {
userPlus_doc: "",
user_id: "",
};
onFileChange = (e) => {
this.setState({
userPlus_doc: e.target.files[0],
});
this.props.setUserUpgradeID({
user_id: this.props.auth.user._id,
});
};
/* onChange = () => {
this.props.setUserUpgradeID({
user_id: this.props.auth.user._id,
});
console.log(this.props.setUserUpgradeID);
};
*/
onSubmit = (e, user_id) => {
e.preventDefault();
const formData = new FormData();
/* formData.append("userPlus", this.state.userPlus); */
formData.append("userPlus_doc", this.state.userPlus_doc);
this.props.upgradeUser(formData, this.props.user_id);
};
render() {
return ( ... )
}
}
ProfileUpgrade.propTypes = {
auth: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
user: state.user,
auth: state.auth,
user_id: state.user_id,
});
export default connect(mapStateToProps, { upgradeUser, setUserUpgradeID })(
ProfileUpgrade
);
The only things I need from the client-side are the doc title and the user ID to update the correct user object. To update the boolean userPlus I set the backend to set the value to true and save it.
Am I missing something in my action to make the component pass filter the user ID?
I'm currently trying to add and delete my items on the page and it's returning an error.
Unhandled rejection (TypeError): Cannot read property data of undefined pointing to .catch in both of in the below code.
export const addItem = (item) => (dispatch,
getState) => {
axios
.post('/api/items', item, tokenConfig(getState))
.then(res => dispatch({
type: ADD_ITEM,
payload: res.data
}))
.catch(err => dispatch(returnErrors(err.response.data, err.response.status))
);
};
export const deleteItem = (id) => (dispatch, getState) => {
axios
.delete(`/api/items/${id}`, tokenConfig(getState))
.then(res => dispatch({
type: DELETE_ITEM,
payload: id
}))
.catch(err => dispatch(returnErrors(err.response.data, err.response.status))
);
};
/////////////////////////////////////////////////
The returnErrors method referenced above is from another file that is here:
import { GET_ERRORS, CLEAR_ERRORS } from './types';
// RETURN ERRORS
export const returnErrors = (msg, status, id = null) => {
return {
type: GET_ERRORS,
payload: { msg, status, id }
};
};
// CLEAR ERRORS
export const clearErrors = () => {
return {
type: CLEAR_ERRORS
};
};
I have put a console.log(err.response) and a console.log(err.response.data) right above the dispatch(returnErrors(err.response.data, err.response.data)); and returned undefined for the first and uncaught (in promise) cannot read property of undefined
I was told by someone that
This essentially means your error object doesn't have correct data. Please look into the error object returned. It could be an issue with items/user api, it should return correct error object.
items api route
router.post('/', auth, (req, res) => {
const newItem = new Item({
name: req.body.name
})
newItem.save().then(item => res.json(item));
});
// DELETE api/items/:id
// Delete an item
// Private
router.delete('/:id', auth, (req, res) => {
Item.findById(req.params.id)
.then(item => item.remove().then(() => res.json({ deleted: true
})))
.catch(err => res.status(404).json({ deleted: false }));
})
Not sure where data is undefined. Anyone see anything missing?
You can take a look at what the chrome dev tools network tab returned here:
https://imgur.com/D5OGLpf
authActions
// Check token & Load User
// Want to check routes/auth.js for user by id that's included with token
// Going to use asynchronous request, use dispatch
export const loadUser = () => (dispatch, getState) => {
// User loading
dispatch({ type: USER_LOADING });
// Fetch user
axios.get('/api/auth/user', tokenConfig(getState))
.then(res => dispatch({
type: USER_LOADED,
payload: res.data
}))
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status));
dispatch({
type: AUTH_ERROR
});
});
};
// Register User
export const register = ({ name, email, password }) => dispatch => {
// Headers
const config = {
headers: {
'Content-Type': 'application/json'
}
}
// Request body
const body = JSON.stringify({ name, email, password });
axios.post('/api/users', body, config)
.then(res => dispatch({
type: REGISTER_SUCCESS,
payload: res.data
}))
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status, 'REGISTER_FAIL'));
dispatch({
type: REGISTER_FAIL
});
});
};
// LogIn
export const login = ({ email, password }) => dispatch => {
// Headers
const config = {
headers: {
'Content-Type': 'application/json'
}
}
// Request body
const body = JSON.stringify({ email, password });
axios.post('/api/auth', body, config)
.then(res => dispatch({
type: LOGIN_SUCCESS,
payload: res.data
}))
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status,
'LOGIN_FAIL'));
dispatch({
type: LOGIN_FAIL
});
});
};
// LogOut
export const logout = () => {
return {
type: LOGOUT_SUCCESS
};
};
// Setup config/headers and Token
export const tokenConfig = (getState) => {
// Get token from localstorage
const token = getState().auth.token;
// Headers
const config = {
headers: {
"Content-type": "application/json"
}
}
// Check if token exists, add to Headers
if(token) {
config.headers['x-auth=token'] = token;
}
return config;
}
Base your image https://imgur.com/D5OGLpf, your request to axios.delete('/api/items/${id} do not reach the route /api/items/:id.
Why I said so?
The response status is 401 (https://imgur.com/D5OGLpf), meaning that Unauthorized. The endpoint of the route router.delete('/:id' might be protected by the authentication middleware or something like that.
To solve it,
First
You need to make an authenticated request using the way you set up for your api either basic authentication, oauth[2], or your customized one.
Then
Before dispatch dispatch(returnErrors..., you need to check if the data exists.
axios
.delete(`/api/items/${id}`, tokenConfig(getState))
.then(res => dispatch({
type: DELETE_ITEM,
payload: id
}))
.catch(err => {
if(error.status === 404) {
// here, you are sure that error.response.data exists
dispatch(returnErrors(err.response.data, err.response.status)
}
else {
// do something else to handle the error
}
})
**Remember that ** the caught error can be anything ranging from your error status 400, 500,... to you un-caught error within the .then(...).
The inner promise to remove the item remains in a pending state and as you have noted doesn't return any response.
To have an idea what is happening.
Item.findById(req.params.id)
.then(item => item.remove().then(() => res.json({ deleted: true
})))
.catch(err => res.status(404).json({ deleted: false }));
})
can be simplified to
P.then(p => Q)
where P and Q are promises objects.
When P is fulfilled, it returns Q and Q continues to remain in a pending state waiting for it to be resolved.
You can resolve Q by flattening the then chain to handle when the remove operation is fulfilled.
Item.findById(req.params.id)
.then(item => item.remove())
.then(() => res.json({ deleted: true }))
.catch(err => res.status(404).json({ deleted: false }));
I'm setting a basic authentication on a Nuxt project with JWT token and cookies to be parsed by nuxtServerInit function.
On login with email/password, works as intended, setUser mutation is triggered and the appropriate user object is stored in state.auth.user.
On reload, nuxtServerInit will get the jwt token from req.headers.cookies, call the GET method and identify user.Works like a charm.
Problem starts when I hit the /logout endpoint. state.auth.user is set to false and Im effectively logged out... but If I refresh, I'm logged in again with the previous user data. Even if my cookies are properly empty (on below code, both user and cookie are undefined after logout and refresh, as expected)
So I really don't get why is my state.auth.user is back to its initial value...
store/index.js
import Vuex from "vuex";
import auth from "./modules/auth";
import axios from "~/plugins/axios";
const cookieparser = process.server ? require("cookieparser") : undefined;
const END_POINT = "api/users";
const createStore = () => {
return new Vuex.Store({
actions: {
async nuxtServerInit({ commit, dispatch}, { req }) {
let cookie = null;
console.log(req.headers.cookie)
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie);
try {
cookie = JSON.parse(parsed.auth);
console.log("cookie", cookie)
const {accessToken} = cookie
const config = {
headers: {
Authorization: `Bearer ${accessToken}`
}
}
const response = await axios.get(`${END_POINT}/current`, config)
const user = response.data
console.log("user nuxt server init", user)
await commit('setUser', user)
} catch (err) {
// No valid cookie found
console.log(err);
}
}
}
},
modules: {
auth
}
});
};
export default createStore;
modules/auth.js
import axios from "~/plugins/axios";
const Cookie = process.client ? require("js-cookie") : undefined;
const END_POINT = "api/users";
export default {
state: {
user: null,
errors: {}
},
getters: {
isAuth: state => !!state.user
},
actions: {
login({ commit }, payload) {
axios
.post(`${END_POINT}/login`, payload)
.then(({ data }) => {
const { user, accessToken } = data;
const auth = { accessToken };
Cookie.set("auth", auth);
commit("setUser", user);
})
.catch(e => {
const error = e;
console.log(e);
commit("setError", error);
});
},
logout({ commit }) {
axios
.post(`${END_POINT}/logout`)
.then(({ data }) => {
Cookie.remove("auth");
commit("setUser", false);
})
.catch(e => console.log(e));
},
},
mutations: {
setUser(state, user) {
state.user = user;
},
setError(state, errors) {
state.errors = errors;
}
}
};
The way I logout my user is by creating a mutation called clearToken and commit to it in the action :
State :
token: null,
Mutations :
clearToken(state) {
state.token = null
},
Actions :
logout(context) {
context.commit('clearToken')
Cookie.remove('token')
}
This way, you token state revert back to null.