I created a Context and hook to be able to see if areas of the application has been changed, and validate actions based on the current state in context.
Its called DirtyContext and the Implementation is used as follows:
const {isDirty, setDirtyContextFor} = useDirtyContext();
setDirtyContextFor(key) - Ads a key to a list, to mark something as dirty.
isDirty - Reports the current state of the application based on a memoized value that updates everytime something is removed or added to the list of keys.
I have a list of objects, that helps me create a set of dynamic elements on the page.
const thisFunctionWillLooseContextReference = (e) => {
e.preventDefault();
console.log('Context Value - IsDirty: ', isDirty)
};
const [buttons, setButtons] = useState(() => {
return [{onClick: thisFunctionWillLooseContextReference}]
});
This is tied together in the UI using the following:
const renderButtons = () => {
return buttons.map((btn, index) => (
<button onClick={btn.onClick}>Button-{index}</button>
));
}
Even if the context value isDirty is set to true, the function passed to the button, always just reports the initial value of isDirty.
Would appreciate any help on why this is happening, and how i can get the expected results (which is the correct/current value of isDirty)
Codepen - Have a look at the console when clicking the buttons: (Code and implementation details are reduced to the smallest reproducable state)
const { useState, useMemo } = React;
const DirtyContext = React.createContext();
const DirtyContextProvider = ({ children }) => {
const [dirtyList, setDirtyList] = useState(new Set());
const isDirty = useMemo(() => {
return dirtyList.size > 0;
}, [dirtyList]);
function setDirtyStateFor(componentName) {
const newDirtyList = new Set(dirtyList);
newDirtyList.add(componentName);
setDirtyList(newDirtyList);
}
return (<DirtyContext.Provider value={{
setDirtyStateFor,
isDirty,
}}>
{children}
</DirtyContext.Provider>);
};
const useDirtyContext = () => React.useContext(DirtyContext);
const MyDirtyLittleApp = () => {
const {isDirty, setDirtyStateFor} = useDirtyContext();
const [input, setValue] = useState("");
const thisFunctionWillLooseContextReference = (e) => {
e.preventDefault();
console.log('Context Value - IsDirty: ', isDirty)
};
const [buttons, setButtons] = useState(() => {
return [{onClick: thisFunctionWillLooseContextReference}]
});
function handleInput(event) {
setValue(event.target.value);
setDirtyStateFor('MyDirtyLittleApp');
}
function updateInput(event) {
event.preventDefault();
console.log('Am i dirty ?', isDirty)
}
const renderButtons = () => {
return buttons.map((btn, index) => (
<button class="button is-dark" data-reactstuff={isDirty} onClick={btn.onClick}>btn {index}</button>
));
}
return (
<React.Fragment>
<h1>{isDirty ? 'I`m Dirty': 'I`m Clean'}</h1>
<form className="form">
<div class="field">
<label for="name-1">Update DirtyContext</label>
<div class="control">
<input type="text" value={input} name="name-1" onChange={handleInput} class="input"/>
</div>
</div>
<div class="field">
<div class="control">
<button onClick={updateInput} class="button is-dark">Save</button>
{renderButtons()}
</div>
<control>
<h5>Check console for results when clicking on the buttons</h5>
</control>
</div>
</form>
</React.Fragment>
)
}
const App = () => {
return (
<DirtyContextProvider>
<div className="box">
<MyDirtyLittleApp />
</div>
</DirtyContextProvider>
)
}
ReactDOM.render(<App />,
document.getElementById("root"))
body {
height: 100vh;
margin: 0;
display: grid;
place-items: center;
}
.box {
width: 300px;
h1 {
font-size: 20px;
margin: 0 0 1rem 0;
}
h5 {
font-size: 12px;
}
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" type="text/css" />
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>