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
Related
Hi everyone I am currently facing a type error in my login file as I am not familiar with typescript, when trying to set the response from the api into the user variable inside my handleLogin function and user is represent my context, I am getting this error this expression is not callable. Type '{}' has no call signatures.ts(2349).
Here below are my login.tsx and authContext.tsx files:
Login.tsx:
function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [showError, setShowError] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [user, setUser] = useContext(UserContext);
const handleLogin = (e: any) => {
e.preventDefault()
setIsSubmitting(true)
setError('')
const genericErrorMessage = 'Something went wrong! Please try again later.'
fetch('http://localhost:8081/users/login', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
})
.then(async response => {
setIsSubmitting(false)
if (!response.ok) {
if (response.status === 400) {
setError("Please fill all the fields correctly!")
} else if (response.status === 401) {
setShowError(true)
setError("Invalid username and password combination.")
} else {
setError(genericErrorMessage)
}
} else {
const data = await response.json()
setUser((oldValues: any) => {
return { ...oldValues, token: data.token }
})
workflow.go(Workflow.Tasks)
}
})
.catch(error => {
setIsSubmitting(false)
setError(genericErrorMessage)
})
}
const handleKeyDown: React.KeyboardEventHandler<HTMLInputElement> = ({ keyCode }) => {
setShowError(false);
if (keyCode === 13) handleLogin();
};
authContext.tsx:
import React, { PropsWithChildren, useState } from "react"
const UserContext = React.createContext([{}, () => { }])
let initialState = {
username: '',
password: '',
}
const UserProvider = (props: PropsWithChildren<any>) => {
const [state, setState] = useState(initialState)
return (
<UserContext.Provider value={[state, setState]}>
{props.children}
</UserContext.Provider>
)
}
export { UserContext, UserProvider }
In this line you're creating context and setting as default value an array containing an empty object and a function that returns an empty object:
const UserContext = React.createContext([{}, () => { }])
Typescript infers that this is the type for the value you're going to be using in this context, which is why it is showing you the error message.
This is not, however, how you should declare a context. You can either give it a meaningful initial value or just null, "", []... if you don't know it beforehand, but if you do the latter you must pass the correct types to the createContext function.
In your case it could be something like this:
interface User {
username: string;
password: string;
}
const initialState = {
username: '',
password: '',
}
const UserContext = React.createContext<
[User, React.Dispatch<React.SetStateAction<User>> | null]
>([initialState, null])
// I'd recommend you type children as React.ReactNode
const UserProvider = ({ children }: { children: React.ReactNode }) => {
const [state, setState] = useState(initialState)
return (
<UserContext.Provider value={[state, setState]}>
{children}
</UserContext.Provider>
)
}
export { UserContext, UserProvider }
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.
I am working on a login page and trying to store my data in context. But i get response from the database as undefined.
I think i am doing something wrong. Help please
Here's my code snippet
"AUTH CONTEXT" (The auth Context file returns undefined as response)
import { signIn as signInApi } from '../apis'
const AuthContext = createContext()
export const AuthProvider = ({ children }) => {
const [token, setToken] = useState(localStorage.getItem('token'))
const [user, setUser] = useState(localStorage.getItem('user'))
const [loading, setLoading] = useState(false)
const signIn = async (email, password, callback) => {
setLoading(true)
const res = await signInApi(email, password)
console.log(res)
......
const value = {
token,
loading,
signIn,
signOut,
}
}
export default AuthContext
APIS.JS (The API.js file below returns response data from the database)
import axios from 'axios'
export const signIn = async (email, password) => {
try {
const res = await axios.post(
`${process.env.REACT_APP_API}/auth/login`,
{
email,
password,
},
{
headers: {
'Content-Type': 'application/json',
},
}
)
} catch (error) {
console.log(error)
}
}
LOGIN FILE
const auth = useContext(AuthContext)
const handleLogin = (e) => {
e.preventDefault()
auth.signIn(email, password, () => history.replace('/admin'))
}
you didnot return response from your signIn function in API.js file
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))
i try to create a Login Screen with the Auth function and navigation in a seperate file but i always get an error by the navigation. I tried to pass props but it dont work...
Can you please help me?
This is my code:
App.js
export default class App extends React.Component{
render(){
return(
<RootStack navigation={this.props.navigation} />
)
}
}
rootstack.js
const StackNavigator = createStackNavigator({
Login: {
screen: login,
navigationOptions: {
headerShown: false,
}
},
Home: {
screen: TaskScreen,
navigationOptions: {
headerShown: false,
}
}
})
export default createAppContainer(StackNavigator);
Login.js
...
<Auth props={this.props}/>
...
auth.js
export function Auth() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const auth = (props) => {
fetch('http://192.168.178.26:8000/auth/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password })
})
.then(res => res.json())
.then(res => {
saveToken(res.token);
console.log(res.token);
props.navigation.navigate('Home'); # Here i get the error
})
.catch(error => console.log(error));
};
const saveToken = async (token) => {
await AsyncStorage.setItem('IN_Token', token)
};
...
Can you please help me?
Oh sorry, i forgot to add the Error Message:
undefined is not an object (evaluating 'props.navigation.navigate')
As you are using function component you should pass the props as params to the function component to access them
So, in your auth.js just send props in the params of the function
export function Auth(props) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const auth = (props) => {
fetch('http://192.168.178.26:8000/auth/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password })
})
.then(res => res.json())
.then(res => {
saveToken(res.token);
console.log(res.token);
props.props.navigation.navigate('Home'); # Here i get the error
})
.catch(error => console.log(error));
};
const saveToken = async (token) => {
await AsyncStorage.setItem('IN_Token', token)
};
or
const auth = ({props}) => {
...
.then(res => res.json())
.then(res => {
saveToken(res.token);
console.log(res.token);
props.navigation.navigate('Home'); # Here i get the error
})
This should work!
you are not getting the props in the top of the Auth component, you need to get the props in the declaration, not on the function above.
you should declare Auth like this export function Auth(props)
Another thing is: you are passing Auth props={this.props} so your props object inside Auth probably is something like this: props.props.navigation. You can use spread operator to avoid this <Auth {...this.props}/>
This should work.
Another approach you can do and i like more is the Auth function returns a callback to Login.js and inside Login.js you do the navigation.