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.
Related
I'm trying to sign in and setting the current user. The problem is that the login is successful, the data is correct but I can't set the state, the user is empty.
UserContext.js
import React, { useContext, useState } from 'react';
const UserContext = React.createContext();
export const useAuth = () => {
return useContext(UserContext);
}
export const UserContextProvider = ( {children} ) => {
const [ user, setUser ] = useState({
name: '',
lastname: '',
username: '',
password: ''
});
const [ validation, setValidation ] = useState({
username: '',
password: ''
});
const setUserData = (e) => {
return ( {target: {value}} ) => {
setUser(data => ( {...data, [e]: value} ));
}
}
const setUserValidation = (e) => {
return ( {target: {value}} ) => {
setValidation(data => ( {...data, [e]: value} ));
}
}
const signUp = async () => {
return await fetch('http://localhost:8080/users/signup', {
method: 'POST',
body: JSON.stringify(user),
headers: { 'Content-Type': 'application/json' }
});
}
const signIn = async () => {
return await fetch('http://localhost:8080/users/signin', {
method: 'POST',
body: JSON.stringify(validation),
headers: { 'Content-Type': 'application/json' }
}).then((res) => res.json())
.then(data => {
console.log(data);
setUser({
name: data.name,
lastname: data.lastname,
username: data.username,
password: data.password
});
console.log(user);
});
}
const signOut = async () => {
await fetch('http://localhost:8080/users/signout');
setUser(null);
return;
}
return (
<UserContext.Provider value={{
user,
setUserData,
setUserValidation,
signUp,
signIn,
signOut
}}>
{ children }
</UserContext.Provider>
);
}
SignIn.js
import './SignIn.css';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/UserContext';
const SignIn = () => {
const { signIn, setUserValidation, user } = useAuth();
const [ errorMessage, setErrorMessage ] = useState(null);
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage(null);
signIn().then(() => {
console.log(user);
setErrorMessage(null);
navigate('/');
}).catch(err => {
setErrorMessage('Error singing in, please try again.', err);
});
}
return (
<div className='SignIn'>
<h3>Sign In</h3>
<form className='LoginForm' onSubmit={handleSubmit}>
{ errorMessage && <h4 className='ErrorMessage'>{errorMessage}</h4> }
<input type='email' name='username' placeholder='Email' onChange={setUserValidation('username')} required/>
<input type='password' name='password' placeholder='Password' onChange={setUserValidation('password')} required/>
<button className='Login' type='submit'>Sign In</button>
</form>
<h5>Don't have an account?</h5><Link className='Redirect' to='/signup'>Sign Up</Link>
</div>
);
}
export default SignIn;
As you can see, the first console.log shows the correct user information, but then is empty after the setUser() and in the SignIn.js component.
This is a normal behavior. You can't access state directly after set state. The updated state will only be available on next render. So do about context.
This is what you can do if you need to access user data directly after SignIn().
UserContext.js
const signIn = async () => {
return await fetch('http://localhost:8080/users/signin', {
method: 'POST',
body: JSON.stringify(validation),
headers: { 'Content-Type': 'application/json' }
}).then((res) => res.json())
.then(data => {
console.log(data);
const usr = {
name: data.name,
lastname: data.lastname,
username: data.username,
password: data.password
}
setUser(usr);
// console.log(user); <-- You can't do this
return usr // <--
});
}
SignIn.js
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage(null);
signIn().then((usr) => { // <---
console.log(usr);
// console.log(user); <-- You can't do this
setErrorMessage(null);
navigate('/');
}).catch(err => {
setErrorMessage('Error singing in, please try again.', err);
});
}
I guess what could be happening here. I have no time to test my guess, so please forgive me if I'm saying something wrong.
When you change a state in a context, the provider and its children are re-rendered. In this case, the UserContextProvider and its children.
So first thing, please be sure that SignIn is rendered inside a UserContextProvider, e.g. embedding all the app inside a . I generally do this in the index.js file.
ReactDOM.render(
<UserContextProvider>
{/* ... app here, that includes SignIn ... */}
</UserContextProvider>,
document.getElementById('root')
);
Second thing, since you are including the console.log() so that are executed in the same rendering in which you change the state, it's clear that they won't reflect the new value that will be available in the successive rendering only.
I suggest that you put the console.log(user) at the beginning of the SignIn component, say immediately after useNavigate(), outside the handleSubmit function.
const SignIn = () => {
const { signIn, setUserValidation, user } = useAuth();
const [ errorMessage, setErrorMessage ] = useState(null);
const navigate = useNavigate();
console.log(user)
// ...ecc...
}
If I'm right, this console.log will be executed (at least) twice, one for the initial rendering, one for the subsequent rendering triggered by setUser (you can also include a console.log in the handleSubmit just to detect the re-rendering triggered by setUser). In the last rendering, you should see the user data.
If this works as I expect, I guess that you can handle the signIn with something like this
const SignIn = () => {
const { signIn, setUserValidation, user } = useAuth();
const [ errorMessage, setErrorMessage ] = useState(null);
const navigate = useNavigate();
// in the first rendering, the userName will be '', so it won't navigate
// if the component is re-rendered after the setUser in signIn,
// in this rendering there will be a userName, hence the navigation will proceed
if (user.userName !== '') {
navigate('/');
}
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage(null);
signIn().catch(err => {
setErrorMessage('Error singing in, please try again.', err);
});
}
return (
// ... as before ...
);
}
Happy coding! - Carlos
I want to passing data between components but I have a problem. Not getting any error as long as I don't passing data yet, it's fine. When I try to show the props in the console, I can easily see what I want (history,match,location,AuthStore). But when I try to pass the data, I can only see value and AuthStore in the console and value returns empty. Where am I wrong?
front.layout.js
import Profile from '../../Views/profile'
const Layout = (props) => {
const [user, setUser] = useState({});
const [isLoggedIn, setIsLoggedIn] = useState(false);
props.AuthStore.getToken();
const history = useHistory();
useEffect(() => {
const token =
props.AuthStore.appState != null
? props.AuthStore.appState.user.access_token
: null;
axios
.post(
"/api/authenticate",
{},
{
headers: {
Authorization: "Bearer " + token,
},
}
)
.then((res) => {
if (!res.data.isLoggedIn) {
history.push("/login");
}
setUser(res.data.user);
setIsLoggedIn(res.data.isLoggedIn);
})
.catch((error) => {
history.push("/login");
});
}, []);
return (
<>
<Profile value={user} />
</>
)
index.js
const Profile = (props) => {
console.log(props);
const { params } = props.match;
const [data, setData] = useState({});
const history = useHistory();
if(props.location.key){
useEffect(() => {
axios
.get(
`/api/${params.username}`,
{
headers: {
Authorization:
"Bearer " +
props.AuthStore.appState.user.access_token,
},
}
)
.then((res) => {
if (res.data.username) {
setData(res.data);
}
})
.catch((error) => {
console.log(error);
});
}, []);
}
I'm trying to update my user.cart which is array of objects. When I push new item in cart it's okay till I reload the page. How can I keep the state updated ?
Here is my function:
const {user, setUser} = useContext(UserContext);
const addToCart = (userId, product) => {
fetch(`${API}/cart/usercart`, {
method:"POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify([userId, product])
})
.then(() => {
const newArr = user.cart.concat(product)
setUser(oldState => ({
...oldState,
cart: newArr
}))
})
.catch(error => console.log(error))
}
Here is my UserContext:
const UserContextProvider = (props) => {
const [user, setUser] = useState({})
useEffect(() => {
fetch(`${API}/auth/user`, {
method: 'GET',
withCredentials: true,
credentials: 'include'
})
.then (response => response.json())
.then (response => {
setUser(response.user)
})
.catch (error => {
console.error (error);
});
}, [setUser])
return (
<UserProvider value={{user, setUser}}>
{props.children}
</UserProvider>
)
}
export default UserContextProvider;
You need to go through this before the question https://www.freecodecamp.org/news/state-management-with-react-hooks/
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
I'm trying to use context to create a authentication provider for my app. Following a guide for Authentication with react router and react hooks. It's using a memo to fetch authentication if auth changes.
This is the guide i was following
https://medium.com/trabe/implementing-private-routes-with-react-router-and-hooks-ed38d0cf93d5
I've tried looking up the error and working around the memo to no avail.
Here is my provider
const AuthDataContext = createContext(null)
const initialAuthData = {}
const AuthDataProvider = props => {
const [authData, setAuthData] = useState(initialAuthData)
useEffect(() => {
async function getAuth(){
let currentAuthData = await fetch("URL/api/v1/logged_in", {
method: "GET",
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(res => res.json())
.then(data => (data))
.catch(error => console.error("Error:", error));
if(currentAuthData) {
setAuthData(currentAuthData)
}
};
getAuth()
},[])
const onLogout = () => setAuthData(initialAuthData);
const onLogin = newAuthData => setAuthData(newAuthData);
const authDataValue = useMemo({ ...authData, onLogin, onLogout }, [authData]);
return <AuthDataContext.Provider value={authDataValue} {...props} />;
};
export const useAuthDataContext = () => useContext(AuthDataContext);
The PrivateRoute component
const PrivateRoute = ({ component, ...options }) => {
const { user } = useAuthDataContext();
const finalComponent = user ? component : SignInPage;
return <Route {...options} component={finalComponent} />;
};
The AuthProvider wrapping my app
const MyApp = props => (
<BrowserRouter>
<AuthDataProvider>
<App />
</AuthDataProvider>
</BrowserRouter>
);
I expected the auth provider to fetch the user and Logged_In status and provide it to child components. The private route to see the authData from the context api and only render the route if the user is logged in.
Thanks in advance!
The useMemo is throwing this error
nextCreate is not a function
mountMemo
13962 | var hook = mountWorkInProgressHook();
13963 | var nextDeps = deps === undefined ? null : deps;
> 13964 | var nextValue = nextCreate();
| ^ 13965 | hook.memoizedState = [nextValue, nextDeps];
13966 | return nextValue;
13967 | }
View compiled
The "nextCreate is not a function" error is thrown when you're not passing a function as a useMemo() argument.
For example, useMemo(123, []) will throw and useMemo(() => 123, []) won't.
You don't need to use useMemo. It will duplicate what Provider already accomplishes.
Instead
return <AuthDataContext.Provider value={{ authData, onLogin, onLogout }} {...props} />;