Of all the hooks available in React, useReducer
is easily my favourite. It’s nice to be able to solve simple problems with useState
, but in component’s with even a smaller amount of complexity than can be solved with a boolean state value, I tend to favour useReducer
.
Let’s dig in, see some examples, and find out why.
The examples I’m going to use are somewhat trivial only for the purposes of easy demonstration. The concepts learned are useful on larger, more complex components.
From useState to useReducer
The first example is a simple JavaScript / JSX counter component. For me, this would actually be preferable to keep as using the useState
approach, but showing a way we are all familiar with, and how that transfers to useReducer
has some value, I feel.
OK, here we go:
// src/Counter.jsx
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
Code language: JavaScript (javascript)
You could ‘upgrade’ this to a TypeScript component very easily:
// src/Counter.tsx
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState<number>(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
Code language: TypeScript (typescript)
In this TypeScript version, we explicitly specify the type of the count
state using useState<number>(0)
. This tells TypeScript that count
is expected to be a number.
It really doesn’t make a huge amount of difference at this stage. But the types will come to help us as we progress.
In our current Counter component implementation we start with a number, 0
by default.
We can then click on a button to add one to the number, or remove one from the number.
I’ve added a bit of style using Tailwinds and Daisy UI, and without much effort we have something looking like this:
<div>
<p className="text-3xl mb-4">Count: {count}</p>
<button onClick={increment} className="btn btn-neutral mr-2">
Increment
</button>
<button onClick={decrement} className="btn btn-neutral">
Decrement
</button>
</div>
Code language: JavaScript (javascript)
Let’s pretend our Counter website suddenly got to the front page of Reddit, and usage is going to the moon 🚀🌚
Feature requests are flying in left and right. The biggest request, by far, is for a way to reset the counter.
Adding A ‘Reset’ Functionality
In this example, we’re managing the state of the count using useState
. Now, let’s add a feature to reset the count back to the initial value. With useState
, you can do it like this:
// src/Counter.jsx
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
const reset = () => {
setCount(0);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
}
export default Counter;
Code language: JavaScript (javascript)
With three different actions I’d already be starting to consider useReducer
. These are simple transitions, but in reality our components often are more complex, even with a small handful of possible states. The more complex the state, the more complex the transitions between states, and once you have a bunch of components in play, remembering how everything works down to the nitty gritty can become challenging.
Let’s see how this same component might look when using useReducer
:
// src/Counter.jsx
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return initialState;
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
</div>
);
}
export default Counter;
Code language: JavaScript (javascript)
I was going to do a line highlight here of everything that changed, but, well… everything has changed 🙂
Well, I say that, but only the internals have changed. The behaviour is actually fundamentally identical to what we had in the useState
example.
Let’s break down the component and its key elements:
Initial State
const initialState = { count: 0 };
Code language: JavaScript (javascript)
Here, initialState
defines the initial state of the component, which includes a single property count
initialised to 0
.
Reducer Function
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return initialState;
default:
return state;
}
}
Code language: JavaScript (javascript)
The reducer
function is responsible for updating the state based on different actions that we define.
The reducer
function takes the current state and an action object as parameters and returns a new state.
In this case, it handles three types of actions: 'INCREMENT'
, 'DECREMENT'
, and 'RESET'
.
Depending on the action type, it updates the count
property of the state accordingly.
useReducer
Hook
const [state, dispatch] = useReducer(reducer, initialState);
Code language: JavaScript (javascript)
The useReducer
hook is used to manage state in a more structured way. It takes two arguments: the reducer
function and the initialState
.
Much like useState
, it returns an array with two elements:
state
: This holds the current state, which is initially set toinitialState
.dispatch
: This is a function used to dispatch actions to the reducer, triggering state updates.
Rendered UI
Finally we have the actual output of our component. For simplicity I’ve kept the styles off this output.
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
</div>
Code language: HTML, XML (xml)
We start by displaying the current value of count
from the state
object.
Then there are the three buttons: “Increment,” “Decrement,” and “Reset.”
When these buttons are clicked, they dispatch the corresponding action to the reducer
function by calling dispatch({ type: 'OUR_ACTION_TYPE' })
.
WebStorm actually gets a bit unhappy about some of this:
You can see a few bits are lightly underlined. No errors here, just warnings.
They all go away if we convert over to TypeScript:
// src/Counter.tsx
import React, { useReducer } from "react";
interface State {
count: number;
}
type Action = { type: "INCREMENT" } | { type: "DECREMENT" } | { type: "RESET" };
const initialState: State = { count: 0 };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
case "RESET":
return initialState;
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
<button onClick={() => dispatch({ type: "RESET" })}>Reset</button>
</div>
);
}
export default Counter;
Code language: TypeScript (typescript)
In the TypeScript version we need to tell TypeScript exactly what kind of things we expect and allow.
Initial State
On lines 5-7 we define a custom interface
. It could be called anything, but as it will represent the components internal state, I went with the name State
.
Our component handles one piece of state, and that is the count
. The count
is a number
, much like we had back when we typed useState
:
const [count, setCount] = useState<number>(0);
Code language: HTML, XML (xml)
Action Type
The Action
type is really where the most interesting part of this change occurs.
Again, Action
is just a name. But it explains what concept we are wanting to describe.
Our component has three different things it can do. Three actions. Again, you don’t need to follow the convention given, but it’s really common to use a type
key, and then a string property, typically in uppercase, to describe what is happening.
type Action = { type: "INCREMENT" } | { type: "DECREMENT" } | { type: "RESET" };
Code language: JavaScript (javascript)
Here we have a union type – denoted by the pipe between the three different type definitions.
You could also write this as:
type Action =
| { type: "INCREMENT" }
| { type: "DECREMENT" }
| { type: "RESET" };
Code language: JavaScript (javascript)
Which says that when we type something to be Action
, it may be an object in any of them three different shapes.
const initialState: State = { count: 0 };
Code language: JavaScript (javascript)
We defined the State
interface, and now we tell TypeScript that our initialState
object should have that type. In other words, initialState
should be an object that has a count
property with a numerical value.
Typed Reducer
And then we can add types to our reducer
function:
function reducer(state: State, action: Action): State {
Code language: JavaScript (javascript)
Which brings this all together.
The reducer
function takes our two arguments, just like we covered earlier, only now they are typed.
The first argument is going to an object that has the type / shape of the State
interface. So, again, some object that has a count
property with a numerical value. It could be anything. Whatever properties you defined on your own component’s State
.
And then an action
.
That action can only be one of the three actions, in exactly those shapes, that we defined in the Action
type. This works super well to give us some lovely intelli-sense / autocomplete in our IDE.
You can see this in action. When you use the dispatch
function in your code, TypeScript is now clever enough to work out that the only viable and allowable arguments must match one of the Action
shapes you defined:
If you try to use a type
that you didn’t specify in your Action
type, or you add in some extra or incorrect property, TypeScript will throw out various warnings, and may stop you from compiling your code – depending on how strict your settings are in tsconfig.json
.
TS2345: Argument of type { type: "INCREMENT"; someExtraKey: string; } is not assignable to parameter of type Action
Object literal may only specify known properties, and someExtraKey does not exist in type { type: "INCREMENT"; }
Code language: CSS (css)
That might seem trivial on a small example component like this, but on larger projects, and with components you personally didn’t create, it can be a super helpful thing in my experience.
With this, WebStorm is really quite happy:
Let’s look at one more example, which is a little more complex.
Shopping Basket Example
Whilst the Counter component shows everything involved in useReducer
for pretty much every use case I’ve ever had, it still might not sell the idea to you because the example is trivial.
If we look at a slightly more complex component, the use of a reducer
function – and how testable that function is – should hopefully sell this concept to you, if you aren’t already sold.
Using useState for Shopping Basket
We’ll start with useState
and then refactor it to use useReducer
to illustrate how and why useReducer
can be a better approach in more complex scenarios:
// src/ShoppingBasket.jsx
import React, { useState } from "react";
const productVariations = [
{ id: 1, name: "Product 1", price: 10 },
{ id: 2, name: "Product 2", price: 5 },
{ id: 3, name: "Product 3", price: 15 },
{ id: 4, name: "Product 4", price: 8 },
{ id: 5, name: "Product 5", price: 12 },
];
function ShoppingBasket() {
const [basket, setBasket] = useState([]);
const [total, setTotal] = useState(0);
const addToBasket = (product) => {
setBasket([...basket, product]);
setTotal(total + product.price);
};
const isProductInBasket = (productId) => {
return basket.some((product) => product.id === productId);
};
const removeFromBasket = (product) => {
const updatedBasket = basket.filter((item) => item.id !== product.id);
setBasket(updatedBasket);
setTotal(total - product.price);
};
const resetBasket = () => {
setBasket([]);
setTotal(0);
};
return (
<div>
<h2>Shopping Basket</h2>
<ul>
{basket.map((product) => (
<li key={product.id}>
{product.name} - £{product.price}
<button
onClick={() => removeFromBasket(product)}
>
Remove
</button>
</li>
))}
</ul>
<p>Total: £{total}</p>
{productVariations.map((product) => (
<button
key={product.id}
onClick={() => addToBasket(product)}
disabled={isProductInBasket(product.id)}
>
Add {product.name}
</button>
))}
<button onClick={resetBasket}>
Reset Basket
</button>
</div>
</div>
);
}
export default ShoppingBasket;
Code language: JavaScript (javascript)
Here’s how that looks with a bit of style applied:
And the behaviour is you can select each product, in any order, exactly once, before it is disabled / unavailable for selection. You can either remove it using the inline button, or clear / reset the entire basket:
Again, it is a trivialised component to a point. The aim is to show the more involved state updates.
Let’s break down each part of the state manipulation logic:
State Initialisation
const [basket, setBasket] = useState([]);
const [total, setTotal] = useState(0);
Code language: JavaScript (javascript)
Two state variables, basket
and total
, are initialised using the useState
hook.
basket
represents the array of products in the shopping basket, and total
represents the running total cost of the items currently in the basket.
Check if Product is in Basket
const isProductInBasket = (productId) => {
return basket.some((product) => product.id === productId);
};
Code language: JavaScript (javascript)
The isProductInBasket
function takes a productId
as an argument and checks if a product with that ID is already in the basket
array. It uses the some
method to determine if at least one item in the basket has a matching ID.
The use of some
here is a left over from the original example I coded up. I wanted to be able to add multiple copies of the same product to the basket, but that got even more complicated.
Add to Basket
const addToBasket = (product) => {
if (!isProductInBasket(product.id)) {
setBasket([...basket, product]);
setTotal(total + product.price);
}
};
Code language: JavaScript (javascript)
The addToBasket
function takes a product
as an argument and checks if the product is not already in the basket using isProductInBasket
.
If the product is not in the basket, it updates the basket
state by adding the new product and increases the total
by the product’s price.
Two state updates here, and it’s critical that both are performed as an individual unit of behaviour. Hmm.
Remove from Basket
const removeFromBasket = (product) => {
const updatedBasket = basket.filter((item) => item.id !== product.id);
setBasket(updatedBasket);
setTotal(total - product.price);
};
Code language: JavaScript (javascript)
The removeFromBasket
function also takes a product
as an argument.
It creates a new array, updatedBasket
, by filtering out the product with the matching ID.
The basket
state is then updated with this new array, and the total
is decreased by the price of the removed product.
Again we have two state updates happening in one function, both of which are absolutely critical to this component behaving properly.
Reset Basket
const resetBasket = () => {
setBasket([]);
setTotal(0);
};
Code language: JavaScript (javascript)
The resetBasket
function sets both the basket
and total
states to their initial values, effectively clearing the shopping basket.
We must remember to keep these two setters in sync with the default values used in the State Initialisation step. Yikes. What could go wrong?
Refactoring the useState Approach to TypeScript
Before we switch to useReducer
, let’s take the existing plain JavaScript ShoppingBasket
component and rewrite it using TypeScript.
// src/ShoppingBasket.tsx
import React, { useState } from "react";
interface Product {
id: number;
name: string;
price: number;
}
const productVariations: Product[] = [
{ id: 1, name: "Product 1", price: 10 },
{ id: 2, name: "Product 2", price: 5 },
{ id: 3, name: "Product 3", price: 15 },
{ id: 4, name: "Product 4", price: 8 },
{ id: 5, name: "Product 5", price: 12 },
];
function ShoppingBasket() {
const [basket, setBasket] = useState<Product[]>([]);
const [total, setTotal] = useState<number>(0);
const isProductInBasket = (productId: Product['id']): boolean => {
return basket.some((product) => product.id === productId);
};
const addToBasket = (product: Product): void => {
if (!isProductInBasket(product.id)) {
setBasket([...basket, product]);
setTotal(total + product.price);
}
};
const removeFromBasket = (product: Product): void => {
const updatedBasket = basket.filter((item) => item.id !== product.id);
setBasket(updatedBasket);
setTotal(total - product.price);
};
const resetBasket = (): void => {
setBasket([]);
setTotal(0);
};
return (
<div>
<h2>Shopping Basket</h2>
<ul>
{basket.map((product) => (
<li key={product.id}>
{product.name} - £{product.price}
<button onClick={() => removeFromBasket(product)}>Remove</button>
</li>
))}
</ul>
<p>Total: £{total}</p>
{productVariations.map((product) => (
<button
key={product.id}
onClick={() => addToBasket(product)}
disabled={isProductInBasket(product.id)}
>
Add {product.name}
</button>
))}
<button onClick={resetBasket}>Reset Basket</button>
</div>
);
}
export default ShoppingBasket;
Code language: TypeScript (typescript)
In this TypeScript version:
- I added the
Product
interface to define the structure of a product. - The
basket
state is explicitly typed as an array ofProduct
. - The
total
state is explicitly typed as anumber
. - Function parameters and return types are explicitly typed.
- The
isProductInBasket
,addToBasket
,removeFromBasket
, andresetBasket
functions have type annotations for better type safety.
There are a couple of things that I want to call out in slightly more detail.
Slightly Nicer Basket Reset
The first is that whilst we do have to still explicitly setBasket
and setTotal
back to default values, we do at least get some further assurances when using TypeScript:
We’re still responsible for setting the values, but TypeScript at least ensures they match the types we provided in the original calls to useState
:
const [basket, setBasket] = useState<Product[]>([]);
const [total, setTotal] = useState<number>(0);
Code language: TypeScript (typescript)
basket
must be an empty array, or an array of things that look like Product
.
And likewise, total
can only be a number.
But … any number. We need not reset the total
to 0
like we did in the original call. We still have to keep those two in sync. It sounds so trivial, but this is a legitimate source of bugs on larger projects.
Re-use Your Type Definitions: Indexed Access Types
This is one I see frequently out there in the real world:
const isProductInBasket = (productId: number): boolean => {
return basket.some((product) => product.id === productId);
};
// versus
const isProductInBasket = (productId: Product['id']): boolean => {
return basket.some((product) => product.id === productId);
};
Code language: TypeScript (typescript)
The two lines are functionally identical.
Our Product
interface defines id
as a number
:
interface Product {
id: number;
name: string;
price: number;
}
Code language: CSS (css)
And either give us type safety:
But we already defined the type of id
on our Produc
t interface. By defining the parameter of isProductInBasket
as number
, we have disconnected the two concepts.
Sure, they are both number
… now. But what about in the future? What if we swap to a uuid
or something? Well, now we need to update two places, or hope that our unit tests (which we do have, right?) catch the issue.
It sounds so trivial. However it, again, is a legitimate source of real world problems.
TypeScript calls this concept an Indexed Access Type. Why not use it?
Refactoring To useReducer
We’re almost there now.
I’m going to skip the JavaScript version of this component now, because in reality I haven’t made a plain JSX component in a very long time and have no intention of going back there when TypeScript exists.
We’ve covered the useState
version of the ShoppingBasket
. Now let’s refactor the code to retain the exact same behaviour, but make use of the useReducer
hook.
// src/ShoppingBasket.tsx
import React, { useReducer } from "react";
interface Product {
id: number;
name: string;
price: number;
}
interface State {
basket: Product[];
total: number;
}
type Action =
| { type: "ADD_TO_BASKET"; product: Product }
| { type: "REMOVE_FROM_BASKET"; product: Product }
| { type: "RESET_BASKET" };
const initialState: State = {
basket: [],
total: 0,
};
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TO_BASKET":
return {
basket: [...state.basket, action.product],
total: state.total + action.product.price,
};
case "REMOVE_FROM_BASKET":
const updatedBasket = state.basket.filter(
(item) => item.id !== action.product.id,
);
return {
basket: updatedBasket,
total: state.total - action.product.price,
};
case "RESET_BASKET":
return initialState;
default:
return state;
}
};
const isProductInBasket = (
basket: State["basket"],
productId: Product["id"],
): boolean => {
return basket.some((product) => product.id === productId);
};
const productVariations: Product[] = [
{ id: 1, name: "Product 1", price: 10 },
{ id: 2, name: "Product 2", price: 5 },
{ id: 3, name: "Product 3", price: 15 },
{ id: 4, name: "Product 4", price: 8 },
{ id: 5, name: "Product 5", price: 12 },
];
function ShoppingBasket() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h2>Shopping Basket</h2>
<ul>
{state.basket.map((product) => (
<li key={product.id}>
{product.name} - £{product.price}
<button
onClick={() => dispatch({ type: "REMOVE_FROM_BASKET", product })}
>
Remove
</button>
</li>
))}
</ul>
<p>Total: £{state.total}</p>
<div>
{productVariations.map((product) => (
<button
key={product.id}
onClick={() => dispatch({ type: "ADD_TO_BASKET", product })}
disabled={isProductInBasket(state.basket, product.id)}
>
Add {product.name}
</button>
))}
<button
onClick={() => dispatch({ type: "RESET_BASKET" })}
>
Reset Basket
</button>
</div>
</div>
);
}
export default ShoppingBasket;
Code language: TypeScript (typescript)
There are two things that jump out at a glance:
- There is a lot of setup / pre-stuff
- The actual component rendering logic is now really quite slim
So yeah, we jumped up by about 30 lines.
Was it worth it?
I’d say so. And here’s why I think that.
Type Safety
We’ve explicitly described our component’s types:
interface Product {
id: number;
name: string;
price: number;
}
interface State {
basket: Product[];
total: number;
}
type Action =
| { type: "ADD_TO_BASKET"; product: Product }
| { type: "REMOVE_FROM_BASKET"; product: Product }
| { type: "RESET_BASKET" };
Code language: TypeScript (typescript)
Throughout this post we’ve covered all of this information. The only addition is the inclusion of product
in two of our Action
s.
As I said earlier, our types are ours.
They represent our things. We can change them however we like, and however we need.
To add or remove a product to or from the basket, we need to say which Product
we are working with. Now that information is captured in the specific Action
. This means if we want to add a product to the basket, we must provide the product we are adding.
Makes a lot of sense.
Declare And ReUse Initial State
One of my big gripes with previous iterations of this component was the need to set and reset the initial state.
Now we define the components initial state only once:
const initialState: State = {
basket: [],
total: 0,
};
Code language: TypeScript (typescript)
But we make use of it twice.
const initialState: State = {
basket: [],
total: 0,
};
const reducer = (state: State, action: Action): State => {
switch (action.type) {
// ...
case "RESET_BASKET":
return initialState;
default:
return state;
}
};
// ...
function ShoppingBasket() {
const [state, dispatch] = useReducer(reducer, initialState);
Code language: TypeScript (typescript)
It’s a small change, but it helps reduce bugs. Don’t repeat yourself, and all that.
Our State Is Highly Unit Testable
For me, being a test loving saddo, the biggest win is the fact that the components state is a highly testable pure function:
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TO_BASKET":
return {
basket: [...state.basket, action.product],
total: state.total + action.product.price,
};
case "REMOVE_FROM_BASKET":
const updatedBasket = state.basket.filter(
(item) => item.id !== action.product.id,
);
return {
basket: updatedBasket,
total: state.total - action.product.price,
};
case "RESET_BASKET":
return initialState;
default:
return state;
}
};
Code language: JavaScript (javascript)
There’s something so satisfying about testing a pure function. Which is to say if the function is given the same arguments, it always returns the same result.
1 + 1 = 2
There’s no API calls firing off or reliance on some external state that may have changed leading to a different result.
Everything is self contained here. You could extract this logic out to a separate file, unit test it and import it back in. Confidence. I love it.
Monkey Jungle
A lot of this stuff is opinion.
Here’s another.
const isProductInBasket = (
basket: State["basket"],
productId: Product["id"],
): boolean => {
return basket.some((product) => product.id === productId);
};
Code language: JavaScript (javascript)
Does that need extracting?
I would say it makes it easier to unit test, so yes, extract it.
Another way to write this would be to have it inside the component function itself, and get access to state
via closure:
function ShoppingBasket() {
const [state, dispatch] = useReducer(reducer, initialState);
const isProductInBasket = (productId: number): boolean => {
return state.basket.some((product) => product.id === productId);
};
Code language: TypeScript (typescript)
My argument against this is that firstly, you don’t need the full state
object to figure out whether a product is in the basket.
And secondly, containing the isProductInBasket
function inside the ShoppingBasket
makes it really hard to test in isolation.
Extracting the function out, and only passing in the specific bit of state you need to get the outcome is preferable (again, imho):
<button
key={product.id}
onClick={() => dispatch({type: "ADD_TO_BASKET", product})}
disabled={isProductInBasket(product.id)}
>
// versus
<button
key={product.id}
onClick={() => dispatch({ type: "ADD_TO_BASKET", product })}
disabled={isProductInBasket(state.basket, product.id)}
>
Code language: HTML, XML (xml)
Why pass the entire state (the entire jungle) when you only need the basket array (the monkey)?
It just makes setting up your tests that much harder.
Wrap Up
That’s my take on why I prefer useReducer
over useState
in almost any component of real world complexity.
Of course your mileage my vary, these are only my opinions, and I’m always open to improving or changing, if a better way presents itself.
My experience has shown me that some developers tend to shy away from useReducer
in favour of useState
as they think the useReducer
approach is more complex. I hope I have gone some way to convince you otherwise.