React Hooks: accessing state across functions without changing event handler function references - javascript

In a class based React component I do something like this:
class SomeComponent extends React.Component{
onChange(ev){
this.setState({text: ev.currentValue.text});
}
transformText(){
return this.state.text.toUpperCase();
}
render(){
return (
<input type="text" onChange={this.onChange} value={this.transformText()} />
);
}
}
This is a bit of a contrived example to simplify my point. What I essentially want to do is maintain a constant reference to the onChange function. In the above example, when React re-renders my component, it will not re-render the input if the input value has not changed.
Important things to note here:
this.onChange is a constant reference to the same function.
this.onChange needs to be able to access the state setter (in this case this.setState)
Now if I were to rewrite this component using hooks:
function onChange(setText, ev) {
setText(ev.currentValue.text);
};
function transformText(text) {
return text.toUpperCase();
};
function SomeComponent(props) {
const [text, setText] = useState('');
return (
<input type="text" onChange={onChange} value={transformText()} />
);
}
The problem now is that I need to pass text to transformText and setText to onChange methods respectively. The possible solutions I can think of are:
Define the functions inside the component function, and use closures to pass the value along.
Inside the component function, bind the value to the methods and then use the bound methods.
Doing either of these will change the constant reference to the functions that I need to maintain in order to not have the input component re-render. How do I do this with hooks? Is it even possible?
Please note that this is a very simplified, contrived example. My actual use case is pretty complex, and I absolutely don't want to re-render components unnecessarily.
Edit:
This is not a duplicate of What useCallback do in React? because I'm trying to figure out how to achieve a similar effect to what used to be done in the class component way, and while useCallback provides a way of doing it, it's not ideal for maintainability concerns.

This is where you can build your own hook (Dan Abramov urged not to use the term "Custom Hooks" as it makes creating your own hook harder/more advanced than it is, which is just copy/paste your logic) extracting the text transformation logic
Simply "cut" the commented out code below from Mohamed's answer.
function SomeComponent(props) {
// const [text, setText] = React.useState("");
// const onChange = ev => {
// setText(ev.target.value);
// };
// function transformText(text) {
// return text.toUpperCase();
// }
const { onChange, text } = useTransformedText();
return (
<input type="text" onChange={React.useCallback(onChange)} value={text} />
);
}
And paste it into a new function (prefix with "use*" by convention).
Name the state & callback to return (either as an object or an array depending on your situation)
function useTransformedText(textTransformer = text => text.toUpperCase()) {
const [text, setText] = React.useState("");
const onChange = ev => {
setText(ev.target.value);
};
return { onChange, text: textTransformer(text) };
}
As the transformation logic can be passed (but uses UpperCase by default), you can use the shared logic using your own hook.
function UpperCaseInput(props) {
const { onChange, text } = useTransformedText();
return (
<input type="text" onChange={React.useCallback(onChange)} value={text} />
);
}
function LowerCaseInput(props) {
const { onChange, text } = useTransformedText(text => text.toLowerCase());
return (
<input type="text" onChange={React.useCallback(onChange)} value={text} />
);
}
You can use above components like following.
function App() {
return (
<div className="App">
To Upper case: <UpperCaseInput />
<br />
To Lower case: <LowerCaseInput />
</div>
);
}
Result would look like this.
You can run the working code here.

