Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: How to skip useEffect on first render

I'm trying to use the useEffect hook inside a controlled form component to inform the parent component whenever the form content is changed by user and return the DTO of the form content. Here is my current attempt

const useFormInput = initialValue => {
  const [value, setValue] = useState(initialValue)

  const onChange = ({target}) => {
    console.log("onChange")
    setValue(target.value)
  }

  return { value, setValue, binding: { value, onChange }}
}
useFormInput.propTypes = {
  initialValue: PropTypes.any
}

const DummyForm = ({dummy, onChange}) => {

  const {value: foo, binding: fooBinding} = useFormInput(dummy.value)
  const {value: bar, binding: barBinding} = useFormInput(dummy.value)

  // This should run only after the initial render when user edits inputs
  useEffect(() => {
    console.log("onChange callback")
    onChange({foo, bar})
  }, [foo, bar])

  return (
    <div>
      <input type="text" {...fooBinding} />
      <div>{foo}</div>

      <input type="text" {...barBinding} />
      <div>{bar}</div>
    </div>
  )
}


function App() {
  return (
    <div className="App">
      <header className="App-header">
        <DummyForm dummy={{value: "Initial"}} onChange={(dummy) => console.log(dummy)} />
      </header>
    </div>
  );
}

However, now the effect is ran on the first render, when the initial values are set during mount. How do I avoid that?

Here are the current logs of loading the page and subsequently editing both fields. I also wonder why I get that warning of missing dependency.

onChange callback
App.js:136 {foo: "Initial", bar: "Initial"}
backend.js:1 ./src/App.js
  Line 118:  React Hook useEffect has a missing dependency: 'onChange'. Either include it or remove the dependency array. If 'onChange' changes too often, find the parent component that defines it and wrap that definition in useCallback  react-hooks/exhaustive-deps
r @ backend.js:1
printWarnings @ webpackHotDevClient.js:120
handleWarnings @ webpackHotDevClient.js:125
push../node_modules/react-dev-utils/webpackHotDevClient.js.connection.onmessage @ webpackHotDevClient.js:190
push../node_modules/sockjs-client/lib/event/eventtarget.js.EventTarget.dispatchEvent @ eventtarget.js:56
(anonymous) @ main.js:282
push../node_modules/sockjs-client/lib/main.js.SockJS._transportMessage @ main.js:280
push../node_modules/sockjs-client/lib/event/emitter.js.EventEmitter.emit @ emitter.js:53
WebSocketTransport.ws.onmessage @ websocket.js:36
App.js:99 onChange
App.js:116 onChange callback
App.js:136 {foo: "Initial1", bar: "Initial"}
App.js:99 onChange
App.js:116 onChange callback
App.js:136 {foo: "Initial1", bar: "Initial2"}
like image 466
Tuomas Toivonen Avatar asked Sep 09 '19 17:09

Tuomas Toivonen


People also ask

How do you make React useEffect hook not run on initial render?

We can make the React useEffect callback not run on the first render by creating a ref that keeps track of whether the first render is done. Then we can check the ref's value to see when the first render is done and run the function we want when the first render is done.

Does useEffect always run on first render?

By default, useEffect will run on initial render as well as every future render (update) of your component.

How do I stop useEffect in React?

To get rid of your infinite loop, simply use an empty dependency array like so: const [count, setCount] = useState(0); //only update the value of 'count' when component is first mounted useEffect(() => { setCount((count) => count + 1); }, []); This will tell React to run useEffect on the first render.


1 Answers

You can see this answer for an approach of how to ignore the initial render. This approach uses useRef to keep track of the first render.

  const firstUpdate = useRef(true);
  useLayoutEffect(() => {
    if (firstUpdate.current) {
      firstUpdate.current = false;
    } else {
     // do things after first render
    }
  });

As for the warning you were getting:

React Hook useEffect has a missing dependency: 'onChange'

The trailing array in a hook invocation (useEffect(() => {}, [foo]) list the dependencies of the hook. This means if you are using a variable within the scope of the hook that can change based on changes to the component (say a property of the component) it needs to be listed there.

like image 191
garrettmaring Avatar answered Oct 07 '22 10:10

garrettmaring