English 中文(简体)
使用“ tanstack- query” 时状态未适当更新
原标题:state is not properly updated when using with `tanstack-query`

我有一个可以控制和控制的 Uploader 组件。 instantUpload prop 允许在选择文件后进行imididate 更新。 不幸的是,由于某种原因, files 变量在 onUploadRegent 中未适当更新, 并且 on Success 回调( 即 < code> file 是空数组, 而不是选择的 file 。 为何这样解释?

 use client ;

import { ChangeEvent, RefObject, createContext, useRef, useState } from  react ;

import { useSaveFile } from  @/api/file/hooks ;
import { useToast } from  @/components/ui/use-toast ;
import { DefaultUploadContainer } from  @/components/uploader/default-upload-container ;
import { DefaultUploadTrigger } from  @/components/uploader/default-upload-trigger ;
import { formatFileSize, getFileExtension } from  @/lib/utils ;
import { UploadedFile } from  @/types ;
import { DefaultExtensionType } from  react-file-icon ;

export type UploaderFile = {
  file?: globalThis.File;
  src: string;
  extension: DefaultExtensionType;
  progress?: number;
  uploading?: boolean;
  name: string;
  size: number;
  id?: string;
  uploadedFile?: UploadedFile;
};

interface UploaderContext {
  files: UploaderFile[];
  onChange: (files: UploaderFile[]) => void;
  handleUpload: (file: UploaderFile) => void;
  handleRemoveFile: (file: UploaderFile) => void;
  fileInputRef: RefObject<HTMLInputElement>;
}

export const UploaderContext = createContext<UploaderContext>({} as any);

export interface UploaderProps {
  allowedExtensions?: DefaultExtensionType[];
  multiple?: boolean;
  instantUpload?: boolean;
  files?: UploaderFile[];
  defaultFiles?: UploaderFile[];
  maxSize?: number;
  onChange?: (files: UploaderFile[]) => void;
  onClick?: (file: UploaderFile) => void;
  triggerRenderer?: () => JSX.Element;
  containerRenderer?: () => JSX.Element;
  Trigger?: React.ComponentType;
  Container?: React.ComponentType;
}
export function Uploader({
  multiple = true,
  allowedExtensions,
  instantUpload = true,
  maxSize,
  files: valueFromProps,
  defaultFiles: defaultValue,
  Trigger,
  Container,
  onChange: onChangeFromProps,
}: UploaderProps) {
  // A component can be considered controlled when its value prop is
  // not undefined.
  const isControlled = typeof valueFromProps !=  undefined ;
  // When a component is not controlled, it can have a defaultValue.
  const hasDefaultValue = typeof defaultValue !=  undefined ;

  // If a defaultValue is specified, we will use it as our initial
  // state.  Otherwise, we will simply use an empty array.
  const [internalValue, setInternalValue] = useState<UploaderFile[]>(
    hasDefaultValue ? defaultValue : []
  );

  // Internally, we need to deal with some value. Depending on whether
  // the component is controlled or not, that value comes from its
  // props or from its internal state.
  let files: UploaderFile[];
  if (isControlled) {
    files = valueFromProps;
  } else files = internalValue;

  const onChange = (value: UploaderFile[]) => {
    // If exists, we always call onChange callback passed in props
    // We do this even if there is no props.value (and the component
    // is uncontrolled.)
    if (onChangeFromProps) {
      onChangeFromProps(value);
    }

    // If the component is uncontrolled, we need to update our
    // internal value here.
    if (!isControlled) {
      setInternalValue(value);
    }
  };

  const fileInputRef = useRef<HTMLInputElement>(null);

  const mutationUploadFile = useSaveFile({});
  const { toast } = useToast();

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    let inputFiles: UploaderFile[] = Array.from(event.target.files || []).map(
      (file) => ({
        file: file,
        name: file.name,
        size: file.size,
        src: URL.createObjectURL(file),
        extension: getFileExtension(file.name),
      })
    );
    if (!inputFiles.length) return;

    if (!multiple && files.length !== 0) {
      toast({
        title:  Zbyt wiele plików! ,
        description: <div>Możesz przesłać maksymalnie jeden plik!</div>,
        variant:  destructive ,
      });
      return;
    }

    for (let file of inputFiles) {
      if (!file.file) continue;
      if (allowedExtensions && !allowedExtensions.includes(file.extension)) {
        toast({
          title:  Nieprawidłowy format pliku! ,
          description: (
            <div className= w-full max-w-full >
              <p>
                Dozwolone są jedynie pliki w nastepujących formatach:{   }
                <span className= font-bold >
                  {allowedExtensions.map((x) => x.toUpperCase()).join( ,  )}.
                </span>
              </p>
              <p className= pt-2 >
                Pominięto plik: <span className= italic >{file.file.name}</span>
              </p>
            </div>
          ),
          variant:  destructive ,
        });
        inputFiles = inputFiles.filter((item) => item.name !== file.name);
      }
      if (maxSize && file.file.size > maxSize) {
        toast({
          title:  Zbyt duży rozmiar pliku! ,
          description: (
            <div>
              Rozmiar pliku <span className= italic >{file.file.name}</span> (
              {formatFileSize(file.file.size)}) przekracza maksymalny dozwolony
              rozmiar{   }
              <span className= font-bold >({formatFileSize(maxSize)})</span>
            </div>
          ),
          variant:  destructive ,
        });
        inputFiles = inputFiles.filter((x) => x !== file);
      }
      if (files.some((x) => x.file?.name === file.file?.name)) {
        inputFiles = inputFiles.filter((x) => x !== file);
        toast({
          title:  Plik już istnieje! ,
          description: (
            <div>
              Plik <span className= italic >{file.file.name}</span> (
              {formatFileSize(file.file.size)}) został już dodany.
            </div>
          ),
          variant:  destructive ,
        });
      }
    }

    onChange([...files, ...inputFiles]);

    if (instantUpload) {
      for (let file of inputFiles) {
        handleUpload(file);
      }
    }

    fileInputRef.current!.value =   ;
  };

  const handleUpload = (file: UploaderFile) => {
    mutationUploadFile.mutate(
      {
        file: file.file!,
        onUploadProgress(progress) {
          onChange(
            files.map((item) => {
              if (item.name === file.name)
                return {
                  ...file,
                  uploading: true,
                  progress: Math.floor(
                    (progress.loaded / progress.total!) * 100
                  ),
                };
              return item;
            })
          );
        },
      },
      {
        onSuccess(res) {
          onChange(
            files.map((item) => {
              if (item.name === file.name)
                return {
                  ...item,
                  id: res.id,
                  src: res.url,
                  uploadedFile: res,
                  uploading: false,
                };
              return item;
            })
          );
        },
        onError(error, variables, context) {},
      }
    );
  };

  const handleRemoveFile = (file: UploaderFile) => {
    if (file.uploadedFile) {
      onChange(files.filter((item) => item.name !== file.name));
    } else {
      onChange(files.filter((item) => item.name !== file.name));
    }
  };

  return (
    <UploaderContext.Provider
      value={{
        files,
        onChange,
        handleUpload,
        handleRemoveFile,
        fileInputRef,
      }}
    >
      <div className= w-full >
        <input
          type= file 
          name= file 
          hidden
          multiple={multiple}
          ref={fileInputRef}
          onChange={handleChange}
        />
        {Trigger ? <Trigger /> : <DefaultUploadTrigger />}
        {Container ? <Container /> : <DefaultUploadContainer />}
      </div>
    </UploaderContext.Provider>
  );
}

