问题
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