How to specify a constructor with a functional component (fat arrow syntax)?

Viewed 48775

Given this component:

import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'

const NewGoalInput = props => {
  return (
    <input type="text" onKeyUp={handleKeyUp}/>
  )
}

const handleKeyUp = (e) => {
  if (e.key === "Enter") {
    // TODO Add goal
  }
}

export default NewGoalInput

How do I add a constructor where I can define the state without using the extends React.Component syntax?

8 Answers

Now that we have useState and hooks the answers are kind of out of date. I came across this question because I was doing something wrong. Here's some simplified code of what I was doing.

// set an initial state
const [ value, setValue ] = useState(0)

// gets called after component is re-rendered
useEffect(() => {
   // callback to parent that set props
   props.update()
})

// if we have an existing value passed in
if (props.value) {
   setValue(props.value)
}

This code was converted from a stateful class to a function using hooks, originally setting the default props in the constructor - but functions don't have constructors and that check happens every time the component re-renders:

  1. calls useState
  2. triggers re-render
  3. useEffect is triggerd
  4. parent is called which sets the props
  5. props update so child renders again
  6. GOTO 1

As you can see this results in an infinite loop. The solution is really quite simple. Here's a mock diff from the original.

- const [ value, setValue ] = useState(0)
+ const [ value, setValue ] = useState(props.value || 0)

- if (props.value) {
-   setValue(props.value)
- }

Basically, just initialise the state from the props and don't do silly things like calling useState except in response to an event or callback of some type.

You can use useMemo hook (as below) to demonstrate as constructor for functional component. Somebody suggested to use useEffect but it will be invoked after render.

useMemo(() => {
  console.log('This is useMemo')
}, []);

you could set a useState as the first line inside of your functional component and add a function as "initial value":

const MyComponentName = props => {
  useState(() => {
    console.log('this will run the first time the component renders!');
  });
  return <div>my component!</div>;
};

To simulate constructor in FC use useEffect.

useEffect(() => {
  ... here your init code
}, []);

That's it! EZ! This useEffect runs only once when the component loads and never runs after, just don't forget to add square brackets at the end.

For those who want to run a function once before the component is mounted, here is a hook (written in TypeScript).

Normally useEffect and useLayoutEffect suffice, but they run after the component is mounted, and sometimes you want to run code before that happens (like a constructor).

import React, { useRef } from "react";

function useOnce<Type>(callBack: () => Type): Type {
  const result = useRef<Type | null>(null);

  if (result.current !== null) {
    return result.current;
  }

  result.current = callBack();
  return result.current;
}

const Component: React.FC<{}> = () => {
  const result = useOnce(() => {/* Code you would normally put in a constructor */});

  return <div />
}

Alternatively, you can use react-afc

import { afc, reactive } from 'react-afc'

function heavyCalc() {/*...*/}

const Conponent = afc(props => {
  // Called once, before the first render

  const state = reactive({
    name: 'Stack',
    inputsCount: 0
  })

  // Without useMemo(..., [])
  const result = heavyCalc()

  // The function is created once and does not cause
  // a re-render of child components
  function onInput(e) {
    state.inputsCount++
    state.name = e.currentTarget.value
  }

  // Saved between renders (no longer need useRef)
  let rendersCount = 0

  // Must return the render-function
  return () => {
    // The function works like a regular react-component
    // Here you can use the usual hooks
    rendersCount++
    return (
      <input onChange={onInput} value={state.name}/>
    )
  }
})

The package has the necessary methods for working with state (including redux), react-hooks, lifecycle methods and context

Related