问题回答

您使用 set InternalValue( value( value)( value) (value) 重新更新状态, 但是您需要以 < a href=" https://react. dev/ reference/ react/use States#update_ state- based- on- the- previous- state " relate= " rel= " nofollow noreferr" > > retter like set InternionalValue( (( 旧状态) @gt; new State) 进行更新状态, 因为您需要根据先前的状态重新更新。 否则, 在 < code > file < files 中存在风险 < uncord 中 < 并不属于更新状态 。

这样做有点困难,因为你必须同时处理受控和不受控制的使用案例。 可能需要做一些重新构思,但不受控制的最新消息会是这样的。

onSuccess: () => {
  if (!isControlled) {
    setInternalValue((files) =>
      files.map((item) => {
        if (item.name === file.name)
        {
          return {
            ...file,
            uploadedFile: res,
            uploading: false,
          };
        }
        return item;
      })
    );
  } else {
    // handle controlled updates
  }
}





相关问题
How to use one react app into another react app?

I have two react apps, parent app and child app. and child app have an hash router. I have build the child app using npm run build, It creates build folder. That build folder moved into inside the ...

how to get selected value in Ant Design

I want to print the selected value, and below is my option list: const vesselName = [ { value: 0 , label: ALBIDDA , }, { value: 1 , label: ALRUMEILA , }, { value: 2 ,...

How to add a <br> tag in reactjs between two strings?

I am using react. I want to add a line break <br> between strings No results and Please try another search term. . I have tried No results.<br>Please try another search term. but ...

热门标签