I have a question regarding "one time actions" in react ponents. Imagine for example I want to scroll some element to certain position, or reset the internal react state.
So far I've been doing this by using a bination of a boolean flag (e.g. doAction: true
) and an update action (e.g. setDoActionBackToFalse
), but this seems too plex. Does anyone have any nice solution to this?
Note: The action can actually happen multiple times during the lifetime of the ponent but each time it has to be specifically triggered and happen only once (not keep happening on every rerender). E.g. scroll to every newly added item in scrollpane.
I created small fiddle to make the problem more obvious: / This uses the boolean flag approach.
I have a question regarding "one time actions" in react ponents. Imagine for example I want to scroll some element to certain position, or reset the internal react state.
So far I've been doing this by using a bination of a boolean flag (e.g. doAction: true
) and an update action (e.g. setDoActionBackToFalse
), but this seems too plex. Does anyone have any nice solution to this?
Note: The action can actually happen multiple times during the lifetime of the ponent but each time it has to be specifically triggered and happen only once (not keep happening on every rerender). E.g. scroll to every newly added item in scrollpane.
I created small fiddle to make the problem more obvious: https://jsfiddle/martinkadlec/et74rkLk/1/ This uses the boolean flag approach.
Share Improve this question edited Mar 20, 2017 at 13:36 Martin Kadlec asked Mar 20, 2017 at 10:03 Martin KadlecMartin Kadlec 4,9852 gold badges22 silver badges36 bronze badges 3- constructor() ? – Pranav Singh Commented Mar 20, 2017 at 10:13
- After your ments and question update I'm not sure if I understand your problem. If you want to call an action only once when for example you add new item to the list you can just call your action inside method that adds this item...it will not be called on every re-render but only when the method that adds item is called. – Bartek Fryzowicz Commented Mar 20, 2017 at 12:27
- Well, the function that adds the new item is actually in model, but I need to react to it also in the react ponent (because thats where I have access to the actual dom elements). Now, the ponent gets updated with the new list of items in props, but I don't want to do some difficult pares in lifecycle methods to determine new item was added so instead I would prefer to just let the ponent somehow know that it needs to scroll to the item afterwards. – Martin Kadlec Commented Mar 20, 2017 at 12:33
3 Answers
Reset to default 3It has been some time since I asked this question and since then I found that as long as the "one time action" doesn't actually rerender the ponent, but instead just modifies some browser state (e.g. focus, scroll position, etc.) people generally tend to solve this by having a class method and calling it from the parent ponent using refs.
To illustrate on the focus example:
class Input extends React.Component {
inputElRef = React.createRef();
focus = () => this.inputElRef.current.focus();
render() {
return (
<input ref={this.inputElRef} />
);
}
}
class Parent extends React.Component {
inputRef = React.createRef();
render() {
return (
<div>
<button onClick={() => this.inputRef.current.focus()}>Focus input</button>
<Input ref={this.inputRef} />
</div>
);
}
}
I think that you can use ponentDidMount lifecycle hook. This hook is invoked only once immediately after a ponent is mounted and the DOM can be accessed in it.
You can also call your 'one time action' in ponent constructor
but it's called before ponent is mounted and before initial render so you can't access DOM there.
So you can initialize ponent state in a constructor
(according to React docs: constructor is the right place to initialize state) but you can't scroll some element to certain position in constructor
because you can't access ponent DOM elements in it.
Summing up: state initialization should be done in constructor
while 'one time actions' manipulating DOM should be done in ponentDidMount
.
Wrap your action handlers inside a higher order function which invokes them only once. Lodash has once
. Ramda has it too.
Updates for your scrolling scenario.... Scrolling is a side effect which must be initiated by the DOM API. You can write an HOC which wraps any ponent inside it -
function OnFocusExtender(Wrapped){
return class ExtendedFocus{
focus = _.once(elem => elem && elem.focus && elem.focus());
render(){
return <Wrapped ref={this.focus} {...this.props} />;
}
}
}
Then you can use it in your code like -
render(){
let FocusedComponent = FocusExtender(YourComponent);
return <FocusedComponent a={"blah"} b={blah} />
}
Updated for a generic side-effects approach:
The HOC:
function WelingParty(...party)=>(Wrapped)=>{
return class ExtendWele{
// Every host in the weling party greets
// the guest :)
wele = (ref) => party.forEach(host => host(ref));
render(){
return <Wrapped ref={this.wele} {...this.props} />;
}
}
}
Usage:
let hostFn = (fn)=>(ref)=> ref && (typeof ref[fn] == "function") && ref[fn](),
hosts = ["focus", "scrollIntoView"].map(hostFn);
render(){
let Component = WelingParty(...hosts)(YourComponent);
return <Component a={"blah"} b={blah} />
}