How can I make this a callable utils function that contains a hook?

岁酱吖の 提交于 2021-01-04 06:36:09

问题


I'm currently using Apollo Client's useQuery and useMutation hooks to perform the same function in several different components. Instead of repeating the logic, I would like to create a helper function that can keep the logic in a single place.

The problem with this, is useQuery and useMutation being hooks mean they can't just live in a separate file as functions, but instead need to be valid React function components.

I started trying to make something functional but starting to think this might not be able to be done.

  export function HandleFollow (props) {
      const [useFollowUser, { followedData, followError }] = useMutation(FOLLOW_USER);
      useFollowUser({ variables: { fromId: auth().currentUser.uid, toId: "userIDxyz"}, onCompleted: props.handleUserFollow(user, isFollowingAuthor)})
  }

Is it possible to use util/helper functions with React Hooks? Thanks in advance!

When creating the below, I'm receiving "Invalid hook call. Hooks can only be called inside of the body of a function component." and I can't figure out why.

I think that may be due to calling this from inside a function, but I can't figure out how to avoid that.

export function useHandleFollow(props) {
  const { user, isFollowingUser } = props
  const [useFollowUser, { followedData, followError }] = useMutation(FOLLOW_USER);
  const [useUnfollowUser, { unfollowedData, unfollowedError }] = useMutation(UNFOLLOW_USER);
  const [followSuccess, setFollowSuccess] = useState(false)
  
  if(!isFollowingUser){
    useFollowUser({ variables: { fromId: auth().currentUser.uid, toId: user.id}, onCompleted: setFollowSuccess(true)})
  }else{
    useUnfollowUser({ variables: { fromId: auth().currentUser.uid, toId: user.id}, onCompleted: setFollowSuccess(true)})
  }
  return followSuccess
}

And when I call this right away in my component (instead of calling from inside a function), it continues to infinitely re-render:

Here's some more complete code from the component

in Home.js:

const followed = useHandleFollow({ user: author, isFollowingAuthor })


export default  posts = (props) =>{
  let { post, excludeUser = false } = props
  let { author } = post
  let { date, things, isFollowingAuthor } = post
  let { profile_picture } = author
  let currentUser = auth().currentUser
  
  const [followed] = useHandleFollow({ user: author, isFollowingAuthor })
  
  return ()
}

And this is in follow.js, which I'm importing as import { useHandleFollow } from...

export function useHandleFollow(props) {
  const { user, isFollowingUser } = props
  const [followUser, { followedData, followError }] = useMutation(FOLLOW_USER);
  const [unfollowUser, { unfollowedData, unfollowedError }] = useMutation(UNFOLLOW_USER);
  const [followSuccess, setFollowSuccess] = useState(false)
  if(!isFollowingUser){
    followUser({ variables: { fromId: auth().currentUser.uid, toId: user.id}, onCompleted: () => setFollowSuccess(true)})
  }else{
    unfollowUser({ variables: { fromId: auth().currentUser.uid, toId: user.id}, onCompleted: () => setFollowSuccess(true)})
  }
  return [followSuccess]
}

回答1:


You can create a Custom Hook to achieve your goal by declaring a function which starts with use like this:

  export function useHandleFollow (props) {
      const [useFollowUser, { followedData, followError }] = useMutation(FOLLOW_USER);
      useFollowUser({ variables: { fromId: auth().currentUser.uid, toId: "userIDxyz"}, onCompleted: props.handleUserFollow(user, isFollowingAuthor)})
  }

remember that all the React Hooks Rule is also applied to Custom Hooks.




回答2:


Of course it is! You can create your own custom react hooks. React hooks use a rather simple naming convention, "use-" prefix.

export function useHandleFollow(props) {
  const [useFollowUser, { followedData, followError }] = useMutation(
    FOLLOW_USER
  );
  useFollowUser({
    variables: { fromId: auth().currentUser.uid, toId: "userIDxyz" },
    onCompleted: props.handleUserFollow(user, isFollowingAuthor)
  });
}

Only Call Hooks from React Functions

