Thomas Segato
Thomas Segato

Reputation: 5255

Skip hook change (useEffect) on initialize

I have created a function component with a dropdown. Everytime the user selects a new value i send an event to consuming component. I am using hooks for the selected index. I realized that hooks works async. When a new value was selected the old value was posted to the consuming component. So I started using "useEffect". However useEffect has that side effect that it is called when the variable is initialized. Is there anyway to tell REACT not to send an event to useEffect on initialization so I dont send a selected change event on initialization? Do you just add a boolean variable (like hasBeenInit) or is there something smarter?

https://codesandbox.io/s/blissful-clarke-ky1nr?fontsize=14&hidenavigation=1&theme=dark

import React, { useState, useEffect } from "react";
import {
  Dropdown,
  DropdownToggle,
  DropdownMenu,
  DropdownItem,
  Button
} from "reactstrap";

const DropdownPaging = props => {
  const [selectedValue, setSelectedValue] = useState(10);
  const [dropdownOpen, setDropdownOpen] = useState(false);
  const [pageIndex, setPageIndex] = useState(0);

  const toggle = () => setDropdownOpen(prevState => !prevState);

  function dropDownChanged(val) {
    setSelectedValue(val);
  }

  useEffect(() => {
    triggerEventChange(selectedValue, pageIndex);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedValue, pageIndex]);

  function pageIndexIncremented() {
    setPageIndex(counter => counter + 1);
  }

  function pageIndexDecremented() {
    if (pageIndex >= 1) {
      setPageIndex(counter => counter - 1);
    } else {
      setPageIndex(0);
    }
  }

  function triggerEventChange(take, index) {
    if (props.valueChanged) {
      props.valueChanged({ take: take, pageIndex: index });
    } else {
    }
  }

  return (
    <div>
      <table>
        <tr>
          <td>
            <Button
              outline
              disabled={pageIndex === 0}
              color="dark"
              onClick={pageIndexDecremented}
            >
              &lt;
            </Button>
          </td>
          <td>{pageIndex}</td>
          <td>
            <Button outline color="dark" onClick={pageIndexIncremented}>
              &gt;
            </Button>
          </td>
          <td>
            <Dropdown isOpen={dropdownOpen} toggle={toggle}>
              <DropdownToggle caret outline color="dark">
                {selectedValue}
              </DropdownToggle>
              <DropdownMenu>
                <DropdownItem onClick={() => dropDownChanged(10)}>
                  10
                </DropdownItem>
                <DropdownItem onClick={() => dropDownChanged(25)}>
                  25
                </DropdownItem>
                <DropdownItem onClick={() => dropDownChanged(50)}>
                  50
                </DropdownItem>
                <DropdownItem onClick={() => dropDownChanged(100)}>
                  100
                </DropdownItem>
              </DropdownMenu>
            </Dropdown>
          </td>
        </tr>
      </table>
    </div>
  );
};

export default DropdownPaging;

Upvotes: 2

Views: 2962

Answers (2)

dashton
dashton

Reputation: 2714

You could use useRef to simulated whether the component has mounted or not, so your component becomes :

  const mounted = useRef(false);
  const toggle = () => setDropdownOpen(prevState => !prevState);

  function dropDownChanged(val) {
    setSelectedValue(val);
  }

  useEffect(() => {
    if (mounted.current) {
      triggerEventChange(selectedValue, pageIndex);
    } else {
      mounted.current = true;
    }

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedValue, pageIndex]);

updated sandbox: https://codesandbox.io/s/dropdownpager-urvrp

Upvotes: 5

JeromeBu
JeromeBu

Reputation: 1159

The boolean value is an option. If you need the behavior in other places in your app you can create a custom hook:

const useEffectIgnoringInit = (callBack, watchedValues) => {
  const [hasBeenInit, setHasBeenInit] = useState(true);
  useEffect(() => {
    if(hasBeenInit){
      callBack();
    } else {
      setHasBeenInit(true);
    }
  }, watchedValues);
}

Then you use it instead of useEffect:

useEffectIgnoringInit(() => {
  triggerEventChange(selectedValue, pageIndex);
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedValue, pageIndex]);

Upvotes: 1

Related Questions