Hooks API
In this chapter I want to summarize all the Hooks which are available to us internally and describe how and when they can be used. The official React documentation differentiates between three basic Hooks and seven additional Hooks. These additional Hooks are often used for very specific use cases (such as performance optimizations) or they are extensions of the basic Hooks.
The three basic Hooks which I mentioned briefly beforehand are:
    useState
    useEffect
    useContext
The seven additional Hooks are:
    useReducer
    useCallback
    useMemo
    useRef
    useImperativeHandle
    useLayoutEffect
    useDebugValue

useState

1
const [state, setState] = useState(initialState);
Copied!
This Hook returns a value as well as a function to us, which we can use to update the value. During the first rendering of a component that uses this Hook, this value is equal to the initialState which you pass to the state. If the parameter that has been passed in is a function, it will use the return value of the function as its initial value.
When the update function is called, React ensures that the function always has the same identity and does not create a new function whenever the Hook is called. This is important as it reduces the number of renders and also means that we do not need to pass any other dependencies (as is the case in useEffect() or useCallback()).
useState() will return an array to us of which the first value always denotes the state and the second value is always a function which we use to update said value. Due to array destructuring, we are not limited in naming this value and function. However, conventions have developed that follow the pattern of value / setValue. For example, user and setUser. But of course you could also go for something along the lines of this: changeUser and updateUserState.
The mechanism of actually updating the state is very similar to that of this.setState() which we already encountered in the chapter on Class components. The function can either receive a new value which then replaces the current old value or we can pass an updater function to the function. The updater function receives the previous value and uses the return value from the function as its new state.
But be careful: in contrast to this.setState(), objects are not merged with their previous state but the old state is completely replaced by the new state.
To illustrate this, let's have a look at the following example:
1
import React, { useState, useEffect } from 'react';
2
import ReactDOM from 'react-dom';
3
4
class StateClass extends React.Component {
5
state = { a: 1, b: 2 };
6
7
componentDidMount() {
8
this.setState({ c: 3 });
9
this.setState(() => {
10
return { d: 4 };
11
});
12
}
13
14
render() {
15
// { a: 1, b: 2, c: 3, d: 4 }
16
return <pre>{JSON.stringify(this.state, null, 2)}</pre>;
17
}
18
}
19
20
const StateHook = () => {
21
const [example, setExample] = useState({ a: 1, b: 2 });
22
23
useEffect(() => {
24
setExample({ c: 3 });
25
setExample(() => {
26
return { d: 4 };
27
});
28
}, []);
29
30
// { d: 4 }
31
return <pre>{JSON.stringify(example, null, 2)}</pre>;
32
};
33
34
const App = () => {
35
return (
36
<>
37
<StateClass />
38
<StateHook />
39
</>
40
);
41
};
42
43
ReactDOM.render(<App />, document.getElementById('root'));
Copied!
While StateClass collects and merges the data of all calls of this.setState(), the setState() function in the StateHook completely replaces the old value with the new one. In the Class component, the output returned to us will be {a: 1, b: 2, c: 3, d: 4} whereas the Function component containing the Hook will only return {d: 4} as this has been written into state last.
If the updater function returns the exact same value as the value currently in state, the state update is cancelled and no re-render or side-effects are triggered.

useEffect

1
useEffect(effectFunction, dependenciesArray);
Copied!
This Hook is intended for imperative side effects such as API requests, timers or global event listeners. Normally, these side effects should be avoided in Function components as they can lead to unexpected behaviour or bugs that might be hard to solve for.
The useEffect() Hook combats this problem and allows for a safe mechanism to use side effects within Function components.
The Hook expects a function as its first parameter and a dependency array as its second. The function is called after the component has rendered. If we have passed an optional dependency array to this Hook, the function we pass will only be executed if at least one of the values in the dependency array has changed. If an empty dependency array is passed, the function will only be run on the first render of the component - similar to the componentDidMount() lifecycle method which we learned about with Class components.

Cleaning up side effects

