I am having a hard time understanding why the onClick event handler (which invokes 2 calls to a custom hook wrapper function) is not responding properly. I expect that everytime I click the button in the example would swap its border color from green to red based on a value that is being incremented. I understand the example is rudimentary and could easily be solved by conditioning the error prop on the value.value instead of sharing , but this is a simplified example of a more complex interaction, and I have boiled down the issue to a simple example for clarification. Any help would be appreciated. https://codesandbox.io/s/custom-hooks-with-closure-issue-2fc6g?file=/index.js
index.js
import useValueErrorPair from "./useValueErrorPair";
import styled from "styled-components";
import ReactDOM from "react-dom";
import React from "react";
const Button = styled.button`
background-color: black;
padding: 10px;
color: white;
${props =>
props.error ? "border: 3px solid #ff0000;" : "border: 3px solid #00ff00;"}
`;
const e = React.createElement;
const DemoComponent = () => {
const [value, setValue, setError] = useValueErrorPair(0, false);
console.log(value);
return (
<Button
error={value.error}
onClick={e => {
e.preventDefault();
setError((value.value + 1) % 2 === 1); // If number of clicks is odd => error.
setValue(value.value + 1); // Increment the state hook for value.
}}
>
Click Me For Problems!
</Button>
);
};
const domContainer = document.querySelector("#root");
ReactDOM.render(e(DemoComponent), domContainer);
export default DemoComponent;
useValueErrorPair.js
import { useState } from "react";
const useValueErrorPair = (initialValue, initialError) => {
const [v, setV] = useState({ value: initialValue, error: initialError });
const setValue = newValue => {
setV({ error: v.error, value: newValue });
};
const setError = newError => {
if (newError !== v.error) setV({ error: newError, value: v.value });
};
return [v, setValue, setError];
};
export default useValueErrorPair;
Snippet:
const { useState } = React;
const useValueErrorPair = (initialValue, initialError) => {
const [v, setV] = useState({ value: initialValue, error: initialError });
const setValue = newValue => {
setV({ error: v.error, value: newValue });
};
const setError = newError => {
if (newError !== v.error) setV({ error: newError, value: v.value });
};
return [v, setValue, setError];
};
const DemoComponent = () => {
const [value, setValue, setError] = useValueErrorPair(0, false);
console.log(value);
return (
<button type="button" className={value.error ? "error" : "okay"}
onClick={e => {
e.preventDefault();
setError((value.value + 1) % 2 === 1); // If number of clicks is odd => error.
setValue(value.value + 1); // Increment the state hook for value.
}}
>
Click Me For Problems!
</button>
);
};
const domContainer = document.querySelector("#root");
const e = React.createElement;
ReactDOM.render(e(DemoComponent), domContainer);
.error {
border: 1px solid red;
}
.okay {
border: 1px solid green;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>