Don’t call Hooks from regular JavaScript functions. Instead, you can:

  • ✅ Call Hooks from React function components.
  • ✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).

By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code.

On the next page is an Extracting a Custom Hook section!

A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.

Edit

Still receiving invalid hook usage.

export function useHandleFollow(props) {
  const { user, isFollowingUser } = props;
  const [useFollowUser, { followedData, followError }] = useMutation(
    FOLLOW_USER
  );
  const [useUnfollowUser, { unfollowedData, unfollowedError }] = useMutation(
    UNFOLLOW_USER
  );
  const [followSuccess, setFollowSuccess] = useState(false);

  if (!isFollowingUser) {
    useFollowUser({
      variables: { fromId: auth().currentUser.uid, toId: user.id },
      onCompleted: setFollowSuccess(true)
    });
  } else {
    useUnfollowUser({
      variables: { fromId: auth().currentUser.uid, toId: user.id },
      onCompleted: setFollowSuccess(true)
    });
  }
  return followSuccess;
}

I think the issue here is the naming of the "mutate" function, which you've inconveniently named just like another React Hook. The React Hook linting rules are interpreting the if (!isFollowingUser) {...} as conditionally calling a hook, which is invald. Give them a non-React-hook name. useFollowUser to followUser and useUnfollowUser to unfollowUser.

export function useHandleFollow(props) {
  const { user, isFollowingUser } = props;
  const [followUser, { followedData, followError }] = useMutation(
    FOLLOW_USER
  );
  const [unfollowUser, { unfollowedData, unfollowedError }] = useMutation(
    UNFOLLOW_USER
  );
  const [followSuccess, setFollowSuccess] = useState(false);

  if (!isFollowingUser) {
    followUser({
      variables: { fromId: auth().currentUser.uid, toId: user.id },
      onCompleted: setFollowSuccess(true)
    });
  } else {
    unfollowUser({
      variables: { fromId: auth().currentUser.uid, toId: user.id },
      onCompleted: setFollowSuccess(true)
    });
  }
  return followSuccess;
}

Still render looping

useHandleFollow is called each render cycle. I think your custom hook triggers the render looping when it is unconditionally updating the followSuccess state in each branch of logic.

export function useHandleFollow(props) {
  ...
  const [followSuccess, setFollowSuccess] = useState(false);

  // setFollowSuccess(true) called always
  if (!isFollowingUser) {
    followUser({
      variables: { fromId: auth().currentUser.uid, toId: user.id },
      onCompleted: setFollowSuccess(true)
    });
  } else {
    unfollowUser({
      variables: { fromId: auth().currentUser.uid, toId: user.id },
      onCompleted: setFollowSuccess(true)
    });
  }
  ...
}

This ends up updating state every time the hook is called. You should place a condition on when you want this effect to be run, i.e. place that code block in an useEffect hook with a callback dependency on isFollowingUser. The effect callback will only run when the isFollowingUser value changes, not whenever the component renders.

export function useHandleFollow(props) {
  const { user, isFollowingUser } = props;
  const [followUser, { followedData, followError }] = useMutation(
    FOLLOW_USER
  );
  const [unfollowUser, { unfollowedData, unfollowedError }] = useMutation(
    UNFOLLOW_USER
  );
  const [followSuccess, setFollowSuccess] = useState(false);

  useEffect(() => {
    if (!isFollowingUser) {
      followUser({
        variables: { fromId: auth().currentUser.uid, toId: user.id },
        onCompleted: setFollowSuccess(true)
      });
    } else {
      unfollowUser({
        variables: { fromId: auth().currentUser.uid, toId: user.id },
        onCompleted: setFollowSuccess(true)
      });
    }
  }, [isFollowingUser]);

  return followSuccess;
}

Should I be returning something, like a set method or something, to accept new props? Otherwise, how do I end up calling the hook with those new props?

You only need to return updater function from hooks if you need them. React hooks are called each render cycle, so if you pass props to them then they will always receive the current props.



来源:https://stackoverflow.com/questions/65547620/how-can-i-make-this-a-callable-utils-function-that-contains-a-hook

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!