简体   繁体   中英

Best practice of setting state from axios request

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?

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.

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);
  })();
}, []);

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) {
    const dispatch = useAppDispatch();

    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() {
    login("User Name", "********");

    const user = useAppSelector((state) => state.user.value);
    console.log(user);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM