如何在初始渲染后重新渲染自定义钩子

Posted

技术标签:

【中文标题】如何在初始渲染后重新渲染自定义钩子【英文标题】:How to re-render a custom hook after initial render 【发布时间】:2022-01-02 11:44:57 【问题描述】:

我有一个名为useIsUserSubscribed 的自定义钩子,用于检查是否订阅了特定用户。如果用户已订阅则返回 true,如果用户未订阅则返回 false...

import  useState, useEffect  from "react";
import  useSelector  from "react-redux";
import  checkSubscription  from "../services";

// this hook checks if the current user is subscribed to a particular user(publisherId)
function useIsUserSubscribed(publisherId) 
  const [userIsSubscribed, setUserIsSubscribed] = useState(null);
  const currentUserId = useSelector((state) => state.auth.user?.id);

  useEffect(() => 
    if (!currentUserId || !publisherId) return;

    async function fetchCheckSubscriptionData() 
      try 
        const res = await checkSubscription(publisherId);
        setUserIsSubscribed(true);
       catch (err) 
        setUserIsSubscribed(false);
      
    

    fetchCheckSubscriptionData();
  , [publisherId, currentUserId]);

  return userIsSubscribed;


export default useIsUserSubscribed;

...我有一个使用此钩子的按钮,它根据从useIsUserSubscribed...返回的布尔值有条件地呈现文本

import React,  useEffect, useState  from "react";
import  add, remove  from "../../services";
import useIsUserSubscribed from "../../hooks/useIsUserSubscribed";

const SubscribeUnsubscribeBtn = (profilePageUserId) => 

  const userIsSubscribed = useIsUserSubscribed(profilePageUserId);
  
  const onClick = async () => 
    if (userIsSubscribed) 
       // this is an API Call to the backend
      await removeSubscription(profilePageUserId);

     else 
      // this is an API Call to the backend
      await addSubscription(profilePageUserId);
    
    // HOW CAN I RERENDER THE HOOK HERE!!!!?
  

  return (
    <button type="button" className="sub-edit-unsub-btn bsc-button" onClick=onClick>
          userIsSubscribed ? 'Subscribed' : 'Unsubscribed'
    </button>
  );
 

onClick 之后,我想重新渲染useIsUserSubscribed 挂钩,以便我的按钮文本切换。这个可以吗?

【问题讨论】:

【参考方案1】:

你不能为此目的在你的钩子中使用 useEffect 试试这个:

钩子:

function useIsUserSubscribed() 
  const currentUserId = useSelector((state) => state.auth.user?.id);


  const checkUser = useCallback(async (publisherId, setUserIsSubscribed) => 
    if (!currentUserId || !publisherId) return;
      try 
        const res = await checkSubscription(publisherId);
        setUserIsSubscribed(true);
       catch (err) 
        setUserIsSubscribed(false);
      
    
  , [currentUserId]);

  return checkUser;


export default useIsUserSubscribed;

组件:

const SubscribeUnsubscribeBtn = (profilePageUserId) => 
    const [userIsSubscribed,setUserIsSubscribed]=useState(false);
    const  checkUser  = useIsUserSubscribed();

     useEffect(()=>
        checkUser(profilePageUserId,setUserIsSubscribed)
     ,[checkUser,profilePageUserId]);
  
  const onClick = async () => 
    if (userIsSubscribed) 
       // this is an API Call to the backend
      await removeSubscription(profilePageUserId);

     else 
      // this is an API Call to the backend
      await addSubscription(profilePageUserId);
    
    // HOW CAN I RERENDER THE HOOK HERE!!!!?
    checkUser(profilePageUserId,setUserIsSubscribed)
  

  return (
    <button type="button" className="sub-edit-unsub-btn bsc-button" onClick=onClick>
          userIsSubscribed ? 'Subscribed' : 'Unsubscribed'
    </button>
  );
 

你也可以在你的钩子中添加一些加载状态并返回它们,这样你就可以检查过程是否已经完成

【讨论】:

我打算在应用程序的其他部分重用这个逻辑。如果钩子不是最好的方法,有什么更好的方法可以使其可重用? @SimoneAnthony hooks 没什么问题,你可以使用它,但我也注意到你使用 redux,所以你也可以使用 redux-thunk 动作【参考方案2】:

添加对 useIsUserSubscribed 的 useEffect 的依赖。

钩子:

function useIsUserSubscribed(publisherId) 
    const [userIsSubscribed, setUserIsSubscribed] = useState(null);
    const currentUserId = useSelector((state) => state.auth.user?.id);
    // add refresh dependece
    const refresh = useSelector((state) => state.auth.refresh);

    useEffect(() => 
        ...
    , [publisherId, currentUserId, refresh]);
    ...

组件:

const onClick = async () => 
    ...
    // HOW CAN I RERENDER THE HOOK HERE!!!!?
    // when click, you can dispatch a refresh flag.
    dispatch(refreshSubState([]))

公开 forceUpdate 方法。

钩子:

function useIsUserSubscribed(publisherId) 
    const [update, setUpdate] = useState();
    const forceUpdate = () => 
        setUpdate();
      

    return userIsSubscribed, forceUpdate;

组件:

const userIsSubscribed, forceUpdate = useIsUserSubscribed(profilePageUserId);

const onClick = async () => 
    ...
    forceUpdate();

【讨论】:

【参考方案3】:

这是用户@bitspook 的另一个解决方案

SubscribeUnsubscribeBtn 依赖于 useIsUserSubscribed,但 useIsUserSubscribed 不依赖于来自 SubscribeUnsubscribeBtn 的任何内容。 相反,useIsUserSubscribed 保持本地状态。你有几个选择:

    将有关用户是否订阅的状态上移一级,因为您正在使用 Redux,可能在 Redux 中。 通知useIsUserSubscribed,您需要更改其内部状态。

对于1)

  const [userIsSubscribed, setUserIsSubscribed] = useState(null);

将此状态移动到 Redux 存储并与 useSelector 一起使用。

对于2),从钩子返回一个值数组和回调,而不仅仅是值。它将允许您从组件通信回钩子。

useIsUserSubscribed

  return [userIsSubscribed, setUserIsSubscribed];

然后在onClick,你可以调用setUserIsSubscribed(false),改变钩子的内部状态,重新渲染你的组件。

【讨论】:

以上是关于如何在初始渲染后重新渲染自定义钩子的主要内容,如果未能解决你的问题,请参考以下文章

尽管状态发生了变化,但自定义钩子不会触发组件重新渲染

为啥带有 useContext 触发器的自定义路由 HOC 会重新渲染?

使用自定义挂钩时的重新渲染问题

React ,自定义钩子慢渲染

Layui数据表格 加入 自定义扩展方法(重新渲染Render当前页数据)

坐标更改后自定义标记不重新渲染