Sometimes side effects leave "traces" that have to be cleaned up once a component is no longer in use. If for example you had intervals which you had started with setInterval(), these should be stopped with clearTimeOut() once the component has been removed. If left untreated, these side effects can lead to actual problems or even memory leaks.
Globally registered event listeners such as resize or orientationchange which have been added to the window object with addEventListener() should also be removed once the component unmounts by using removeEventListener() so they will not be executed anymore if the component itself is not even part of the component tree anymore.
In order to make this cleanup a bit more systematic and easier, we can return a cleanup function from the effect function. If an effect function returns a cleanup function, it is called before each call of the effect function with the exception of the very first call:
1
import React, { useState, useEffect } from "react";
2
import ReactDOM from "react-dom";
3
4
const Clock = () => {
5
const [time, setTime] = useState(new Date());
6
7
useEffect(() => {
8
const intervalId = setInterval(() => {
9
setTime(new Date());
10
}, 1000);
11
return () => {
12
clearInterval(intervalId);
13
};
14
}, []);
15
16
return `${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`;
17
);
18
19
ReactDOM.render(<Clock />, document.getElementById("root"));
Copied!
In the above example we have set up an interval that starts once the component mounts. Once the component unmounts we stop the timer as we would otherwise change the state of the component which is not part of the component tree anymore. If we tried to do this, React would inform us with a warning and suggest to clean up asynchronous tasks and subscriptions in a cleanup function:
Warning: We can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
By returning a cleanup function from the effect function we can stop the interval with a call of clearInterval(). This happens before each call of the effect function, but at the very latest it would happen during unmounting.

Conditional calls of the effect function

Normally the useEffect() Hook or its associated effect function is executed after each render of a component. This way, we ensure that the effect is executed each time once of its dependencies have changed. If we access state or props of a component within the effect function, the side effect should also be executed if one of the dependencies change. If we wanted to display profile data of a particular user and requested this data from an API, the API request should also be initiated if the user's profile that we want to look at changes while the component is already mounted.
However, this might lead to a lot of unnecessary calls of this function and it might even be executed if no data has actually changed since the last render (which are relevant for the side effect). This is why the React allows us to define a dependency array as a second parameter in the effect function. Only if one or more values in the dependency array have changed, the function will be called again. Let's put our previous example into a little code snippet:
1
useEffect(() => {
2
const user = api.getUser(props.username);
3
setUser(user);
4
}, [props.username]);
Copied!
In this example, we have put the username into the dependency array which we use to request data from the API.
While creating such a dependency array, we should take the utmost care to include all values that are present in the function and could change within the lifetime of the component. If the effect function should only be run once and perform a similar task such as componentDidMount(), we leave the dependency array empty.
In order to facilitate or automate the creation of dependency arrays, the eslint-plugin-react-hooks offers an exhaustive-deps rule which will automatically write dependencies used in the effect function into the dependency array or at least warn that they should be included. These could be run on Format on Save or another similar editor configuration.
You can active it by setting "exhaustive-deps": "warn" in therules block of the ESLint configuration.

Sequence of operations

The effect function is called asynchronously with a little bit of delay after the layout and paint phase of the browser. For most side effects this should be sufficient. You might run into situations though in which it is necessary to to synchronously run side effects. For example, you might need to perform DOM mutations and a delay in execution would lead to an inconsistent user interface or jarring content on the screen.
To deal with these problems, React introduced the useLayoutEffect() Hook. It works almost identical to the useEffect() Hook: it expects an effect function which can also return a cleanup function and also contains a dependency array. This dependency array is identical to that of the regular useEffect() Hook. The difference in the two Hooks is that the useLayoutEffect() Hook is executed synchronously and run just after all DOM mutations have finished, as opposed to asynchronously as is the case in the regular useEffect() Hook.
useLayoutEffect() can read from the DOM and can also synchronously modify it before the browser will display these changes in its paint phase.

Asynchronous effect functions

