I have a global token variable that I want to update whenever I make an API request with axios. The problem that I am having is how to update the the token variable since the axios request is not made in a functional component, so I am not able to take advantage of React hooks when making such request.
const logInUser = async (usernameOrEmail, password) => {
//const myContext = useContext(AppContext);
//^ gives an error b/c it's a react hook
axios
.post(
`https://jellybackend.herokuapp.com/authenticate`, {
username: usernameOrEmail,
password: password,
})
.then((response) => {
//myContext.setToken(response.data.token); //update token set , error since not a functional component
console.log(response);
tokenGlobal = response.data.token
})
.catch((error) =>
console.log(error)
);
};
I am making my token a global state, so I have the hook defined in App.js, as seen below
export default function App() {
//define global variable of token
const [token, setToken] = useState("");
const userSettings = {
token: "",
setToken,
};
return (
<AppContext.Provider value={userSettings}>
...
</AppContext.Provider>
);
}
Any idea on how to update my global state variable, or how to refactor my code would be very appreciated!
What I want eventually to happen is that whenever a user logs in, the token is updated, since that is what is returned from the axios post request.
The button below is how a user logs in
function LoginScreen(props) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const myContext = useContext(AppContext);
return (
...
<Button
onPress={ () => {logInUser(email, password);} //I want to update the token here...
w="40%"
py="4"
style={styles.button}
>
A very simple and trivial refactor would be to pass callback functions to the logInUser utility.
Example:
const logInUser = async (usernameOrEmail, password, onSuccess, onFailure) => {
axios
.post(
`https://jellybackend.herokuapp.com/authenticate`, {
username: usernameOrEmail,
password: password,
})
.then((response) => {
onSuccess(response);
console.log(response);
})
.catch((error) =>
onFailure(error);
console.log(error);
);
};
...
function LoginScreen(props) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const myContext = useContext(AppContext);
const successHandler = response => myContext.setToken(response.data.token);
const failureHandler = error => myContext.setToken(null);
return (
...
<Button
onPress={() => logInUser(email, password, successHandler, failureHandler)}
w="40%"
py="4"
style={styles.button}
>
...
</Button>
...
);
}
You could setup your axios call in a module that can then return the value that you would like to store in global state.
Your axios call doesn't have to exist within a functional component per se, but it would need to be imported/called within one for this solution.
So, you could change your axios call to be within a module function that could then be exported, say globalsapi.js, then imported to your functional component:
exports.logInUser = async () => {
const globalData = await axios
.post(
`https://jellybackend.herokuapp.com/authenticate`, {
username: usernameOrEmail,
password: password,
});
const token = await globalData.data.token;
return token;
}
Now, wherever you decide to call the setToken state update, you can just import the function and set the global token:
import { logInUser } from './globalsapi';
logInUser().then(data => {
myContext.setToken(data);
});
You could pass whatever parameters needed to the logInUser function.
Related
I want to make a Logout function when the token has expired. There is an AuthProvider in my application:
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({ token: localStorage.getItem("access_token") });
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
Now that the token has expired I need to call the setAuth hook and write an empty token there:
const logout = () =>{
const axiosInstance = axios.create({
withCredentials: true
})
axiosInstance.get("http://localhost:8080/api/auth/logout")
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error.config);
});
window.location.href = '/auth'
};
const Logout = () => {
const {auth,setAuth} = useAuth();
const token = '';
setAuth({token});
localStorage.removeItem("access_token");
localStorage.clear();
logout()
};
export default Logout;
I am exporting this function in another file and want to call if the backend returns a response about an expired token.
const getStockData = async () => {
return instance.get(`/api/stock/symbols/${slug}`);
}
useEffect(() => {
(async () => {
const response = await getStockData();
console.log(response)
const data = response.data;
const stockInfo = data.chart.result[0];
console.log(stockInfo);
setPrice(stockInfo.meta.regularMarketPrice.toFixed(2));
setPriceTime(new Date(stockInfo.meta.regularMarketTime * 1000));
setSymbol(stockInfo.meta.symbol);
const quote = stockInfo.indicators.quote[0];
const prices = stockInfo.timestamp.map((timestamp, index) => ({
x: new Date(timestamp * 1000),
y: [quote.open[index], quote.high[index], quote.low[index], quote.close[index]].map(round)
}));
setPriceInfo([{
data: prices,
}]);
setStockData({ data });
})().catch(
(error) =>{
Logout()
}
);
}, []);
Here getStockData can return 403 if the token has expired.
But of course I get an error saying that the hook can't be used in a function like that. And I can't find a solution how to wrap or to do something similar so that this function can be called?
React doesn't let you initialize hooks inside of non-component functions. Instead, you can initialize the hook on the component level and let whatever function needs the hook's values to accept them as arguments.
const Logout = (auth, setAuth) => {
const token = '';
setAuth({token});
localStorage.removeItem("access_token");
localStorage.clear();
logout()
};
export default Logout;
// Initialize the hook at the component level
const {auth, setAuth} = useAuth();
.catch(
(error) =>{
// then pass the values from above to this function
Logout(auth, setAuth)
}
);
const Login=(props)=>{
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [user, setUser] = useState({});
const login = (e)=>{
e.preventDefault();
axios.post('/login',{email, password})
.then(response => {
setUser(response.data)
console.log(response.data, user, "user data");
}).catch(err => {
console.log(err);
})
}
this is the empty user data i
this is my code for the login request its fetching the data and logging it in the console but it doesnt want to set the user
Because setUser is async. So the new state only update when component rerender. You can check by move console.log outside the login function
console.log(user, "user data");
return(...)
You can use useEffect to check state after component rerender:
useEffect(() => {
console.log(user);
}, [user])
I have a functional component that that has an input field where the user types a question and hits enter and I send the query to the backend.
Here is the simplified version of the functional component
UserQuery.js
import {postQuery} from '../actions/postQueryAction'
const UserQuery = () => {
const [name, setName] = useState("")
function sendMessage(userQuery) {
postUserQuery(userQuery)
}
return (
<>
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
onKeyPress={sendMessage}
id="userQuery"
/>
</>
)
}
export default UserQuery
As you can see I have a callback called postQuery which actually makes the axios request and posts the user query. Here is how it looks like
postQueryAction.js
export const postQuery = (userQuery) => async dispatch => {
let userInfo = useSelector(state => state.userInfo.data)
const username = userInfo.username
const group = userInfo.group
const headers = {
'Content-Type': 'application/json;charset=UTF-8',
}
const params = {
group: group,
user: username,
data: userQuery
}
await axios.post(`/postQuestion`,params, {headers}, {
}).then(response => {
console.log("response check", response.data);
})
.catch(err => {
console.log("Error log", err);
});
}
But I get Invalid hook call error. If I remove useSelector code, then it doesn't complain and the request goes through.
I could use the useSelector in original functional component (UserQuery.js) and pass the parameters accordingly. But I want the postQuery method to only accept the userQuery parameter and figure the other information from the redux state.
What should I do?
Hooks can only be called from react components or other hooks. Instead of using the hook inside the postQuery function you could call it in the component and pass the user info to postQuery
const UserQuery = () => {
const [name, setName] = useState("");
let userInfo = useSelector((state) => state.userInfo.data);
function sendMessage(userQuery) {
postUserQuery(userQuery, userInfo);
}
...
export const postQuery = (userQuery, userInfo) => async (dispatch) => {
...
I have a react class component with rather lengthy onSubmit function that I have put into another file in order to keep the code a bit tidier.
I tried to convert the class component to a functional one, replacing all of my state and setState functions with useState but now my useState state updaters are returning undefined inside the imported function. Am I able to update state using an imported function with a functional component? The function worked fine when it was imported into a class component and my state updater was setState();
//Imported function in utils.js
export const loginUser = async function (email, password) {
try {
const login = await axios.post('http://localhost:5000/api/v1/auth/login', {
email,
password
});
const options = {
headers: {
Authorization: `Bearer ${login.data.token}`
}
};
const getUser = await axios.get(
'http://localhost:5000/api/v1/auth/me',
options
);
const user = getUser.data.data;
setAuthenticated(true);
setUser(getUser.data.data);
setEmail('');
setPassword('');
localStorage.setItem('user', JSON.stringify(user));
console.log(localStorage.getItem('user'));
} catch (err) {
console.log(err);
}
};
// Functional component with imported function
import React, { useState } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import { Login } from './Login';
const { loginUser } = require('../utils/utils');
export const Splash = () => {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
const [authenticated, setAuthenticated] = useState(false);
const [msg, setMsg] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const _handleEmail = (e) => {
setEmail(e.target.value);
};
const _handlePass = (e) => {
setPassword(e.target.value);
};
const _handleSubmit = async (e) => {
e.preventDefault();
loginUser(email, password);
if (user) {
console.log(user);
this.props.onHandleUser(user);
}
};
return (
<div className='splashStyle'>
{!authenticated && (
<Login
handleEmail={_handleEmail}
handlePass={_handlePass}
handleSubmit={_handleSubmit}
isAuthenticated={authenticated}
/>
)}
</div>
);
};d
EDIT: My issue that setAuthenticated, setUser, setEmail, and setPassword are coming undefined in utils.js
Thanks!
One way of achieving that would be passing all the set methods as a paramters to loginUser function.
But a better way of doing this will be like:
create two separate files
1 for login api call like :
login.js
function login(email, password){
const login = await axios.post('http://localhost:5000/api/v1/auth/login', {
email,
password
});
return login.data;
}
another for getting data
getProfile.js
function getProfile(token){
const options = {
headers: {
Authorization: `Bearer ${token}`
}
};
const getUser = await axios.get(
'http://localhost:5000/api/v1/auth/me',
options
);
return getUser.data;
}
Now do you setting state stuff in actuall component submit call function like
const _handleSubmit = async (e) => {
e.preventDefault();
const token = await login(email, password);
const user = await getProfile(token);
if (user) {
console.log(user);
props.onHandleUser(user);
setAuthenticated(true);
setUser(getUser.data.data);
setEmail('');
setPassword('');
localStorage.setItem('user', JSON.stringify(user));
console.log(localStorage.getItem('user'));
}
};
You need to pass the setAuthenticated function to the loginUser function before calling it in that.
return an onSubmiHandler function from your login user hook.
const doLogin = (email , password) => {
/// your code here
}
return {doLogin}
then use the doLogin function inside your main component
//inside functional component
const {doLogin} = loginUser();
onSubmit = () => doLogin(email, password)
for more you can see how to use custom hooks from here
https://reactjs.org/docs/hooks-custom.html
To start loginUser can't know about the setState you insert there try to pass it as arguments and it will fix it 😁
another problem I see is that you use the this keyword and in the functional component you use the just props.
and just for you to know don't pass null as the initial value pass an empty string, number, etc..
Update
this is how you also pass a setState as argument
loginUser((e)=>setEmail(e))
const LoginContainer = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const dispatch = useDispatch();
const onLoginClick = (e) => {
dispatch(userLogin);
dispatch(email);
};
return (
<>
<div className='content'>
<SectionLogin
onLoginClick={onLoginClick}
setEmail={setEmail}
setPassword={setPassword}
/>
</div>
</>
);
};
export default LoginContainer;
I want to access email in redux file.in my redux file, I have wrote actions and types. Give are welcome if any further modification is required
Redux Action file
export const userLogin = async (dispatch, action) => {
dispatch({ type: POST_LOGIN_DETAILS_START });
try {
const response = await fetch('http://localhost:5000/api/login', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: this.state.email,
password: this.state.password,
}),
});
const payload = await response.json();
dispatch({ type: POST_LOGIN_DETAILS_SUCCESS, payload });
} catch (error) {
dispatch({ type: POST_LOGIN_DETAILS_FAIL });
}
};
I want to access email and password in action file to send a post request
wrong: dispatch(email)
right: dispatch({ type: 'YOUR_ACTION_IDENTIFIER', payload: email })
see:
redux: dispatch an action
react-redux: useDispatch()
I have got the answer
import React, { useState } from 'react';
import { useDispatch } from 'react-redux'; import SectionLogin from './LoginPage'; import { userLogin } from './dux';
const LoginContainer = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const dispatch = useDispatch();
const onLoginClick = (e) => {
dispatch(userLogin);
dispatch(userLogin(email));// should pass variables as argument to the function
};
return (
<>
<div className='content'>
<SectionLogin
onLoginClick={onLoginClick}
setEmail={setEmail}
setPassword={setPassword}
/>
</div>
</>
);
};
export default LoginContainer;
Redux Action file
export const userLogin = (email) => async (dispatch) => {
//have to access email inside userLogin function
try {
//code here
}
catch (error) {
//code here
};
This is the best method if you want to send data to the redux file, by passing the variable as an argument