Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useState with a lambda invokes the lambda when set

Tags:

reactjs

I'm trying to use useState with a void returning lambda. For example (TypeScript):

const [doSomething, setDoSomething] = useState<() => void>();

What I'm finding is that when I call setDoSomething, not only does it set the state to the lambda I've defined, but it also invokes the lambda. For example (TypeScript):

setDoSomething(() => alert("Did something!"));

This statement results in an alert showing "Did something!" I just want to set the state to a lambda at this point, I don't want to invoke it. I want to invoke it later by doing:

doSomething();

I think maybe what's going on is that React.js is treating my lambda as a lambda to lazily get the state value, but this is not the desirable behavior in this case. Is it possible to use useState with lambda types?

like image 279
Ryan Tremblay Avatar asked Dec 05 '25 17:12

Ryan Tremblay


2 Answers

React DOES treat lambda the way you think. You can create a custom hook with useReducer to get around that limitation.

const useState2 = initVal => {
  const setter = useRef((__, next) => ({ val: next })).current
  const [wrapper, dispatch] = useReducer(setter, { val: initVal })
  const state = wrapper.val
  const setState = dispatch
  return [state, setState]
}

Live Demo:

const { useRef, useReducer } = React

const useState2 = initVal => {
  const setter = useRef((__, next) => ({ val: next })).current
  const [wrapper, dispatch] = useReducer(setter, { val: initVal })
  const state = wrapper.val
  const setState = dispatch
  return [state, setState]
}

function App() {
  const [say, setSay] = useState2(() => console.log('something'))
  return (
    <div>
      <button onClick={say}>say something</button>
      <button onClick={() => setSay(() => console.log('yo!'))}>say yo! instead</button>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>

<div id="app"></div>
like image 177
hackape Avatar answered Dec 07 '25 15:12

hackape


Another simpler work-around would be to wrap your lambda in another lambda:

setDoSomething(() => () => alert("Did something!"));

The outer lamba is invoked by React, the inner lambda is invoked by you.

like image 20
adam0101 Avatar answered Dec 07 '25 15:12

adam0101