Define the callbacks inside the component function, and use closures to pass the value along. Then what you are looking for is useCallback hook to avoid unnecessary re-renders. (for this example, it's not very useful)
function transformText(text) {
return text.toUpperCase();
};
function SomeComponent(props) {
const [text, setText] = useState('');
const onChange = useCallback((ev) => {
setText(ev.target.value);
}, []);
return (
<input type="text" onChange={onChange} value={transformText(text)} />
);
}
Read more here

I know it's bad form to answer my own question but based on this reply, and this reply, it looks like I'll have to build my own custom hook to do this.
I've basically built a hook which binds a callback function with the given arguments and memoizes it. It only rebinds the callback if the given arguments change.
If anybody would find a need for a similar hook, I've open sourced it as a separate project. It's available on Github and NPM.

The case isn't specific to hooks, it would be the same for class component and setState in case transformText and onChange should be extracted from a class. There's no need for one-line functions to be extracted, so it can be assumed that real functions are complex enough to justify the extraction.
It's perfectly fine to have transform function that accepts a value as an argument.
As for event handler, it should have a reference to setState, this limits ways in which it can be used.
A common recipe is to use state updater function. In case it needs to accept additional value (e.g. event value), it should be higher-order function.
const transformText = text => text.toUpperCase();
const onChange = val => _prevState => ({ text: val });
function SomeComponent(props) {
const [text, setText] = useState('');
return (
<input type="text" onChange={e => setText(onChange(e.currentValue.text)} value={transformText(text)} />
);
}
This recipe doesn't look useful in this case because original onChange doesn't do much. This also means that the extraction wasn't justified.
A way that is specific to hooks is that setText can be passed as a callback, in contrast to this.setState. So onChange can be higher-order function:
const transformText = text => text.toUpperCase();
const onChange = setState => e => setState({ text: e.currentValue.text });
function SomeComponent(props) {
const [text, setText] = useState('');
return (
<input type="text" onChange={onChange(setText)} value={transformText(text)} />
);
}
If the intention is to reduce re-renders of children caused by changes in onChange prop, onChange should be memoized with useCallback or useMemo. This is possible since useState setter function doesn't change between component updates:
...
function SomeComponent(props) {
const [text, setText] = useState('');
const memoizedOnChange = useMemo(() => onChange(setText), []);
return (
<input type="text" onChange={memoizedOnChange} value={transformText(text)} />
);
}
The same thing can be achieved by not extracting onChange and using useCallback:
...
function SomeComponent(props) {
const [text, setText] = useState('');
const onChange = e => setText({ text: e.currentValue.text });
const memoizedOnChange = useCallback(onChange, []);
return (
<input type="text" onChange={memoizedOnChange} value={transformText(text)} />
);
}

Related

Parent's methods in array dependencies in useCallback

Let say we have parent and children components like this:
const {useState, useCallback} = React;
const ComponentB = (props) => {
const [text, setText] = useState('')
const { onClick } = props
const handleChange = useCallback((event) => {
setText(event.target.value)
}, [text])
const handleClick = useCallback(() => {
onClick(text)
}, [onClick, text]) // Should I to take into account 'onClick' props?
return (
<div>
<input type="text" onChange={ handleChange } />
<button type="button" onClick={ handleClick }>Save</button>
</div>
)
}
const ComponentA = () => {
const [stateA, setStateA] = useState('')
const handleSetStateA = useCallback((state) => {
setStateA(state)
}, [stateA])
return (
<div>
<ComponentB onClick={ handleSetStateA } />
{ stateA && `${ stateA } saved!` }
</div>
)
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<ComponentA />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
React documentation says that:
every value referenced inside the callback should also appear in the dependencies array
And I'm wondering if I need to put onClick method to array dependencies in useCallback? And if so, why I should do that?
And I'm wondering if I need to put onClick method to array dependencies in useCallback?
Yes.
And if so, why I should do that?
Because it's possible that your component will get re-rendered with a new and different function for the onClick prop that behaves differently from the old one. Without saying it's a dependency, you'll continue using the old value.
In fact, in your given code, it's not just possible but definite: you create a new handleSetStateA function every time stateA changes.
That said, in ComponentA:
There's no reason to have stateA as a dependency in your useCallback creating handleSetStateA; handleSetStateA never uses stateA. (It uses the state setter function for it, but that's not the same thing.)
There's not really any reason for handleSetStateA at all; just pass setStateA directly as onClick. But I'm assuming you do more than just setting the state in that function and just left things out for the purposes of the question.
(Similarly, in ComponentB there's no reason for text to be a dependency on the useCallback for handleChange; handleChange doesn't use text.)
But even if you change ComponentA to pass setStateA directly (or at least provide a stable function), ComponentB shouldn't rely on onClick being unchanging between renders, so you'd use onClick in your useCallback dependency list regardless.
Finally: There's not much point in using useCallback with functions you're passing to unmemoized components. For it to be useful, the component you're providing the callback function to should be memoized (for instance, via React.memo or, for a class component, via shouldComponentUpdate). See my answer here for details on that.
Here's an updated version of your snippet using React.memo and only the necessary dependencies; I've left handleSetStateA in (I added a console.log so it isn't just a wrapper):
const { useState, useCallback } = React;
const ComponentB = React.memo(({ onClick }) => {
const [text, setText] = useState("");
const handleChange = useCallback((event) => {
setText(event.target.value);
}, []);
const handleClick = useCallback(() => {
console.log(`Handling click when text = "${text}"`);
onClick(text);
}, [onClick, text]);
return (
<div>
<input type="text" onChange={handleChange} />
<button type="button" onClick={handleClick}>
Save
</button>
</div>
);
});
const ComponentA = () => {
const [stateA, setStateA] = useState("");
const handleSetStateA = useCallback((state) => {
console.log(`Setting stateA to "${state}"`);
setStateA(state);
}, []);
return (
<div>
<ComponentB onClick={handleSetStateA} />
{stateA && `${stateA} saved!`}
</div>
);
};
ReactDOM.createRoot(document.getElementById("root")).render(<ComponentA />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

Is there a performance/usability benefit to using useCallback for InputElement Events?

When not passing a callback to a child and just using it on the present component, Is there a benefit to wraping the callback in a useCallback?
This:
const Foo = (
const [count, setCount] = useState(500);
const onChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setCount(Number(e.target.value));
}, [setCount]);
return (
<>
<div>
Delay: <input value={count} onChange={onChange} type="number" />
</div>
</>
);
Verse This:
const Foo = (
const [count, setCount] = useState(500);
const onChange =(e: React.ChangeEvent<HTMLInputElement>) => {
setCount(Number(e.target.value));
};
return (
<>
<div>
Delay: <input value={count} onChange={onChange} type="number" />
</div>
</>
);
Codesandbox: https://codesandbox.io/s/loving-stonebraker-48xhs?file=/src/Counter.tsx
I see no benefit there when used internally since each state update (setCount(Number(e.target.value));) necessarily rerenders the component anyway and all the callback is doing is enqueueing a state update.
There may be a small (very negligible) improvement in memory usage if using a single declared, memoized function for the life of the component.
If the callback was used in an useEffect hook it could be provided as a stable reference and be removed from dependency arrays, but typically here you'd just move the function into the effect callback. This doesn't fit the use case you are asking about, but it's a valid use case that isn't passing callbacks down to children.

How can I acess value from textfield in another module in reactJS

I want to have access to the value of a textField in another module in reactjs. The module that wants to have access does not import the whole textfield but only needs access to the value. What is the best way to access the value variable in another module in reactjs?
Here is my functional textField component:
export default function textField(props) {
const [value, setValue] = React.useState("");
const handleChange = (event) => {
setValue(value);
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
You can send onTextFieldChange function as props whenever textField's value changes you can pass a value to onTextFieldChange function and you can use it in the parent component.
There is an alternate way, Redux
You should try to use redux for the shared state between components which are either not related directly(i.e. sibling components or have a lengthy hierarchy). For small applications, redux is overkilled so should be avoided.
The most likely option that comes to mind here is the concept of lifting state, in which the nearest ancestor component has some means by which it also keeps track of the state, and the passes it into the sibling that needs to track it. You could make this an optional feature of your module by allowing a onChangeCallback prop that is called on each change; this prop could then be passed a setSharedState hook that would set the state on the ancestor:
const ParentComponent = () => {
const [textfieldVal, setTextfieldVal] = useState();
return (
<TextField onChangeCallback={setTextFieldVal} />
);
}
And you update your module to something like:
export default function textField(props) {
const [value, setValue] = React.useState("");
const {onChangeCallback} = props;
const handleChange = (event) => {
setValue(value);
if (typeof onChangeCallback === 'function') {
onChangeCallback(event); // or value, I'm not sure which you should be using here, this might be incorrect
}
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
This is just a rough example. Other options for passing around state freely would be using Redux or the Context API, but the former might be overkill for this one case and the latter probably not a great fit for a specific, single-use datapoint.
there are may option but the proper option in pass as props
const modle1= () => {
const [textfieldVal, setTextfieldVal] = useState();
return (
<TextField onChangeCallback={setTextFieldVal} />
);
}
export default function textField(props) {
const [value, setValue] = React.useState("");
const {onChangeCallback} = props;
const handleChange = (event) => {
setValue(value);
if (typeof onChangeCallback === 'function') {
onChangeCallback(event); // or value, I'm not sure which you should be using here, this might be incorrect
}
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
or you can use create context and use context and provide the parent to provide and the child as a consumer but the best option pass as a prop
I would recommend you use the parent-child nature of react in order to handle this. Since I'm not sure the relationship between the other module and this module, I'll provide a rough skeleton.
In a parent component:
export default function ParentComponent(){
const [status, updateStatus] = useState("")
return(
<TextView updateParent={updateStatus}>
</TextView>
)
}
Then in your child component:
const handleChange = (event) => {
setValue(value);
props.updateParent(value);
};
If they are siblings, I would use a parent component and then pass the state down to the sibling. Otherwise, as appropriate, use this parent child relationship to pass and update state.
HTH

Don't understand how to update React hook

I've tried to ask this ungooglable to me question dozens of times. I've made almost the simpliest example possible to ask this question now.
I change the value of the hook in the handleChange method. But then console.log always shows previous value, not new one. Why is that?
I need to change the value of the hook and then instead of doing console.log use it to do something else. But I can't because the hook always has not what I just tried to put into it.
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState(options[0]);
const handleChange = val => {
setValue(val);
console.log(value);
};
return (
<div>
<div>{value}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
handleChange(newValue);
}}
options={options}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
</div>
);
}
You can try it here.
https://codesandbox.io/s/awesome-lumiere-y2dww?file=/src/App.js
I believe the problem is that you are logging the value in the handleChange function. Console logging the value outside of the function logs the correct value. Link: https://codesandbox.io/s/async-fast-6y71b
Hooks do not instantly update the value you want to update, as you might have expected with classes (though that wasn't guaranteed either)
State hook, when calling setValue will trigger a re-render. In that new render, the state will have the new value as you expected. That's why your console.log sees the old value.
Think of it as in each render, the state values are just local variables of that component function call. And think as the result of your render as a result of your state + props in that render call. Whenever any of those two changes (the props from your parent component; the state, from your setXXX function), a new render is triggered.
If you move out the console.log outside of the callback handler (that is, in the body of your rendered), there you will see in the render that happens after your interaction that the state is logged correctly.
In that sense, in your callbacks events from interactions, you just should worry about updating your state properly, and the next render will take care to, given the new props/state, re-render the result
The value doesn't "change" synchronously - it's even declared with a const, so even the concept of it changing inside the same scope doesn't make sense.
When changing state with hooks, the new value is seen when the component is rerendered. So, to log and do stuff with the "new value", examine it in the main body of the function:
const ControllableStates = () => {
const [value, setValue] = React.useState(options[0]);
const handleChange = val => {
setValue(val);
};
// ADD LOG HERE:
console.log('New or updated value:', value);
return (
<div>
<div>{value}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
handleChange(newValue);
}}
options={options}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
</div>
);
}
You're printing out the old value in handleChange, not the new val.
i.e.
const handleChange = val => {
setValue(val);
console.log(value);
};
Should be:
const handleChange = val => {
setValue(val);
console.log(val);
};
Actually, lets get a little back and see the logic behind this scenario.
You should use the "handleChange" function ONLY to update the state hook, and let something else do the logic depends on that state hook value, which is mostly accomplished using "useEffect" hook.
You could refactor your code to look like this:
const handleChange = val => {
setValue(val);
};
React.useEffect(() => {
console.log(value);
// do your logic here
}, [value])
So I think that the main problem is that you're not understanding how React
deals with components and states.
So, I'll vastly simplify what React does.
React renders a new component and remembers it's state, it's inputs (aka
props) and it's the state and inputs of the children.
If at any given point an input changes or a state changes, React will render
the component again by calling the component function.
Consider this:
function SomeComponent(text) {
return (<div>The <i>text</i> prop has the value {text}</div>)
}
Let's say the initial prop value is "abc", React will call SomeComponent("abc"), then the function returns
<div>The <i>text</i> prop has the value abc</div> and React will render that.
If the prop text does not change, then React does nothing anymore.
Now the parent component changes the prop to "def", now React will call
SomeComponent("def") and it will return
<div>The <i>text</i> prop has the value def</div>, this is different from
last call, so React will update the DOM to reflect the change.
Now let's introduce state
function SomeComponent() {
const [name, setName] = React.useState("John")
function doSomething()
{
alert("The name is " + name)
}
return (
<p>Current name: {name}</p>
<button onClick={() => setName("Mary")}>Set name to Mary</button>
<button onClick={() => setName("James")}>Set name to James</button>
<button onClick={() => doSomething()}>Show current name</button>
)
}
So here React will call SomeComponent() and render the name John and the 3
button. Note that the value of the name variable does not change during the
current execution, because it's declared as const. This variable only reflects
the latest value of the state.
When you press the first button, setName() is executed. React will internally store
the new value for the state and because of the change of state, it will render
the component again, so SomeComponent() will be called once again. Now the variable name will
reflect again the latest value of the state (that's what useStatedoes), so in this case Mary. React
will realize that the DOM has to be updated and it prints the name Mary.
If you press the third button, it will call doSomething() which will print the
latest value of the name variable because every time React calls
SomeComponent(), the doSomething() function is created again with the latest
value of name. So once you've called setName(), you don't need to do
anything special to get the new value. React will take care of calling the
component function again.
So when you don't use class components but function components, you have to think
differently: the function gets called all the time by React and at any single
execution it reflects the latest state at that particular point in time. So when you
call the setter of a useState hook, you know that the component function will
be called again and useState will return the new value.
I recommend that you read this article, also read again Components and
Props from the React documentation.
So how should you do proceed? Well, like this:
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState(options[0]);
const handleChange = val => {
setValue(val);
console.log(value);
};
const handleClick = () => {
// DOING SOMETHING WITH value
alter(`Now I'm going to do send ${value}`);
}
return (
<div>
<div>{value}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
handleChange(newValue);
}}
options={options}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
<button type="button" onClick={handleClick}>Send selected option</button>
</div>
);
}
See CodeSandbox.

React Hooks change single value of object with multiple keys without repetition

I'm trying to find a neater way to handle this pattern I keep coming across with react when handling changes for form fields.
For each element of my form object that I handle a change in value for I find myself replicating this pattern quite a bit with the setter function of useState(). I've tried a couple of things like creating shallow copies of the formState and mutating that but the only way I can really get things to work is with the bellow pattern which feels a little repetitive.
const handleTitle = evt => {
props.setFormState({
title: evt.target.value,
bio: props.formState.bio,
formExpertise: props.formState.formExpertise,
formExpertiseYears: props.formState.formExpertiseYears
});
};
If you want to include this.props.formState you can spread the object into the new state. Further, you can use the input’s name as the state key so you don’t have to rewrite this for every input:
props.setFormState({
...this.props.formState, // copy props.formState in
[evt.target.name]: evt.target.value // use input name as state key
});
Suggestion:
You might consider moving the state merging up into the parent component:
// parent component
const [formState, setFormState] = React.useState({});
const onFieldChange = (field, value) => {
setFormState({
...formState,
[field]: value
});
}
return (
<MyFormComponent
formState={formState}
onFieldChange={onFieldChange}
/>
);
Each input can then invoke onFieldChange with the field name and value without concerning itself with the rest of the state:
function MyFormComponent ({onFieldChange}) {
const handler = ({target: {name, value}}) => onFieldChange(name, value);
return (
<div>
<input name="title" value={formState.title} onChange={handler} />
<input name="bio" value={formState.bio} onChange={handler} />
<input name="expertise" value={formState.expertise} onChange={handler} />
</div>
);
}

Categories