简体   繁体   English

(React Hooks) Ref 方法不更新 state 值

[英](React Hooks) Ref method not updating state value

So basically I have a debounced input component that exposes 2 ref methods, one to clear the input's value and one to set it to a string.所以基本上我有一个去抖动的输入组件,它公开了 2 个 ref 方法,一个用于清除输入的值,一个用于将其设置为字符串。

The problem is, using the ref method from the parent component does not work.问题是,使用父组件中的 ref 方法不起作用。

Code:代码:

import React, { ChangeEvent, forwardRef, useImperativeHandle, useState } from 'react';
import { TextInput } from 'components/common';
import { useDebounce } from 'utilities/CustomHooks';
import Logger from 'utilities/Logger';

export type DebouncedInputRef = {
  clearValue: () => void;
  setValue: (value: string) => void;
};

export const DebouncedInput = forwardRef(
  (
    { onChange, ...textInputProps }: ComponentProps.DebouncedInputProps,
    ref,
  ) => {
    const [textInputValue, setTextInputValue] = useState('');
    const debouncedOnChange = useDebounce((newValue: string) => {
      onChange && onChange(newValue);
    }, 1000);

    useImperativeHandle(
      ref,
      (): DebouncedInputRef => {
        return {
          clearValue: () => {
            setTextInputValue('');
          },
          setValue: (newValue: string) => {
            Logger.debug('DebouncedInput', 'setValue fired with', newValue);
            setTextInputValue(newValue);
          },
        };
      },
    );

    return (
      <div>
        {Logger.debug('DebouncedInput', 'in render value', textInputValue)}
        <TextInput
          {...textInputProps}
          value={textInputValue}
          onChange={(e: ChangeEvent<HTMLInputElement>) => {
            setTextInputValue(e.target.value);
            debouncedOnChange(e.target.value);
          }}
        />
      </div>
    );
  },
);

The code being used to call ref method is as follows:用于调用 ref 方法的代码如下:

const ListProducts = () => {
  const debouncedInputRef = useRef<DebouncedInputRef>();
  
  useEffect(() => {
    debouncedInputRef?.current && debouncedInputRef.current.setValue('Test');
  }, []);

  return (
    <DebouncedInput
      ref={debouncedInputRef}
    />
  );
};

The Logger.debug in setValue prints the incoming value from the parent component. setValue 中的 Logger.debug 打印来自父组件的传入值。

The Logger.debug in render also runs twice, signifying that re-render occurs right after setTextInputValue is called.渲染中的 Logger.debug 也运行了两次,表示在调用 setTextInputValue 之后立即发生重新渲染。

However, the value of the state variable during the render is the same as before.但是,在渲染过程中 state 变量的值与之前相同。

Basically, setValue runs, but the state variable is not updated, and I have no idea why.基本上, setValue 运行,但 state 变量没有更新,我不知道为什么。

Any pointers will be very welcome.任何指针都将非常受欢迎。


UPDATE:更新:

Okay, so I got it working.好的,所以我让它工作了。 Basically, my ListProducts component had a little extra detail:基本上,我的 ListProducts 组件有一些额外的细节:

const ListProducts = () => {
  const [loading, setLoading] = useState(false);
  const debouncedInputRef = useRef<DebouncedInputRef>();
  
  const mockApiCall = () => {
    setLoading(true);
    // call API and then
    setLoading(false);
  };

  useEffect(() => {
    debouncedInputRef?.current && debouncedInputRef.current.setValue('Test');
    mockApiCall();
  }, []);

  if (loading) {
    return <div>Spinner here</div>;
  }
  return (
    <DebouncedInput
      ref={debouncedInputRef}
    />
  );
};

What I believe the problem was, the ref was capturing the initial DebouncedInput, and then API was called, which returned the spinner and removed Debounced Input from the DOM.我认为问题在于,ref 捕获了初始 DebouncedInput,然后调用了 API,它返回了微调器并从 DOM 中删除了 Debounced Input。

And later when API was done, it was rendered again, but I guess it was a different DOM element?后来当API做完后,又渲染了一遍,但我猜是不同的DOM元素吧?

I'm not sure why this happened, but it was so.我不知道为什么会这样,但确实如此。 I'd be glad to know what exactly was causing the issue.我很高兴知道究竟是什么导致了这个问题。

Here's a code sandbox example with both, working and not working examples.这是一个代码沙盒示例,其中包含工作示例和不工作示例。

If anyone could elaborate on what exactly is the issue in the not working example, I'd be very grateful:)如果有人可以详细说明无效示例中的问题到底是什么,我将不胜感激:)

First lets start with small tweaks:首先让我们从小调整开始:

useImperativeHandle should be used with dep array, in this case its empty: useImperativeHandle应该与 dep 数组一起使用,在这种情况下它是空的:

useImperativeHandle(...,[]);

useEffect should always have a SINGLE responsibility , according to your logic, you just want to set ref value on mount, add another useEffect (doesn't effect anything in this specific example) useEffect应该总是有一个单一的责任,根据你的逻辑,你只想在 mount 上设置 ref 值,添加另一个useEffect (在这个特定的例子中没有任何影响)

// API CALl
useEffect(callback1, []);
// Ref set on mount
useEffect(callback2, []);

Then, from the theoretic side, on every state change, React compares React Sub Node trees to decide if an UI update needed (see Reconciliation ).然后,从理论上讲,在每次 state 更改时,React 都会比较 React 子节点树来决定是否需要更新 UI(请参阅Reconciliation )。

Those are two different React Nodes:这是两个不同的 React 节点:

// #1
<div>
  {renderContent()}
</div>

// #2
<div>
  <DebouncedInput ref={myCompRef} />
  {loading && <span>Loading right now</span>}
</div>

Since in #1, the function called renderContent() on every render , therefore you actually re-mount the node on every render.由于在 #1 中,function在每个渲染上调用renderContent() ,因此您实际上在每个渲染上重新安装节点。

Why your code didn't work?为什么你的代码不起作用? Because you called some logic on parent MOUNT :因为您在父MOUNT上调用了一些逻辑:

useEffect(() => {
  myCompRef.current?.setValue("Value set from ref method");
}, [])

If the ref mounted, it worked, if its not, there wasn't function call.如果 ref 已安装,它可以工作,如果没有,则没有 function 调用。

But in context of #1 tree, you instantly unmounted it on next render, therefore you reset the inner value state.但是在 #1 树的上下文中,您在下一次渲染时立即将其卸载,因此您重置了内部value state。

In context of #2, its the same React node , therefore React just updated it.在 #2 的上下文中,它是相同的 React 节点,因此 React 只是更新了它。

编辑 Q-67397086-SameReactNode

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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