I want to add a Clickable “Remember Me” checkbox in my login page that tells the browser to save a cookie so that if you close out the window for the site without signing out, the next time you go back, you will be signed back in automatically.that can save username and password
export const getUser = () => {
const userStr = sessionStorage.getItem("user");
if (userStr) return JSON.parse(userStr);
else return null;
};
export const getToken = () => {
return sessionStorage.getItem("token") || null;
};
export const setUserSession = (token, user) => {
sessionStorage.setItem("token", token);
sessionStorage.setItem("user", JSON.stringify(user));
};
export const removeUserSession = () => {
sessionStorage.removeItem("token");
sessionStorage.removeItem("user");
};
export const handleSuccessfulLogin = async (token, rememberMe) => {
localStorage.setItem("token", token);
localStorage.setItem("rememberme", rememberMe);
};
export const handleLogout = () => {
localStorage.clear();
};
This is my login that work with api
const handelLogin = () => {
setError(null);
setLoading(true);
axios
.post("https://www.mecallapi.com/api/login", {
username: username,
password: password,
})
.then((response) => {
setLoading(false);
setUserSession(response.data.token, response.data.user);
navigate("/Dashboard");
})
.catch((error) => {
setLoading(false);
if (error.response.status === 401 || error.response.status === 400) {
setError(error.response.data.message);
} else {
setError("somthing went wrong ,please try again");
}
});
};
This is my remember me checkbox
<div className="login-bottom">
<Checkbox {...label} />
</div>
Related
I have this a Dashboard component which makes three API calls to fetch widget data.
If any API call fails it refreshes token.
But, when Dashboard renders it makes API call individually and they don't wait to check if first api call failed or token is refreshed. Each api call ends up making another call to refresh the token.
It should stop at first API call fail and refresh the token.
But it does so for each request. How can I prevent this behaviour.
It seems I need to make request sequentially.
const Dashboard = () => {
const { response: studentResponse } = useAxios(ApiConfig.STUDENT.GET_STUDENTS);
const { response: courseResponse } = useAxios(ApiConfig.COURSE.GET_COURSES);
const { response: feesResponse } = useAxios(ApiConfig.FEES.GET_TOTAL);
return (
<Box padding={2} width="100%">
<Stack direction={'row'} justifyContent="space-between" gap={2} mb={10}>
<NewWidget type={'student'} counter={studentResponse?.data?.length} />
<NewWidget type={'course'} counter={courseResponse?.data?.length} />
<NewWidget type={'earning'} counter={feesResponse?.data} />
</Stack>
</Box>
);
};
export default Dashboard;
use-axios.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import history from '../utils/history';
import refreshToken from './refresh-token';
const Client = axios.create();
Client.defaults.baseURL = 'http://localhost:3000/api/v1';
const getUser = () => {
const user = localStorage.getItem('user');
return user ? JSON.parse(user) : null;
};
const updateLocalStorageAccessToken = (accessToken) => {
const user = getUser();
user.accessToken = accessToken;
localStorage.setItem('user', JSON.stringify(user));
};
Client.interceptors.request.use(
(config) => {
const user = getUser();
config.headers.Authorization = user?.accessToken;
return config;
},
(error) =>
// Do something with request error
Promise.reject(error)
);
Client.interceptors.response.use(
(response) => response,
(error) => {
// Reject promise if usual error
if (error.response.status !== 401) {
return Promise.reject(error);
}
const user = getUser();
const status = error.response ? error.response.status : null;
const originalRequest = error.config;
console.log(originalRequest);
if (status === 401 && originalRequest.url !== '/auth/refresh-token') {
refreshToken(user.refreshToken)
.then((res) => {
const { accessToken } = res.data.data;
Client.defaults.headers.common.Authorization = accessToken;
// update local storage
updateLocalStorageAccessToken(accessToken);
return Client(originalRequest);
})
.catch((err) => {
console.log(err);
if (err.response.status === 401) {
localStorage.setItem('user', null);
history.push('/login');
}
return Promise.reject(err);
});
}
history.push('/login');
return Promise.reject(error);
}
);
export const useAxios = (axiosParams, isAuto = true) => {
const [response, setResponse] = useState(undefined);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
const fetchData = async (params) => {
try {
const result = await Client.request({
...params,
method: params.method || 'GET',
headers: {
accept: 'application/json',
},
});
setResponse(result.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (isAuto) fetchData(axiosParams);
}, [axiosParams, isAuto]); // execute once only
return { fetch: () => fetchData(axiosParams), response, error, loading };
};
In the interceptor for the response, you check if there's an error. I would keep a state which contains the previous success of a call, implement that how you wish - after that, create an interceptor for requests which checks if that error occurred, and if so, cancel the request:
axios.interceptors.request.use((req: AxiosRequestConfig) => {
if(error){
throw new axios.Cancel('Operation canceled due to previous failure.');
}
else {
return req
}
})
Also see: Axios: how to cancel request inside request interceptor properly?
I am working on authentication for my react native app.
The problem I am having is that the signInUser function seems to be executing in the wrong order. I want the signIn function to fully execute before moving on.
However, that doesn't seem to be happening as I am getting this response in my console, with the "undefined" coming from console.log(response) in the SignInScreen.
Here is the console:
undefined
16d0707a3508a9b43b8c36c8574ca73d8b4b26af
I have this function in the SignInScreen.js
import { signIn } from "../services/authService";
import { useAuthDispatch } from "../contexts/authContext";
const SignInScreen = ({ navigation }) => {
const dispatch = useAuthDispatch();
const [signInLoading, setSignInLoading] = useState(false);
const signInUser = async (values) => {
const { email, password } = values;
setSignInLoading(true);
signIn(email, password)
.then((response) => {
console.log(response);
dispatch({
type: "SIGN_IN",
token: response,
});
})
.catch((e) => {
console.log(e);
})
.finally(() => setSignInLoading(false));
};
And this is my authService.js:
import axios from "axios";
const signIn = async (email, password) => {
axios
.post("http://127.0.0.1:8000/rest-auth/login/", {
username: email,
email: email,
password: password,
})
.then((response) => {
console.log(response.data.key);
return response.data.key;
})
.catch((error) => {
return error;
});
};
How can I fix this?
Ok, this is gonna be hard to explain, but I have a bug that I don't know how to fix. I have a Follow component, where i make a call to the backend to add the user as a follower/following. The function itself works, but when I change the state of the dynamicUser, that User get's added to my navBar and I can access the Profile. I'm sharing my whole code so maybe any of you guys can point me on what is wrong. Thanks in advance!
Follow component
const Follow = ({ userId, user, setUser }) => {
const [error, setError] = useState(null);
const [follow, setFollow] = useState([]);
const classes = useStyles();
const handleFollow = (e) => {
e.preventDefault();
const accessToken = localStorage.getItem(CONST.ACCESS_TOKEN);
axios
.put(
`${CONST.SERVER_URL}${PATHS.USER}/${userId}${PATHS.FOLLOW_USER}`,
{ user },
{ headers: { authorization: accessToken } }
)
.then((response) => {
console.log("BASH", response);
setError(null);
if (!response.status) {
return setError(response);
}
setUser(response?.data);
})
.catch((error) => {
console.log("ERROR", error);
});
};
const handleUnFollow = (e) => {
e.preventDefault();
const accessToken = localStorage.getItem(CONST.ACCESS_TOKEN);
axios
.put(
`${CONST.SERVER_URL}${PATHS.USER}/${userId}${PATHS.UNFOLLOW_USER}`,
{ user },
{ headers: { authorization: accessToken } }
)
.then((response) => {
console.log("Are you?", response);
setError(null);
if (!response.status) {
return setError(response);
}
setUser(response.data);
})
.catch((error) => {
console.log(error);
});
};
return (
<div>
{user.following[0] === userId ? (
<form onSubmit={handleUnFollow}>
<Button
className={classes.unFollow}
variant="contained"
color="secondary"
type="submit"
startIcon={<PersonAddDisabledIcon />}
>
UnFollow
</Button>
</form>
) : (
<form onSubmit={handleFollow}>
<Button
className={classes.follow}
variant="contained"
color="primary"
type="submit"
startIcon={<PersonAddIcon />}
>
Follow
</Button>
</form>
)}
</div>
);
};
Parent component
const SingleUser = (props) => {
const { user, setUser } = props;
const [dynamicUser, setDynamicUser] = useState({});
const [isLoading, setIsLoading] = useState(true);
const classes = useStyles();
useEffect(() => {
setIsLoading(true);
axios
.get(`${CONST.SERVER_URL}/users/${props.match.params.userId}`, {
headers: { authorization: localStorage.getItem(CONST.ACCESS_TOKEN) },
})
.then((response) => {
setDynamicUser(response.data);
})
.catch((err) => {
console.log(err.response);
})
.finally(() => {
setIsLoading(false);
});
}, [props.match.params.userId]);
if (isLoading) {
return <LoadingComponent />;
}
console.log("DYNAMIC", dynamicUser);
return (
<div>
<Grid className={classes.button} raise elevation={3}>
<Follow userId={dynamicUser._id} user={user} setUser={setUser} />
</Grid>
)
}
this will likely either solve the issue, or make it clearer from error messages where the issue is
// ...
const Follow = ({ userId, user, setUser }) => {
const [error, setError] = useState(null);
const [follow, setFollow] = useState([]);
const classes = useStyles();
const handleFollow = () => {
return new Promise(async (resolve, reject) => {
try {
const accessToken = localStorage.getItem(CONST.ACCESS_TOKEN);
const url = `${CONST.SERVER_URL}${PATHS.USER}/${userId}${PATHS.FOLLOW_USER}`
const response = await axios.put(url, { user }, { headers: { authorization: accessToken } })
console.log("BASH", response);
setError(null);
if (!response.status) throw response
setUser(response.data);
resolve()
} catch (err) {
setError(err.response.data)
console.log("ERROR", err);
reject(err)
}
})
};
// try catch blocks are easier to troubleshoot with promises
const handleUnFollow = () => {
return new Promise(async (resolve, reject) => {
try {
const accessToken = localStorage.getItem(CONST.ACCESS_TOKEN);
const url = `${CONST.SERVER_URL}${PATHS.USER}/${userId}${PATHS.UNFOLLOW_USER}`
const response = await axios.put(url, { user }, { headers: { authorization: accessToken } })
console.log("Are you?", response);
if (!response.status) throw response
setError(null);
setUser(response.data);
resolve();
} catch (err) {
console.error(err);
setError(err.response.data);
reject(err);
}
})
};
// i made some purely syntactical changes - i've always felt this was
// more readable in my own code to wite them out this way
const hasUserId = user.following[0] === userId
return (
<div>
<form
onSubmit={async e => {
e.preventDefault();
if (hasUserId) {
await handleUnFollow()
} else {
await handleFollow()
}
}}
>
<Button
className={hasUserId ? classes.unFollow : classes.follow}
variant="contained"
color={hasUserId ? 'secondary' : 'primary'}
type="submit"
startIcon={hasUserId ? <PersonAddDisabledIcon /> : <PersonAddIcon />}
>
{hasUserId ? 'UnFollow' : 'Follow' }
</Button>
</form>
</div>
);
};
const SingleUser = (props) => {
const { user, setUser } = props;
const [dynamicUser, setDynamicUser] = useState({});
const [isLoading, setIsLoading] = useState(true);
const classes = useStyles();
useEffect(() => {
return new Promise(async (resolve, reject) => {
try {
setIsLoading(true);
const url = `${CONST.SERVER_URL}/users/${props.match.params.userId}`
const headers = { authorization: localStorage.getItem(CONST.ACCESS_TOKEN) }
const response = await axios.get(url, { headers })
setDynamicUser(response.data);
setIsLoading(false);
resolve()
} catch (err) {
console.log(err.response.data);
reject(err)
}
})
// im not sure what this part does, since useEffect is never called
// however using promises is much easier to troubleshoot bugged out code
}, [props.match.params.userId]);
if (isLoading) {
return <LoadingComponent />;
}
console.log("DYNAMIC", dynamicUser);
return (
<div>
<Grid className={classes.button} raise elevation={3}>
<Follow userId={dynamicUser._id} user={user} setUser={setUser} />
</Grid>
</div>
)
}
so I ended up fixing this, and the problem was actually on the backend, cause I was sending the wrong user, but now everything works. I'll still will put my code here cause there seem to be a problem with the unFollow.
//follow user
router.put("/:userId/follow", isLoggedIn, async (req, res) => {
try {
const user = await User.findById(req.params.userId);
const currentUser = await User.findById(req.body.user._id);
const follow = await User.findByIdAndUpdate(
currentUser,
{ $addToSet: { following: user } },
{ new: true }
);
await User.findByIdAndUpdate(user, {
$addToSet: { followers: currentUser },
});
return res.json(follow);
} catch (err) {
return res.status(500).json({ error: err });
}
});
//unFollow the user
router.put("/:userId/unFollow", isLoggedIn, async (req, res) => {
try {
const user = await User.findById(req.params.userId);
const currentUser = await User.findById(req.body.user._id);
const follow = await User.findByIdAndUpdate(
currentUser,
{ $pull: { following: user } },
{ new: true }
);
await User.findByIdAndUpdate(user, {
$pull: { followers: currentUser },
});
return res.json(follow);
} catch (err) {
return res.status(500).json({ error: err });
}
});
Just to make it clear router uses the code below and my messages.js are inside api folder....
router.use("/messages", require("./messages"));
so my api call is correct.
Backend for posting the message.... I know conversationId will be null if no conversation exists but... I am trying to send message where conversation exists already and still I am getting cannot read the conversationId of undefined....
// expects {recipientId, text, conversationId } in body
// (conversationId will be null if no conversation exists yet)
router.post("/", async (req, res, next) => {
try {
if (!req.user) {
return res.sendStatus(401);
}
const senderId = req.user.id;
const { recipientId, text, conversationId, sender } = req.body;
// if we already know conversation id, we can save time and just add it to message and return
if (conversationId) {
const message = await Message.create({ senderId, text, conversationId });
return res.json({ message, sender });
}
// if we don't have conversation id, find a conversation to make sure it doesn't already exist
let conversation = await Conversation.findConversation(
senderId,
recipientId
);
if (!conversation) {
// create conversation
conversation = await Conversation.create({
user1Id: senderId,
user2Id: recipientId,
});
if (onlineUsers.includes(sender.id)) {
sender.online = true;
}
}
const message = await Message.create({
senderId,
text,
conversationId: conversation.id,
});
res.json({ message, sender });
} catch (error) {
next(error);
}
});
module.exports = router;
This is the frontend that posts the data to the backend....
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
Okay so here is detail information on how I am dispatching it.
class Input extends Component {
constructor(props) {
super(props);
this.state = {
text: "",
};
}
handleChange = (event) => {
this.setState({
text: event.target.value,
});
};
handleSubmit = async (event) => {
event.preventDefault();
// add sender user info if posting to a brand new convo,
// so that the other user will have access to username, profile pic, etc.
const reqBody = {
text: event.target.text.value,
recipientId: this.props.otherUser.id,
conversationId: this.props.conversationId,
sender: this.props.conversationId ? null : this.props.user,
};
await this.props.postMessage(reqBody);
this.setState({
text: "",
});
};
render() {
const { classes } = this.props;
return (
<form className={classes.root} onSubmit={this.handleSubmit}>
<FormControl fullWidth hiddenLabel>
<FilledInput
classes={{ root: classes.input }}
disableUnderline
placeholder="Type something..."
value={this.state.text}
name="text"
onChange={this.handleChange}
/>
</FormControl>
</form>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(Input));
const mapDispatchToProps = (dispatch) => {
return {
postMessage: (message) => {
dispatch(postMessage(message));
},
};
};
// message format to send: {recipientId, text, conversationId}
// conversationId will be set to null if its a brand new conversation
export const postMessage = (body) => (dispatch) => {
try {
const data = saveMessage(body);
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
So I have attached what I want to do here now....
But I am still getting the problem....
// CONVERSATIONS THUNK CREATORS, this is how I am getting data from the backend
export const fetchConversations = () => async (dispatch) => {
try {
const { data } = await axios.get("/api/conversations");
dispatch(gotConversations(data));
} catch (error) {
console.error(error);
}
};
export const setNewMessage = (message, sender) => {
return {
type: SET_MESSAGE,
payload: { message, sender: sender || null },
};
};
// REDUCER
const reducer = (state = [], action) => {
switch (action.type) {
case GET_CONVERSATIONS:
return action.conversations;
case SET_MESSAGE:
return addMessageToStore(state, action.payload);
case ADD_CONVERSATION:
return addNewConvoToStore(
state,
action.payload.recipientId,
action.payload.newMessage
);
default:
return state;
}
};
I am getting an error saying Cannot read property 'conversationId' of undefined while using a reducer function... Should I give the setintial value of the message to empty?
export const addMessageToStore = (state, payload) => {
const { message, sender } = payload;
// if sender isn't null, that means the message needs to be put in a brand new convo
if (sender !== null) {
const newConvo = {
id: message.conversationId,
otherUser: sender,
messages: [message],
};
newConvo.latestMessageText = message.text;
return [newConvo, ...state];
}
return state.map((convo) => {
if (convo.id === message.conversationId) {
const convoCopy = { ...convo };
convoCopy.messages.push(message);
convoCopy.latestMessageText = message.text;
return convoCopy;
} else {
return convo;
}
});
};
Issue
The saveMessage function is declared async
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
but the postMessage action creator isn't async so it doesn't wait for the implicitly returned Promise to resolve before continuing on and dispatching to the store. This means that data.message is undefined since a Promise object doesn't have this as a property.
export const postMessage = (body) => (dispatch) => {
try {
const data = saveMessage(body); // <-- no waiting
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
Solution
Declare postMessage async as well and await the data response value.
export const postMessage = (body) => async (dispatch) => {
try {
const data = await saveMessage(body); // <-- await response
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
I have built a feature that allows a logged in User to update their email. When I change the email and redirect to a different page the email that is displayed is the old email, and only when I refresh the page does the new email appear. I believe the the best way to address this is for the system to re-authenticate the User (I may be wrong and am open to suggestions).
// UpdateEmail.js
handleSubmit(e) {
e.preventDefault()
this.props.updateEmail(this.state.newEmail)
this.props.history.push('/settings')
}
const mapDispatchToProps = (dispatch) => {
return {
updateEmail: (newEmail) => dispatch(updateEmail(newEmail))
}
}
// authActions.js *NEW*
export const updateEmail = (newEmail, oldEmail, password) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
const firebase = getFirebase();
let user = firebase.auth().currentUser
let credential = firebase.auth.EmailAuthProvider.credential(oldEmail, password);
user.reauthenticateAndRetrieveDataWithCredential(credential)
.then(() => {
user.updateEmail(
newEmail
).then(() => {
dispatch({ type: 'UPDATE_LOGIN_DETAILS_SUCCESS'})
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
dispatch({
type: 'CHECK_REAUTH_SUCCESS',
user: user
})
} else {
dispatch({ type: 'CHECK_REAUTH_ERROR'})
}
});
})
.catch(err => {
dispatch({ type: 'UPDATE_LOGIN_DETAILS_ERROR'}, err)
})
})
.catch(err => {
dispatch({ type: 'REAUTH_ERROR'}, err)
})
}
}
// Settings.js this is where the user is redirected after CHECK_REAUTH_SUCCESS
const mapStateToProps = (state) => {
return {
licenses: state.firestore.ordered.licenses,
aircraft: state.firestore.ordered.aircraft,
profile: state.firebase.profile,
auth: state.firebase.auth
}
}
// authActions.js *OLD*
export const updateEmail = (newEmail) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
const firebase = getFirebase();
const user = firebase.auth().currentUser
user.updateEmail(
newEmail
).then(() => {
dispatch({ type: 'UPDATE_LOGIN_EMAIL_SUCCESS'})
}).catch(err => {
dispatch({ type: 'UPDATE_LOGIN_EMAIL_ERROR', err })
})
}
}