Search code examples
javascriptreactjsreact-state

How do I get a function component to rerender when a state var changes?


I'm new to React and React hooks and very confused about how to trigger a re-render when a state variable changes and I have a useEffect that monitors that state.

Is it possible to trigger renderFileItems to rerun? It is called using {renderFileItems(files)} in the returned html. While I do see useEffect being called I'm not sure how to trigger the re-render for that area, I'm also ok with full re-render of the page but not sure how to trigger that either.

export default function FileUpload() {
  const [files, setFiles] = useState();

  useEffect(() => {
    // what do I do here?
    console.log("CHANGE"); // I do see this getting printed when I add a file
  }, [files, fileStatus]);

  const remove = (filename) => {
    if (files == undefined) {
      return;
    }
    var i = 0;
    while (i < files.length) {
      if (files[i] === filename) {
        files.splice(i, 1);
      } else {
        ++i;
      }
    }
    setFiles(files);
  };

  const renderFileItems = (filelist) =>
    filelist == undefined ? (
      <></>
    ) : (
      filelist.map((f) => (
        <li className="flex" key={f}>
          {f}
          <button onClick={remove(f)}>
            <TrashIcon
              aria-hidden="true"
              className="inline-block align-middle size-5 shrink-0"
            />
          </button>
        </li>
      ))
    );

  const fileHandler = (filesArg) => {
    // does some logic with filesArg then updates Files state var.
    setFiles(/*transformed filesArg*/);
  };

  return (
    <AppLayout>
      <div className="flex flex-col">
        <div className="flex items-center justify-center w-full items-center justify-center w-full pr-24 pl-24 pt-12">
          <label
            id="drop-zone"
            className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100 border-gray-300"
            onDragOver={dragOverImageChange}
            onDragLeave={dragLeaveChange}
            onDrop={dropHandle}
          >
            <div className="flex flex-col items-center justify-center pt-5 pb-6 gap-y-2">
              <p className="mb-2 text-md text-black font-semibold">
                Click to upload or drag and drop
              </p>
              <input
                id="dropzone-file-input"
                type="file"
                className="hidden"
                onChange={fileSelectedHandler}
                accept=".csv"
                multiple="True"
              />
            </div>
          </label>
        </div>
        <div className="flex py-12 w-full mx-auto pl-24">
          <ul
            className="flex flex-col gap-y-1 justify-stretch items-stretch font-semibold text-gray-600"
            id="files-show"
          >
            {renderFileItems(files)}
          </ul>
        </div>
      </div>
    </AppLayout>
  );
}

If I have to split out the renderFileItems as its own component, I am confused how to have it call the remove() function which will have to modify the files state var tracked in the parent component.

Thanks for any help!


Solution

  • In React we don't change state directly. It have own way to deal with state changes, see here.

    To trigger the change, you have to pass a new data on the setState method, this it also will trigger a rerender.

    Extra

    remove(f) will execute the remove method immediately.

    If your intention is pass the f as a parameter, you have to pass a anon function in the onClick prop executing your method.

    Example:

    <button onClick={() => remove(f)}>