I have an api.js file with the following function:
export const login = (loginUserName, loginPassword, setUser) => {
axios({
method: 'post',
data: {
username: loginUserName,
password: loginPassword
},
withCredentials: true,
url: '...........'
}).then(async (res) => {
if (res.data.loggedIn === true) setUser(loginUserName);
});
};
My useState hooks looks the following:
const [ user, setUser ] = useState({});
I was wondering, instead of sending the setUser function from my Login component like I do:
login(userName,password,setUser);
which is in another file, how can I do it from outside of the function like the following:
setUser(login(userName,password));
So in case the function did retrieve the information I wanted it'll return it (in this case the username), and in case not it will set the user as undefined or something.
I tried using
export const login = (loginUserName, loginPassword) => {
axios({
method: 'post',
data: {
username: loginUserName,
password: loginPassword
},
withCredentials: true,
url: '............'
}).then(async (res) => {
if (res.data.loggedIn === true) return loginUserName;
});
};
But I had some struggling with the async function. It seems like it wont return that information for some reason. if I had to guess it returns it only for the axios function or something.. How do I get the effect I am looking for and what's the best practice? Is sending the "setUser" function considered a good practice or is it better trying to get rid of it?
CodePudding user response:
So there's a few different (but related, valid) things going on here. First off, this
setUser(login(userName,password));
isn't going to work. You can't synchronously talk to the server. Your current method of passing the setter from useState
as a callback is fine. Really. There is absolutely nothing wrong with doing that.
But I wouldn't call it a best practice either. "Best practice" is a little subjective, but here's another way you could do this:
// useUser.js
import React, { createContext, useContext, useState, useEffect } from 'react'
// could be in another file and imported, whatever.
const login = async (userName, userPass) => {
const data = await axios(...);
return data.loggedIn ? data : null; // could also throw
};
const UserContext = createContext([]); // user data and setter, eventually
const UserProvider = ({ children }) => {
const userState = useState({});
return (<UserContext.Provider value={userState}>
{children}
</UserContext.Provider>);
};
export const useUser = () => {
const [userData] = useContext(UserContext);
return userData
}
export useLogin = (userName = '', userPass = '') => {
const [_, setUserData] = useContext(UserContext);
useEffect(() => {
if (userName && userPass) {
login(userName, userPass)
.then(data => {
if (data) setUserData(data);
}); // add .catch to handle errors
}
}, [userName, userPass, setUserData])
}
Then, in your App.js
import React from 'react';
import { UserProvider } from 'path/to/file.js';
function App({ children }) {
// code
return <UserProvider>{children}</UserProvider>;
}
In any component that relies on user data you can now
import React from 'react';
import { useUser } from 'path/to/file.js';
export default (props) => {
const userData = useUser();
return <div>{userData.name || 'loading'}</div>;
}
in your login form you can do
import React, { useState } from 'react';
import { useLogin } from 'path/to/file.js';
export default (props) => {
const [userName, setUserName] = useState('');
const [userPass, setUserPass] = useState('');
useLogin(userName, userPass);
return <form>...</form>;
};
By creating the context and the custom hooks, now any component in the tree can have access to the user data just by calling the hook. You don't want to go hog-wild with this, but user data is often used at various levels and it's tedious to pass it through all the intermediate components as props. This pattern can be abstracted, and while it may seem like a lot of code it's easier on your page weight than all of redux.
Because we don't export the context object itself or the user data setter, you don't have to worry about consumers accidentally modifying something they shouldn't, here we only expose the hooks which function like an API with a setter (useLogin) and a getter (useUser).
You can also move the context provider further down the tree (or ever further up, you could wrap your App component in it), but that depends on where you need to use it.
CodePudding user response:
Your setUser
function is only valid inside a component that declares it, so yes, simply return the fetch result and handle setting the state inside that particular component.
And the reason you don't get anything returned could be either the request didn't succeed (check your browser's network tab and the relevant response) or that you didn't await
the result (fetching is always an asonchrynous operation, so you have to expect the response with a delay).
You could do sth like this:
useEffect(() => {
(async () => {
const username = await login(userName,password,setUser);
setUser(username);
})();
}, []);
CodePudding user response:
If I need a state object that I need to use along the flow of my app, like a user object that I'm planning to pass to most child components. Then I'd rather use Redux library to create a store that I can access in all components.
There would be a lot to do to get it work, so I'd be careful while reading the Documentation, but it's worth the work with the given tools.
You can then access/manipulate store states like the following:
import React, { useState } from 'react';
import { useAppSelector, useAppDispatch } from 'app/hooks';
import { decrement, increment } from './counterSlice';
function login(loginUserName, loginPassword) {
axios({
method: 'post',
data: {
username: loginUserName,
password: loginPassword
},
withCredentials: true,
url: '............'
}).then(async (res) => {
if (res.data.loggedIn === true) dispatch(setUser(loginUserName));
};
}
export function Login() {
const count = useAppSelector((state) => state.user.value);
const dispatch = useAppDispatch();
login("User Name", "********");
}