139

is it possible to use a function as my React Component's state ?

example code here:

// typescript 
type OoopsFunction = () => void;

export function App() {
    const [ooops, setOoops] = React.useState<OoopsFunction>(
        () => console.log('default ooops')
    );

    return (
        <div>
            <div onClick={ ooops }>
                Show Ooops
            </div>

            <div onClick={() => {
                setOoops(() => console.log('other ooops'))
            }}>
                change oops
            </div>
        </div>
    )
}

but it doesn't works ... the defaultOoops will be invoked at very beginning, and when clicking change oops, the otrher ooops will be logged to console immediately not logging after clicking Show Ooops again.

why ?

is it possible for me to use a function as my component's state ?

or else React has its special ways to process such the function state ?

5 Answers 5

210

It is possible to set a function in state using hooks, but because state can be initialized and updated with a function that returns the initial state or the updated state, you need to supply a function that in turn returns the function you want to put in state.

const { useState } = React;

function App() {
  const [ooops, setOoops] = useState(() => () => console.log("default ooops"));

  return (
    <div>
      <button onClick={ooops}>Show Ooops</button>

      <button
        onClick={() => {
          setOoops(() => () => console.log("other ooops"));
        }}
      >
        change oops
      </button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

Sign up to request clarification or add additional context in comments.

1 Comment

Maybe these weren't documented in the past, but they are now. See the Lazy initial state section, as well as the yellow box above it regarding merging updates, aka using prevState. I also found this article helpful: medium.com/swlh/…
89

TL;DR

Yes, it is possible to use a function as the React Component's state. In order to do this, you must use a function returning your function in React.useState:

const [ooops, setOoops] = React.useState<OoopsFunction>(
    () => () => console.log('default ooops')
);

// or

const yourFunction = () => console.log('default ooops');
const [ooops, setOoops] = React.useState<OoopsFunction>(
    () => yourFunction
);

To update your function you also must use a function returning your function:

setOoops(() => () => console.log("other ooops"));

// or

const otherFunction = () => console.log("other ooops");
setOoops(() => otherFunction);

Detailed Answer

Notes about React.useState

The signature of useState in React with types is

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];

It shows, that there are two ways to set the initial value in your state:

  1. Provide the initial value as is (React.useState(0) - initial value 0),
  2. Provide a function, which returns the initial value to be set (React.useState(() => 0) - initial value also 0).

Important to note: If you provide a function in React.useState, then this function is executed, when React.useState is executed and the returned value is stored as the initial state.

How to actually store functions

The problem here is if you want to store a function as state you can not provide it as initial state as is, because this results in the function being executed and its return value stored as state instead of the function itself. Therefore when you write

const [ooops, setOoops] = React.useState<OoopsFunction>(
    () => console.log('default ooops')
);

'default ooops' is logged immediately when React.useState is called and the return value (in this case undefined) is stored.

This can be avoided by providing a higher order function returning your function you want to store:

const [ooops, setOoops] = React.useState<OoopsFunction>(
    () => () => console.log('default ooops')
);

This way the outer function will be definitely executed when first running React.useState and its return value will be stored. Since this return value is now your required function, this function will be stored.

Notes about the state setter function

The state setter function's (here setOoops) signature is given as

Dispatch<SetStateAction<S>>

with

type Dispatch<A> = (value: A) => void;
type SetStateAction<S> = S | ((prevState: S) => S);

Like in React.useState there is also the possibility to update state with a value or a function returning the value. So in order to update state the higher order function from above has to be used as well.

3 Comments

Thanks for adding this detail Peter, it was very helpful to me!
How would you execute the function you are storing in state though? You can't use call ooops() like a normal function, no?
@ANimator120 Yes, you execute it exactly like that. This is also what you can try out in the accepted answer.
4

The previous answers set me on the right track, but I needed to tweak my setState parameters slightly as I wanted to execute a function complete with parameters. The following worked for me!

const [handler, setHandler] = useState(() => {});

setHandler(() => () => enableScheduleById(scheduleId));

Comments

0

I had a similiar issue with a React custom control I was writing, which dealt with different types of reference data records.

Usually, the objects would have a "name" field in them, which I would display in the UI. But for users, I wanted the displayed value to be a mixture of "firstName" and "lastName".

To solve this, I used something like this:

const [primaryKey, setPrimaryKey] = useState('');
const [displayFieldFn, setDisplayFieldFn] = useState(() => (s) => { return s?.name });

useEffect(() => {
    switch (props.datatype) {
        case 'cedent':
            setPrimaryKey('id');
            setDisplayFieldFn(() => (s:any) => s?.legalName);
            break;
        case 'user':
            setPrimaryKey('userId');
            setDisplayFieldFn(() => (s:any) => { return s?.lastName + ' ' + s?.firstName });
            break;
        case 'country':
            setPrimaryKey('countryId');
            setDisplayFieldFn(() => (s:any) => { return s?.name });
            break;
}, [props.datatype]);


function onRowSelected(row) {
    //  The user just click on an item in our grid.. but which one?
    var primaryKeyValue = row.data[primaryKey];   //  eg 1002
    var displayText = displayFieldFn(row.data);   //  eg "Mike Jones"
    . . . 
}

Comments

-1
export default function Component(params) {
    const [count, setCount] = useState(0);
    const [todos, setTodos] = useState([]);

    function increment() {
        setCount((c)=>c+1);
    }
    function addTodo() {
        setTodos((tds)=>[...tds, `New Todo ${count}`])
    }


    return <>
        <h2>My todos</h2>
        {todos&&todos.map((t)=><>{t}<br/></>)}
        <button onClick={addTodo}>Add todo</button>
        <button onClick={increment}>Increment</button>
    </>
    
}

1 Comment

This seems unrelated to the question about using functions in state.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.