1

Sometimes, I have found it useful to place some code in the constructor of a React class component, to do some processing once (when the component is instantiated) and be able to reference the result throughout. Is there now a way to do this with a functional component using the React Hooks API?

example:

constructor(props) {
    super(props);
    const componentFactory = createComponentFactory(props.context);
    this.components = props.components.map(componentFactory);
papiro
  • 2,158
  • 1
  • 20
  • 29

2 Answers2

1

useEffect can be used to run the code once but this happens on component mount, so it's a counterpart to componentDidMount, not a constructor:

let components = useRef();

useEffect(() => {
  components.current = props.components.map(createComponentFactory(props.context))
}, []);
// components.current === null
// on first render

useMemo can be used to run the code once on first render:

const components = useMemo(
  () => props.components.map(createComponentFactory(props.context)),
  []
);

It isn't guaranteed to run the code once in future React versions:

You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.

useState can be used to run the code once on first render, too:

const [components] = useState(
  () => props.components.map(createComponentFactory(props.context))
);

Due to limitations of other options, the last option can be considered a preferable way to do this.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • From [React's docs](https://reactjs.org/docs/hooks-effect.html): "If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined." – papiro Apr 19 '19 at 13:16
  • In practice, I prefer this solution to the one officially proposed by React, in @cbdev420's answer. – papiro Apr 19 '19 at 13:45
  • I updated regarding the use of useEffect. It's used with `[]` input when it acts as componentDidMount. *to do some processing once (when the component is instantiated)* - only useMemo and useState do that. FWIW, accepted answer doesn't answer the question. useRef provides instance variables but doesn't prevent the code from running every time a component is rendered. I didn't mention useRef because it's not good at that - you could use short-circuit to init a ref like `const foo = useRef(); foo.current = foo.current || callOnlyOnce() || 'no foo'` but it's cumbersome. – Estus Flask Apr 19 '19 at 14:12
  • I really appreciate the follow-up. After analyzing things further, I see that the example I provided for what I wanted to accomplish doesn't illustrate the question well. `useRef` is exactly the answer (please correct me if I'm wrong) if you're merely interested in the result of said processing, but cannot be used if you just want to do some arbitrary "fire-and-forget" processing. But `useState` seems like it's not really appropriate for general non-result oriented – papiro Apr 19 '19 at 20:50
  • 1
    You're right about useRef. useState causes some insignificant overhead but is appropriate for this case, primarily because it allows for init function. In case useRef works as expected, it's preferable because it's more straight to the point. Notice that ref value can be updated without component re-render while a state causes a re-render, that's major difference between them if instance value needs to be assigned multiple times.. – Estus Flask Apr 19 '19 at 21:03
  • That's a good point! TBH, there doesn't seem to be a one-to-one solution for running arbitrary code on initialization. To run code which produces a result, `useRef` seems best. To run arbitrary code, `useEffect` seems best, even though it happens on `componentDidMount` instead of prior to rendering... Probably a trivial distinction in most cases. – papiro Apr 19 '19 at 21:18
  • I think that useState is the only one-to-one match for constructor code among them. The only thing it misses is the one I mentioned in https://stackoverflow.com/questions/55750223/is-there-a-way-to-emulate-the-run-frequency-of-constructor-code-using-the-react/55750652?noredirect=1#comment98200767_55750973 . Since there's no `this`, you may need an object you can use in callbacks to keep a reference, to avoid useState pitfalls like this one, https://stackoverflow.com/questions/53845595/wrong-react-hooks-behaviour-with-event-listener . That's where refs come to rescue. – Estus Flask Apr 19 '19 at 21:59
1

It looks like you want something like instance variables.

You can do that using the useRef() hook.

Docs: Hooks API

Essentially, useRef is like a “box” that can hold a mutable value in its .current property.

You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with , React will set its .current property to the corresponding DOM node whenever that node changes.

However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

Docs: Hooks FAQ

Is there something like instance variables?

Yes! The useRef() Hook isn’t just for DOM refs. The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class.

Community
  • 1
  • 1
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
  • 1
    How could I have missed this! This is obviously the correct answer, as taken from the React docs, but TBH, I would use @estus's answer since it seems more intuitive to me. I dunno who from the React team thought accessing a `.current` property on an object made sense to store single values. It shouldn't be called `useRef` at that point, IMO. – papiro Apr 19 '19 at 13:43
  • 1
    @papiro Passing object reference instead of a value is a common pattern in JS. useRef extends the pattern used by createRef to be used universally, not just as component refs. It allows to work around some problems that are inherent to React hooks. – Estus Flask Apr 19 '19 at 14:09