Even if effect functions can be run with delay, they are not allowed to be asynchronous by definition, and are not allowed to return a promise. If we tried to perform such an operation, React would give us the following warning:
Warning: An Effect function must not return anything besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, you may write an async function separately and then call it from inside the effect […]
In the future, React will provide a more idiomatic solution for data fetching that doesn't involve writing effects manually.
In the above example, an incorrect useEffect() Hook could have looked like the following (please don't do this):
1
useEffect(async () => {
2
const response = await fetch('https://api.github.com/users/manuelbieh');
3
const accountData = await response.json();
4
setGitHubAccount(accountData);
5
6
fetchGitHubAccount('manuelbieh');
7
}, []);
Copied!
This is not allowed as the effect function is declared with the async keyword. So how would we go about solving this problem? There's a relatively simple solution to this problem. We move the asynchronous part of this function into its own asynchronous function within the effect function and then only call this function:
1
import React, { useEffect, useState } from 'react';
2
import ReactDOM from 'react-dom';
3
4
const App = (props) => {
5
const [gitHubAccount, setGitHubAccount] = useState();
6
7
useEffect(() => {
8
const fetchGitHubAccount = async () => {
9
const response = await fetch(
10
`https://api.github.com/users/${props.username}`
11
);
12
const accountData = await response.json();
13
setGitHubAccount(accountData);
14
};
15
16
fetchGitHubAccount();
17
}, [props.username]);
18
19
if (!gitHubAccount) {
20
return null;
21
}
22
23
return (
24
<p>
25
{gitHubAccount.name} has {gitHubAccount.public_repos} public repos
26
</p>
27
);
28
};
29
30
ReactDOM.render(<App username="manuelbieh" />, document.getElementById('root'));
Copied!
In this case, the effect function itself is not asynchronous. The asynchronous functionality has been extracted into its own asynchronous function fetchGitHubAccount() which is defined inside of the useEffect() Hook.
The asynchronous function does not necessarily need to be defined inside of the effect function. But the effect function itself is not allowed to be asynchronous.

useContext

1
const myContextValue = useContext(MyContext);
Copied!
This Hook only expects one parameter: a context type which we create by calling React.createContext(). It will then return the value of the next highest context provider in the component hierarchy.
The useContext() Hook acts like a context consumer component and causes a re-render of the Function component as soon as the value of the context in the provider element changes.
Using this Hook is optional and it is still possible to create Context consumers in JSX using Function components. However, the Hook is much more convenient and easier to read as no new hierarchical layer is created in the component tree.

useReducer

1
const [state, dispatch] = useReducer(reducerFunc, initialState, initFunc);
Copied!
The useReducer() Hook is an alternative solution for useState() and allows us to manage more complex states. It is based on flux architecture in which a reducer function creates a new state by being passed the last state and a so-called action.
The reducer function is called by executing a dispatch function which in turn receives an action. The action is an object which always has a type property and often a payload property attached. From this action and the previous state, the reducer function can then create the new state. One could summarize this in the following form: (oldState, action) => newState.
Let us have a look at a simple example. We have developed a Counter component which can increment or decrement a counter by pressing a + and - button:
1
import React, { useReducer } from 'react';
2
import ReactDOM from 'react-dom';
3
4
const initialState = {
5
count: 0,
6
};
7
8
const reducerFunction = (state, action) => {
9
switch (action.type) {
10
case 'INCREMENT':
11
return { count: state.count + 1 };
12
case 'DECREMENT':
13
return { count: state.count - 1 };
14
default:
15
throw new Error('Unknown action');
16
}
17
};
18
19
const Counter = () => {
20
const [state, dispatch] = useReducer(reducerFunction, initialState);
21
22
return (
23
<div>
24
<h1>{state.count}</h1>
25
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
26
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
27
</div>
28
);
29
};
30
31
ReactDOM.render(<Counter />, document.getElementById('root'));
Copied!
We have defined the initial State initialState and the reducer function reducerFunction. The initial state only consists of an object which holds a count property which is initially 0. The reducer function on the other hand expects a state and an action which are later passed to the reducer function by calling the dispatch function. These two parameters will then create the new state. But beware: instead of mutating an existing state, we always have to create a new state! Otherwise mutations of the existing state will lead to side effects which are not intended and cause incorrect display of components. A reducer function should always be a pure function.
The reducer function, as well as the initial state, are then passed to the useReducer() Hook which will return a tuple. The tuple consists of two values: the first element will portray the current state in this particular rendering phase and the second value will be the so-called dispatch function.
If we want to change our current state, we call the dispatch function and pass this function an action. In our current example, we achieve this by clicking one of the two buttons which will either dispatch the { type: "INCREMENT" } (to increase the counter) action or { type: "DECREMENT" } (to decrease the counter) action/
If an action has been "dispatched", a new state is created and React will trigger a re-render. The new state will now be accessible in the new state variable which was returned by the reducer function. If however the same state was returned from the reducer, no re-render will be triggered.

The third parameter

Apart from the reducer function and initialState which we always need to specify, we can also pass a third optional parameter to the useReducer() Hook. This third parameter is called an init function which can be used to calculate the initial state. The function could be used to extract the value of a reducer in an external function which is outside of the reducer itself.
If such an init function has been passed to the Hook, it will be called during the first call. The initialState will be passed to it as its initial argument. This can be really useful if the initial state of your component is based on props for example. These props can be passed as the second parameter inside of the init function which can then create the initial state of the reducer based on these:
1
import React, { useReducer } from 'react';
2
import ReactDOM from 'react-dom';
3
4
const reducerFunction = (state, action) => {
5
switch (action.type) {
6
case 'INCREMENT':
7
return { count: state.count + 1 };
8
case 'DECREMENT':
9
return { count: state.count - 1 };
10
default:
11
throw new Error('Unknown action');
12
}
13
};
14
15
const initFunction = (initValue) => {
16
return { count: initValue };
17
};
18
19
const Counter = (props) => {
20
const [state, dispatch] = useReducer(
21
reducerFunction,
22
props.startValue,
23
initFunction
24
);
25
26
return (
27
<div>
28
<h1>{state.count}</h1>
29
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
30
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
31
</div>
32
);
33
};
34
35
ReactDOM.render(<Counter startValue={3} />, document.getElementById('root'));
Copied!
In this example, the useReducer() Hook has been extended to include a third and optional parameter: the init function. The initialState is now an argument for the init function. The value for this argument is passed to the component via propsstartValue in our case.

Reducers in practice

The guiding principle of reducers should be known to those in the React community who have had exposure to Redux. Redux is a library that allows us to manage complex state in a comfortable manner and was the first point of call whenever handling local state became hard to read and cumbersome. It also created a solution for the so-called "prop drilling" which meant that previous props needed to be passed through multiple hierarchical layers.
Redux manages reducer functions and makes state and their dispatch functions available to those components which should read or modify the global state. The useReducer() Hook is React's custom solution to realize complex state management using reducer functions.
A common use case for reducers forms the management of API requests. Common conventions dictate that three actions for each API request should be defined:
    an action which informs the application that the data is loading when the request has started
    an action which resets the loading state and (if the request has failed) can inform the state of an error
    an action which writes the data received by the API request into state if it was successful
Let's have a look at an example using our previous account data example using the GitHub API:
1
import React, { useEffect, useReducer } from 'react';
2
import ReactDOM from 'react-dom';
3
4
const initialState = {
5
data: null,
6
isLoading: false,
7
isError: false,
8
lastUpdated: null,
9
};
10
11
const accountReducer = (state, action) => {
12
switch (action.type) {
13
case 'REQUEST_START':
14
return {
15
...state,
16
isLoading: true,
17
};
18
case 'REQUEST_SUCCESS':
19
return {
20
...state,
21
data: action.payload,
22
isLoading: false,
23
isError: false,
24
lastUpdated: action.meta.lastUpdated,
25
};
26
case 'REQUEST_ERROR':
27
return {
28
...state,
29
isLoading: false,
30
isError: true,
31
};
32
}
33
};
34
35
const RepoInfo = (props) => {
36
const [state, dispatch] = useReducer(accountReducer, initialState);
37
38
useEffect(() => {
39
const fetchGitHubAccount = async (username) => {
40
try {
41
dispatch({
42
type: 'REQUEST_START',
43
});
44
const response = await fetch(
45
`https://api.github.com/users/${username}`
46
);
47
48
const accountData = await response.json();
49
dispatch({
50
type: 'REQUEST_SUCCESS',
51
payload: accountData,
52
meta: {
53
lastUpdated: new Date(),
54
},
55
});
56
} catch (err) {
57
dispatch({
58
type: 'REQUEST_ERROR',
59
error: true,
60
});
61
}
62
};
63
64
fetchGitHubAccount(props.username);
65
}, [props.username]);
66
67
if (state.isError) {
68
return <p>An error occurred.</p>;
69
}
70
71
if (state.isLoading) {
72
return <p>Loading...</p>;
73
}
74
75
if (!state.data) {
76
return <p>No GitHub account has been loaded.</p>;
77
}
78
79
return (
80
<p>
81
{state.data.name} has {state.data.public_repos} public repositories.
82
</p>
83
);
84
};
85
86
ReactDOM.render(
87
<RepoInfo username="manuelbieh" />,
88
document.getElementById('root')
89
);
Copied!
The useReducer() Hook is used and passed to the accountReducer function. In this function, we deal with the three actions of type REQUEST_START, REQUEST_SUCCESS, and REQUEST_ERROR.
The initialState consists of an object with an empty data property, an isLoading and isError flag and a lastUpdated property. The flags inform us whether the data has actually loaded or whether an error occurred whilst the lastUpdated property will store a timestamp of the last successful request. We can use these later to only use one request per minute or to signal to the user that they might be seeing data but that the interface has not changed for a while.
In addition, we use a useEffect() Hook to initiate loading the data once the GitHub username in the props changes. Once this has happened, we dispatch the REQUEST_START action. The reducer will then create the new state:
1
{
2
data: null,
3
- isLoading: false,
4
+ isLoading: true,
5
isError: false,
6
lastUpdated: null,
7
}
Copied!
As we have defined a condition a bit lower down in our component, we will now display the following:
1
if (state.isLoading) {
2
return <p>Loading...</p>;
3
}
Copied!
This is a clear signal for the user that data is currently being loaded.
After this state, we can be left with one of the following cases: the request either fails, or data is successfully obtained from the API.
If the requests failed, the REQUEST_ERROR action would be dispatched. The state would be reflected as such:
1
{
2
data: null,
3
- isLoading: true,
4
+ isLoading: false,
5
- isError: false,
6
+ isError: true,
7
lastUpdated: null,
8
};
Copied!
As no further request will be fired, the isLoading flag will be reset from true to false so as to not signal to the user that data might still be loading. When an error has occurred, the state of isError is set from false to true. The code snippet above also contains a condition to handle the state of an error, so we can display a message to the user:
1
if (state.isError) {
2
return <p>An error occurred.</p>;
3
}
Copied!
It might be a good idea to tell the user which operation has failed and how they might be able to recover from the error. Maybe the provided username did not exist and we could offer an opportunity to the user to re-enter their username and correct their mistake. Another possibility could be that the API might not be available at the moment which could inform the user to try again at a later point of time.
If however the request was dealt with successfully and we could obtain data from the API, we dispatch the REQUEST_SUCCESS action. This not only contains a payload but also a meta property which includes the timestamp of the request.
The new state which is created by the reducer differs from the previous state in the following way:
1
{
2
- data: null,
3
+ data: {
4
+ "login": "manuelbieh",
5
+ "name": "Manuel Bieh",
6
+ "public_repos": 59,
7
+ [...]
8
+ },
9
- isLoading: true,
10
+ isLoading: false,
11
isError: false,
12
- lastUpdated: null,
13
+ lastUpdated: "2019-03-19T02:29:10.756Z",
14
}
Copied!
The data property contains the data which we have received from the API. The state of isLoading is set back to false andlastUpdated is updated to reflect the point in time when the data was successfully written to state. Based on this information, we can now display to the user:
1
return (
2
<p>
3
{state.data.name} has {state.data.public_repos} public repositories.
4
</p>
5
);
Copied!
Apart from writing our first more complex reducer, we have also successfully learned about how useEffect() and useReducer() can be used together.
As an aside: Similar to the useState() Hook, the useReducer() Hook will not trigger another re-render if the reducer function returns the exact same state as before.

useCallback

1
const memoizedFunction = useCallback(callbackFunction, dependencyArray);
Copied!
The useCallback() Hook can be used to optimize the performance of an application. It receives a function and then creates a unique identity of that function, which will remain active until the dependencies of the Hook itself change.
This is important as we need to provide the same reference to a function, when dealing with PureComponents, when functions implement their own shouldComponentUpdate() method or if they are wrapped by React.memo().
useCallback() expects two parameters. The first being a function and the second being a dependency array (similar to that in useEffect()). It will return a stable reference to the function that we passed in, meaning that the reference only changes if one of its dependencies changed. Up to this point, references to PureComponents or components with React.memo() are the same.
But this sounds a little complicated in theory, let's look at an example:
1
import React, { useState, useCallback } from 'react';
2
import ReactDOM from 'react-dom';
3
4
const FancyInput = React.memo(({ name, onChange }) => {
5
console.log('Rendering FancyInput');
6
return <input type="text" name={name} onChange={onChange} />;
7
});
8
9
const Form = () => {
10
const [values, setValues] = useState({});
11
12
const changeHandler = (e) => {
13
const { name, value } = e.target;
14
15
setValues((state) => {
16
return {
17
...state,
18
[name]: value,
19
};
20
});
21
};
22
23
return (
24
<>
25
<pre>{JSON.stringify(values, null, 2)}</pre>
26
<FancyInput name="example" onChange={changeHandler} />
27
</>
28
);
29
};
30
31
ReactDOM.render(<Form />, document.getElementById('root'));
Copied!
We have defined two components here: FancyInput and Form. The Form component renders a FancyInput component and not only passes it a name attribute but also a function. It will change the state of the Form component whenever changes are made to the input field and subsequently trigger a re-render.
The changeHandler function is created in the form component and is thus generated fresh with every render, meaning the reference to the function changes. We are passing the same function but not an identical one.
Thus, we cannot make use of the React.memo() optimization mechanism in FancyInput. React.memo() checks before each re-render of a component if its props changed compared to the previous render and will trigger a re-render if this is the case. As the changeHandler function is generated from scratch every time the Form component renders, this condition will always be true and the FancyInput will always re-render too.
We can use useCallback() to combat this. By wrapping our changeHandler() function in this Hook, React can create a unique and stable reference and can safely return it so it can be used in the FancyInput component without triggering unnecessary re-renders:
1
const changeHandler = useCallback((e) => {
2
const { name, value } = e.target;
3
4
setValues((state) => {
5
return {
6
...state,
7
[name]: value,
8
};
9
});
10
}, []);
Copied!
We can now use the optimization techniques of React.memo() (or in Class components: PureComponent) without triggering unnecessary renders.
If the function depends on values which can change in the lifespan of the component, we can put these in the dependency array (as was the case in useEffect()) as the second parameter. React will then create a new function with a new reference, if one of the dependencies changes.
As was the case in the useEffect() Hook, the exhaustive-deps rule of theeslint-plugin-react-hooks can help us to configure Dependency Arrays.

useMemo

1
const memoizedValue = useMemo(valueGetterFunction, dependencyArray);
Copied!
The other Hook that's useful for hardcore performance optimization is the useMemo() Hook. It works similarly to the useCallback() Hook, however it does not provide a unique identity for the function going in, but for the return value from the function which has been passed into the useMemo() Hook.
So this snippet of code:
1
useCallback(fn, deps);
Copied!
corresponds to this:
1
useMemo(() => fn, deps);
Copied!
While useCallback() returns a memoized (a "remembered") version of the function that has been passed in, useMemo() provides a memoized version of the return value of the function that has been passed in. useMemo() can be really useful in situations where functions perform complex computational tasks that do not need to be executed in each render.
Let us have a look at a non-optimized component:
1
import React, { useState, useMemo } from 'react';
2
import ReactDOM from 'react-dom';
3
4
const fibonacci = (num) =>
5
num <= 1 ? 1 : fibonacci(num - 1) + fibonacci(num - 2);
6
7
const FibonacciNumber = ({ value }) => {
8
const result = fibonacci(value);
9
return (
10
<p>
11
{value}: {result}
12
</p>
13
);
14
};
15
16
const App = () => {
17
const [values, setValues] = useState([]);
18
19
const handleKeyUp = (e) => {
20
const { key, target } = e;
21
const { value } = target;
22
if (key === 'Enter') {
23
if (value > 40 || value < 1) {
24
alert('Invalid value');
25
return;
26
}
27
setValues((values) => values.concat(target.value));
28
}
29
};
30
31
return (
32
<>
33
<input type="number" min={1} max={40} onKeyUp={handleKeyUp} />
34
{values.map((value, i) => (
35
<FibonacciNumber value={value} key