Equivalent to componentDidUpdate using React hooks
tldr; How do I simulate componentDidUpdate
or otherwise use key
with an array to force my component to be reset?
I'm implementing a component which displays a timer and executes a callback when it reaches zero. The intent is for the callback to update a list of objects. The latter component is made of the new React hooks useState
and useEffect
.
The state
contains a reference to the time at which the timer was started, and the time remaining. The effect
sets an interval called every second to update the time remaining, and to check whether the callback should be called.
The component is not meant to reschedule a timer, or keep the interval going when it reaches zero, it's supposed to execute the callback and idle. In order for the timer to refresh, I was hoping to pass an array to key
which would cause the component's state to be reset, and thus the timer would restart. Unfortunately key
must be used with a string, and therefore whether or not my array's reference has changed produces no effect.
I also tried to push changes to the props by passing the array that I was concerned about, but the state was maintained and thus the interval was not reset.
What would be the preferred method to observe shallow changes in an array in order to force a state to be updated solely using the new hooks API?
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
function getTimeRemaining(startedAt, delay) {
const now = new Date();
const end = new Date(startedAt.getTime() + delay);
return Math.max(0, end.getTime() - now.getTime());
}
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
useEffect(() => {
if (timeRemaining <= 0) {
// The component is set to idle, we do not set the interval.
return;
}
// Set the interval to refresh the component every second.
const i = setInterval(() => {
const nowRemaining = getTimeRemaining(startedAt, props.delay);
setTimeRemaining(nowRemaining);
if (nowRemaining <= 0) {
props.callback();
clearInterval(i);
}
}, 1000);
return () => {
clearInterval(i);
};
});
let message = `Refreshing in ${Math.ceil(timeRemaining / 1000)}s.`;
if (timeRemaining <= 0) {
message = 'Refreshing now...';
}
return <div>{message}</div>;
}
RefresherTimer.propTypes = {
callback: PropTypes.func.isRequired,
delay: PropTypes.number
};
RefresherTimer.defaultProps = {
delay: 2000
};
export default RefresherTimer;
Attempted to use with key
:
<RefresherTimer delay={20000} callback={props.updateListOfObjects} key={listOfObjects} />
Attempted to use with a props change:
<RefresherTimer delay={20000} callback={props.updateListOfObjects} somethingThatChanges={listOfObjects} />
listOfObjects
refers to an array of objects, where the objects themselves won't necessarily change, so the array should be compared with !==
. Typically, the value will be coming from Redux
, where the action updateListOfObjects
causes the array to be reinitialised like so: newListOfObjects = [...listOfObjects]
.
javascript reactjs react-hooks
add a comment |
tldr; How do I simulate componentDidUpdate
or otherwise use key
with an array to force my component to be reset?
I'm implementing a component which displays a timer and executes a callback when it reaches zero. The intent is for the callback to update a list of objects. The latter component is made of the new React hooks useState
and useEffect
.
The state
contains a reference to the time at which the timer was started, and the time remaining. The effect
sets an interval called every second to update the time remaining, and to check whether the callback should be called.
The component is not meant to reschedule a timer, or keep the interval going when it reaches zero, it's supposed to execute the callback and idle. In order for the timer to refresh, I was hoping to pass an array to key
which would cause the component's state to be reset, and thus the timer would restart. Unfortunately key
must be used with a string, and therefore whether or not my array's reference has changed produces no effect.
I also tried to push changes to the props by passing the array that I was concerned about, but the state was maintained and thus the interval was not reset.
What would be the preferred method to observe shallow changes in an array in order to force a state to be updated solely using the new hooks API?
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
function getTimeRemaining(startedAt, delay) {
const now = new Date();
const end = new Date(startedAt.getTime() + delay);
return Math.max(0, end.getTime() - now.getTime());
}
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
useEffect(() => {
if (timeRemaining <= 0) {
// The component is set to idle, we do not set the interval.
return;
}
// Set the interval to refresh the component every second.
const i = setInterval(() => {
const nowRemaining = getTimeRemaining(startedAt, props.delay);
setTimeRemaining(nowRemaining);
if (nowRemaining <= 0) {
props.callback();
clearInterval(i);
}
}, 1000);
return () => {
clearInterval(i);
};
});
let message = `Refreshing in ${Math.ceil(timeRemaining / 1000)}s.`;
if (timeRemaining <= 0) {
message = 'Refreshing now...';
}
return <div>{message}</div>;
}
RefresherTimer.propTypes = {
callback: PropTypes.func.isRequired,
delay: PropTypes.number
};
RefresherTimer.defaultProps = {
delay: 2000
};
export default RefresherTimer;
Attempted to use with key
:
<RefresherTimer delay={20000} callback={props.updateListOfObjects} key={listOfObjects} />
Attempted to use with a props change:
<RefresherTimer delay={20000} callback={props.updateListOfObjects} somethingThatChanges={listOfObjects} />
listOfObjects
refers to an array of objects, where the objects themselves won't necessarily change, so the array should be compared with !==
. Typically, the value will be coming from Redux
, where the action updateListOfObjects
causes the array to be reinitialised like so: newListOfObjects = [...listOfObjects]
.
javascript reactjs react-hooks
There's nocomponentDidReceiveProps
lifecycle method. Did you meancomponentWillReceiveProps
?
– Yangshun Tay
Nov 12 at 7:56
Whoops, I did. In fact, I'm going to edit and refer tocomponentDidUpdate
as usingcomponentWillReceiveProps
is discouraged.
– FMCorz
Nov 12 at 8:24
Please, provide stackoverflow.com/help/mcve . What exactly is ListOfObjects?
– estus
Nov 12 at 8:26
I added some information regarding that array, however it doesn't really matter what this variable represents, so long as it's understood that it should be shallow-compared using!==
.
– FMCorz
Nov 12 at 8:34
Not exactly. I'll provide an answer shortly.
– estus
Nov 12 at 8:35
add a comment |
tldr; How do I simulate componentDidUpdate
or otherwise use key
with an array to force my component to be reset?
I'm implementing a component which displays a timer and executes a callback when it reaches zero. The intent is for the callback to update a list of objects. The latter component is made of the new React hooks useState
and useEffect
.
The state
contains a reference to the time at which the timer was started, and the time remaining. The effect
sets an interval called every second to update the time remaining, and to check whether the callback should be called.
The component is not meant to reschedule a timer, or keep the interval going when it reaches zero, it's supposed to execute the callback and idle. In order for the timer to refresh, I was hoping to pass an array to key
which would cause the component's state to be reset, and thus the timer would restart. Unfortunately key
must be used with a string, and therefore whether or not my array's reference has changed produces no effect.
I also tried to push changes to the props by passing the array that I was concerned about, but the state was maintained and thus the interval was not reset.
What would be the preferred method to observe shallow changes in an array in order to force a state to be updated solely using the new hooks API?
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
function getTimeRemaining(startedAt, delay) {
const now = new Date();
const end = new Date(startedAt.getTime() + delay);
return Math.max(0, end.getTime() - now.getTime());
}
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
useEffect(() => {
if (timeRemaining <= 0) {
// The component is set to idle, we do not set the interval.
return;
}
// Set the interval to refresh the component every second.
const i = setInterval(() => {
const nowRemaining = getTimeRemaining(startedAt, props.delay);
setTimeRemaining(nowRemaining);
if (nowRemaining <= 0) {
props.callback();
clearInterval(i);
}
}, 1000);
return () => {
clearInterval(i);
};
});
let message = `Refreshing in ${Math.ceil(timeRemaining / 1000)}s.`;
if (timeRemaining <= 0) {
message = 'Refreshing now...';
}
return <div>{message}</div>;
}
RefresherTimer.propTypes = {
callback: PropTypes.func.isRequired,
delay: PropTypes.number
};
RefresherTimer.defaultProps = {
delay: 2000
};
export default RefresherTimer;
Attempted to use with key
:
<RefresherTimer delay={20000} callback={props.updateListOfObjects} key={listOfObjects} />
Attempted to use with a props change:
<RefresherTimer delay={20000} callback={props.updateListOfObjects} somethingThatChanges={listOfObjects} />
listOfObjects
refers to an array of objects, where the objects themselves won't necessarily change, so the array should be compared with !==
. Typically, the value will be coming from Redux
, where the action updateListOfObjects
causes the array to be reinitialised like so: newListOfObjects = [...listOfObjects]
.
javascript reactjs react-hooks
tldr; How do I simulate componentDidUpdate
or otherwise use key
with an array to force my component to be reset?
I'm implementing a component which displays a timer and executes a callback when it reaches zero. The intent is for the callback to update a list of objects. The latter component is made of the new React hooks useState
and useEffect
.
The state
contains a reference to the time at which the timer was started, and the time remaining. The effect
sets an interval called every second to update the time remaining, and to check whether the callback should be called.
The component is not meant to reschedule a timer, or keep the interval going when it reaches zero, it's supposed to execute the callback and idle. In order for the timer to refresh, I was hoping to pass an array to key
which would cause the component's state to be reset, and thus the timer would restart. Unfortunately key
must be used with a string, and therefore whether or not my array's reference has changed produces no effect.
I also tried to push changes to the props by passing the array that I was concerned about, but the state was maintained and thus the interval was not reset.
What would be the preferred method to observe shallow changes in an array in order to force a state to be updated solely using the new hooks API?
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
function getTimeRemaining(startedAt, delay) {
const now = new Date();
const end = new Date(startedAt.getTime() + delay);
return Math.max(0, end.getTime() - now.getTime());
}
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
useEffect(() => {
if (timeRemaining <= 0) {
// The component is set to idle, we do not set the interval.
return;
}
// Set the interval to refresh the component every second.
const i = setInterval(() => {
const nowRemaining = getTimeRemaining(startedAt, props.delay);
setTimeRemaining(nowRemaining);
if (nowRemaining <= 0) {
props.callback();
clearInterval(i);
}
}, 1000);
return () => {
clearInterval(i);
};
});
let message = `Refreshing in ${Math.ceil(timeRemaining / 1000)}s.`;
if (timeRemaining <= 0) {
message = 'Refreshing now...';
}
return <div>{message}</div>;
}
RefresherTimer.propTypes = {
callback: PropTypes.func.isRequired,
delay: PropTypes.number
};
RefresherTimer.defaultProps = {
delay: 2000
};
export default RefresherTimer;
Attempted to use with key
:
<RefresherTimer delay={20000} callback={props.updateListOfObjects} key={listOfObjects} />
Attempted to use with a props change:
<RefresherTimer delay={20000} callback={props.updateListOfObjects} somethingThatChanges={listOfObjects} />
listOfObjects
refers to an array of objects, where the objects themselves won't necessarily change, so the array should be compared with !==
. Typically, the value will be coming from Redux
, where the action updateListOfObjects
causes the array to be reinitialised like so: newListOfObjects = [...listOfObjects]
.
javascript reactjs react-hooks
javascript reactjs react-hooks
edited Nov 12 at 8:32
asked Nov 12 at 4:24
FMCorz
867610
867610
There's nocomponentDidReceiveProps
lifecycle method. Did you meancomponentWillReceiveProps
?
– Yangshun Tay
Nov 12 at 7:56
Whoops, I did. In fact, I'm going to edit and refer tocomponentDidUpdate
as usingcomponentWillReceiveProps
is discouraged.
– FMCorz
Nov 12 at 8:24
Please, provide stackoverflow.com/help/mcve . What exactly is ListOfObjects?
– estus
Nov 12 at 8:26
I added some information regarding that array, however it doesn't really matter what this variable represents, so long as it's understood that it should be shallow-compared using!==
.
– FMCorz
Nov 12 at 8:34
Not exactly. I'll provide an answer shortly.
– estus
Nov 12 at 8:35
add a comment |
There's nocomponentDidReceiveProps
lifecycle method. Did you meancomponentWillReceiveProps
?
– Yangshun Tay
Nov 12 at 7:56
Whoops, I did. In fact, I'm going to edit and refer tocomponentDidUpdate
as usingcomponentWillReceiveProps
is discouraged.
– FMCorz
Nov 12 at 8:24
Please, provide stackoverflow.com/help/mcve . What exactly is ListOfObjects?
– estus
Nov 12 at 8:26
I added some information regarding that array, however it doesn't really matter what this variable represents, so long as it's understood that it should be shallow-compared using!==
.
– FMCorz
Nov 12 at 8:34
Not exactly. I'll provide an answer shortly.
– estus
Nov 12 at 8:35
There's no
componentDidReceiveProps
lifecycle method. Did you mean componentWillReceiveProps
?– Yangshun Tay
Nov 12 at 7:56
There's no
componentDidReceiveProps
lifecycle method. Did you mean componentWillReceiveProps
?– Yangshun Tay
Nov 12 at 7:56
Whoops, I did. In fact, I'm going to edit and refer to
componentDidUpdate
as using componentWillReceiveProps
is discouraged.– FMCorz
Nov 12 at 8:24
Whoops, I did. In fact, I'm going to edit and refer to
componentDidUpdate
as using componentWillReceiveProps
is discouraged.– FMCorz
Nov 12 at 8:24
Please, provide stackoverflow.com/help/mcve . What exactly is ListOfObjects?
– estus
Nov 12 at 8:26
Please, provide stackoverflow.com/help/mcve . What exactly is ListOfObjects?
– estus
Nov 12 at 8:26
I added some information regarding that array, however it doesn't really matter what this variable represents, so long as it's understood that it should be shallow-compared using
!==
.– FMCorz
Nov 12 at 8:34
I added some information regarding that array, however it doesn't really matter what this variable represents, so long as it's understood that it should be shallow-compared using
!==
.– FMCorz
Nov 12 at 8:34
Not exactly. I'll provide an answer shortly.
– estus
Nov 12 at 8:35
Not exactly. I'll provide an answer shortly.
– estus
Nov 12 at 8:35
add a comment |
3 Answers
3
active
oldest
votes
A way to remount a component is to provide new key
property. It's not necessarily a string but it will be coerced to a string internally, so if listOfObjects
is a string, it's expected that key
is compared internally with listOfObjects.toString()
.
Any random key can be used, e.g. uuid
or Math.random()
. Shallow comparison of listOfObjects
can be performed in parent component to provide new key. useMemo
hook can be used in parent state to conditionally update remount key, and listOfObjects
can be used as a list of parameters that need to be memoized. Here's an example:
const remountKey = useMemo(() => Math.random(), listOfObjects);
return (
<div>
<RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
</div>
);
As an alternative to remount key, child component could be able to reset own state and expose a callback to trigger a reset.
Doing shallow comparison of listOfObjects
inside child component would be an antipattern because this requires it to be aware of parent component implementation.
Creating a parent component to do the comparison is only pushing my problem outside of my component, how do I achieve the comparison using hooks? Note that just using a random key means that any re-render of the parent component will cause the timer to be lost, this is not an option. AlsolistOfObjects
is an array of objects, so coercing it to string won't work:[object Object],[object Object],[object Object]"
. Lastly, as you said it yourself, the 3rd option is an antipattern.
– FMCorz
Nov 12 at 9:25
Yes, coercinglistOfObjects
won't work, I mentioned this to explain why current attempt doesn't work. Parent component is the component where you use<RefresherTimer>
, it already exists. using a random key means that any re-render of the parent component will cause the timer to be lost - it doesn't mean that. It basically should bekey={this.state.remountKey}
whereremountKey
is something likeshallowEqual(preState.listOfObjects, newState.listOfObjects) && prevState.remountKey || Math.random()
. If you use Redux, remountKey should likely be calculated there.
– estus
Nov 12 at 9:36
If you want to use hooks, useMemo hook can be likely be used in parent component to calculate remountKey. I provided an example that shows the idea. Using an equivalent to componentDidUpdate in child component would be a mistake because this requires it to be aware oflistOfObjects
while it shouldn't.
– estus
Nov 12 at 10:04
add a comment |
In short, you want to reset your timer when the reference of the array changes, right ?
If so, you will need to use some diffing mechanism, a pure hooks based solution would take advantage of the second parameter of useEffect
, like so:
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
//reset part, lets just set startedAt to now
useEffect(() => setStartedAt(new Date()),
//important part
[props.listOfObjects] // <= means: run this effect only if any variable
// in that array is different from the last run
)
useEffect(() => {
// everything with intervals, and the render
}
More information about this behaviour here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
add a comment |
The withRef
creates an "instance variable" in functional component. It acts a flag to indicate whether it is in mount or update phase without updating state.
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
} else {
// do componentDidUpate logic
}
});
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53255951%2fequivalent-to-componentdidupdate-using-react-hooks%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
A way to remount a component is to provide new key
property. It's not necessarily a string but it will be coerced to a string internally, so if listOfObjects
is a string, it's expected that key
is compared internally with listOfObjects.toString()
.
Any random key can be used, e.g. uuid
or Math.random()
. Shallow comparison of listOfObjects
can be performed in parent component to provide new key. useMemo
hook can be used in parent state to conditionally update remount key, and listOfObjects
can be used as a list of parameters that need to be memoized. Here's an example:
const remountKey = useMemo(() => Math.random(), listOfObjects);
return (
<div>
<RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
</div>
);
As an alternative to remount key, child component could be able to reset own state and expose a callback to trigger a reset.
Doing shallow comparison of listOfObjects
inside child component would be an antipattern because this requires it to be aware of parent component implementation.
Creating a parent component to do the comparison is only pushing my problem outside of my component, how do I achieve the comparison using hooks? Note that just using a random key means that any re-render of the parent component will cause the timer to be lost, this is not an option. AlsolistOfObjects
is an array of objects, so coercing it to string won't work:[object Object],[object Object],[object Object]"
. Lastly, as you said it yourself, the 3rd option is an antipattern.
– FMCorz
Nov 12 at 9:25
Yes, coercinglistOfObjects
won't work, I mentioned this to explain why current attempt doesn't work. Parent component is the component where you use<RefresherTimer>
, it already exists. using a random key means that any re-render of the parent component will cause the timer to be lost - it doesn't mean that. It basically should bekey={this.state.remountKey}
whereremountKey
is something likeshallowEqual(preState.listOfObjects, newState.listOfObjects) && prevState.remountKey || Math.random()
. If you use Redux, remountKey should likely be calculated there.
– estus
Nov 12 at 9:36
If you want to use hooks, useMemo hook can be likely be used in parent component to calculate remountKey. I provided an example that shows the idea. Using an equivalent to componentDidUpdate in child component would be a mistake because this requires it to be aware oflistOfObjects
while it shouldn't.
– estus
Nov 12 at 10:04
add a comment |
A way to remount a component is to provide new key
property. It's not necessarily a string but it will be coerced to a string internally, so if listOfObjects
is a string, it's expected that key
is compared internally with listOfObjects.toString()
.
Any random key can be used, e.g. uuid
or Math.random()
. Shallow comparison of listOfObjects
can be performed in parent component to provide new key. useMemo
hook can be used in parent state to conditionally update remount key, and listOfObjects
can be used as a list of parameters that need to be memoized. Here's an example:
const remountKey = useMemo(() => Math.random(), listOfObjects);
return (
<div>
<RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
</div>
);
As an alternative to remount key, child component could be able to reset own state and expose a callback to trigger a reset.
Doing shallow comparison of listOfObjects
inside child component would be an antipattern because this requires it to be aware of parent component implementation.
Creating a parent component to do the comparison is only pushing my problem outside of my component, how do I achieve the comparison using hooks? Note that just using a random key means that any re-render of the parent component will cause the timer to be lost, this is not an option. AlsolistOfObjects
is an array of objects, so coercing it to string won't work:[object Object],[object Object],[object Object]"
. Lastly, as you said it yourself, the 3rd option is an antipattern.
– FMCorz
Nov 12 at 9:25
Yes, coercinglistOfObjects
won't work, I mentioned this to explain why current attempt doesn't work. Parent component is the component where you use<RefresherTimer>
, it already exists. using a random key means that any re-render of the parent component will cause the timer to be lost - it doesn't mean that. It basically should bekey={this.state.remountKey}
whereremountKey
is something likeshallowEqual(preState.listOfObjects, newState.listOfObjects) && prevState.remountKey || Math.random()
. If you use Redux, remountKey should likely be calculated there.
– estus
Nov 12 at 9:36
If you want to use hooks, useMemo hook can be likely be used in parent component to calculate remountKey. I provided an example that shows the idea. Using an equivalent to componentDidUpdate in child component would be a mistake because this requires it to be aware oflistOfObjects
while it shouldn't.
– estus
Nov 12 at 10:04
add a comment |
A way to remount a component is to provide new key
property. It's not necessarily a string but it will be coerced to a string internally, so if listOfObjects
is a string, it's expected that key
is compared internally with listOfObjects.toString()
.
Any random key can be used, e.g. uuid
or Math.random()
. Shallow comparison of listOfObjects
can be performed in parent component to provide new key. useMemo
hook can be used in parent state to conditionally update remount key, and listOfObjects
can be used as a list of parameters that need to be memoized. Here's an example:
const remountKey = useMemo(() => Math.random(), listOfObjects);
return (
<div>
<RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
</div>
);
As an alternative to remount key, child component could be able to reset own state and expose a callback to trigger a reset.
Doing shallow comparison of listOfObjects
inside child component would be an antipattern because this requires it to be aware of parent component implementation.
A way to remount a component is to provide new key
property. It's not necessarily a string but it will be coerced to a string internally, so if listOfObjects
is a string, it's expected that key
is compared internally with listOfObjects.toString()
.
Any random key can be used, e.g. uuid
or Math.random()
. Shallow comparison of listOfObjects
can be performed in parent component to provide new key. useMemo
hook can be used in parent state to conditionally update remount key, and listOfObjects
can be used as a list of parameters that need to be memoized. Here's an example:
const remountKey = useMemo(() => Math.random(), listOfObjects);
return (
<div>
<RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
</div>
);
As an alternative to remount key, child component could be able to reset own state and expose a callback to trigger a reset.
Doing shallow comparison of listOfObjects
inside child component would be an antipattern because this requires it to be aware of parent component implementation.
edited Nov 12 at 10:07
answered Nov 12 at 9:11
estus
66.5k2198208
66.5k2198208
Creating a parent component to do the comparison is only pushing my problem outside of my component, how do I achieve the comparison using hooks? Note that just using a random key means that any re-render of the parent component will cause the timer to be lost, this is not an option. AlsolistOfObjects
is an array of objects, so coercing it to string won't work:[object Object],[object Object],[object Object]"
. Lastly, as you said it yourself, the 3rd option is an antipattern.
– FMCorz
Nov 12 at 9:25
Yes, coercinglistOfObjects
won't work, I mentioned this to explain why current attempt doesn't work. Parent component is the component where you use<RefresherTimer>
, it already exists. using a random key means that any re-render of the parent component will cause the timer to be lost - it doesn't mean that. It basically should bekey={this.state.remountKey}
whereremountKey
is something likeshallowEqual(preState.listOfObjects, newState.listOfObjects) && prevState.remountKey || Math.random()
. If you use Redux, remountKey should likely be calculated there.
– estus
Nov 12 at 9:36
If you want to use hooks, useMemo hook can be likely be used in parent component to calculate remountKey. I provided an example that shows the idea. Using an equivalent to componentDidUpdate in child component would be a mistake because this requires it to be aware oflistOfObjects
while it shouldn't.
– estus
Nov 12 at 10:04
add a comment |
Creating a parent component to do the comparison is only pushing my problem outside of my component, how do I achieve the comparison using hooks? Note that just using a random key means that any re-render of the parent component will cause the timer to be lost, this is not an option. AlsolistOfObjects
is an array of objects, so coercing it to string won't work:[object Object],[object Object],[object Object]"
. Lastly, as you said it yourself, the 3rd option is an antipattern.
– FMCorz
Nov 12 at 9:25
Yes, coercinglistOfObjects
won't work, I mentioned this to explain why current attempt doesn't work. Parent component is the component where you use<RefresherTimer>
, it already exists. using a random key means that any re-render of the parent component will cause the timer to be lost - it doesn't mean that. It basically should bekey={this.state.remountKey}
whereremountKey
is something likeshallowEqual(preState.listOfObjects, newState.listOfObjects) && prevState.remountKey || Math.random()
. If you use Redux, remountKey should likely be calculated there.
– estus
Nov 12 at 9:36
If you want to use hooks, useMemo hook can be likely be used in parent component to calculate remountKey. I provided an example that shows the idea. Using an equivalent to componentDidUpdate in child component would be a mistake because this requires it to be aware oflistOfObjects
while it shouldn't.
– estus
Nov 12 at 10:04
Creating a parent component to do the comparison is only pushing my problem outside of my component, how do I achieve the comparison using hooks? Note that just using a random key means that any re-render of the parent component will cause the timer to be lost, this is not an option. Also
listOfObjects
is an array of objects, so coercing it to string won't work: [object Object],[object Object],[object Object]"
. Lastly, as you said it yourself, the 3rd option is an antipattern.– FMCorz
Nov 12 at 9:25
Creating a parent component to do the comparison is only pushing my problem outside of my component, how do I achieve the comparison using hooks? Note that just using a random key means that any re-render of the parent component will cause the timer to be lost, this is not an option. Also
listOfObjects
is an array of objects, so coercing it to string won't work: [object Object],[object Object],[object Object]"
. Lastly, as you said it yourself, the 3rd option is an antipattern.– FMCorz
Nov 12 at 9:25
Yes, coercing
listOfObjects
won't work, I mentioned this to explain why current attempt doesn't work. Parent component is the component where you use <RefresherTimer>
, it already exists. using a random key means that any re-render of the parent component will cause the timer to be lost - it doesn't mean that. It basically should be key={this.state.remountKey}
where remountKey
is something like shallowEqual(preState.listOfObjects, newState.listOfObjects) && prevState.remountKey || Math.random()
. If you use Redux, remountKey should likely be calculated there.– estus
Nov 12 at 9:36
Yes, coercing
listOfObjects
won't work, I mentioned this to explain why current attempt doesn't work. Parent component is the component where you use <RefresherTimer>
, it already exists. using a random key means that any re-render of the parent component will cause the timer to be lost - it doesn't mean that. It basically should be key={this.state.remountKey}
where remountKey
is something like shallowEqual(preState.listOfObjects, newState.listOfObjects) && prevState.remountKey || Math.random()
. If you use Redux, remountKey should likely be calculated there.– estus
Nov 12 at 9:36
If you want to use hooks, useMemo hook can be likely be used in parent component to calculate remountKey. I provided an example that shows the idea. Using an equivalent to componentDidUpdate in child component would be a mistake because this requires it to be aware of
listOfObjects
while it shouldn't.– estus
Nov 12 at 10:04
If you want to use hooks, useMemo hook can be likely be used in parent component to calculate remountKey. I provided an example that shows the idea. Using an equivalent to componentDidUpdate in child component would be a mistake because this requires it to be aware of
listOfObjects
while it shouldn't.– estus
Nov 12 at 10:04
add a comment |
In short, you want to reset your timer when the reference of the array changes, right ?
If so, you will need to use some diffing mechanism, a pure hooks based solution would take advantage of the second parameter of useEffect
, like so:
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
//reset part, lets just set startedAt to now
useEffect(() => setStartedAt(new Date()),
//important part
[props.listOfObjects] // <= means: run this effect only if any variable
// in that array is different from the last run
)
useEffect(() => {
// everything with intervals, and the render
}
More information about this behaviour here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
add a comment |
In short, you want to reset your timer when the reference of the array changes, right ?
If so, you will need to use some diffing mechanism, a pure hooks based solution would take advantage of the second parameter of useEffect
, like so:
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
//reset part, lets just set startedAt to now
useEffect(() => setStartedAt(new Date()),
//important part
[props.listOfObjects] // <= means: run this effect only if any variable
// in that array is different from the last run
)
useEffect(() => {
// everything with intervals, and the render
}
More information about this behaviour here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
add a comment |
In short, you want to reset your timer when the reference of the array changes, right ?
If so, you will need to use some diffing mechanism, a pure hooks based solution would take advantage of the second parameter of useEffect
, like so:
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
//reset part, lets just set startedAt to now
useEffect(() => setStartedAt(new Date()),
//important part
[props.listOfObjects] // <= means: run this effect only if any variable
// in that array is different from the last run
)
useEffect(() => {
// everything with intervals, and the render
}
More information about this behaviour here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
In short, you want to reset your timer when the reference of the array changes, right ?
If so, you will need to use some diffing mechanism, a pure hooks based solution would take advantage of the second parameter of useEffect
, like so:
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
//reset part, lets just set startedAt to now
useEffect(() => setStartedAt(new Date()),
//important part
[props.listOfObjects] // <= means: run this effect only if any variable
// in that array is different from the last run
)
useEffect(() => {
// everything with intervals, and the render
}
More information about this behaviour here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
answered Nov 16 at 12:36
Bear-Foot
20818
20818
add a comment |
add a comment |
The withRef
creates an "instance variable" in functional component. It acts a flag to indicate whether it is in mount or update phase without updating state.
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
} else {
// do componentDidUpate logic
}
});
add a comment |
The withRef
creates an "instance variable" in functional component. It acts a flag to indicate whether it is in mount or update phase without updating state.
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
} else {
// do componentDidUpate logic
}
});
add a comment |
The withRef
creates an "instance variable" in functional component. It acts a flag to indicate whether it is in mount or update phase without updating state.
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
} else {
// do componentDidUpate logic
}
});
The withRef
creates an "instance variable" in functional component. It acts a flag to indicate whether it is in mount or update phase without updating state.
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
} else {
// do componentDidUpate logic
}
});
answered Nov 21 at 6:27
Morgan Cheng
28.1k52151212
28.1k52151212
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53255951%2fequivalent-to-componentdidupdate-using-react-hooks%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
There's no
componentDidReceiveProps
lifecycle method. Did you meancomponentWillReceiveProps
?– Yangshun Tay
Nov 12 at 7:56
Whoops, I did. In fact, I'm going to edit and refer to
componentDidUpdate
as usingcomponentWillReceiveProps
is discouraged.– FMCorz
Nov 12 at 8:24
Please, provide stackoverflow.com/help/mcve . What exactly is ListOfObjects?
– estus
Nov 12 at 8:26
I added some information regarding that array, however it doesn't really matter what this variable represents, so long as it's understood that it should be shallow-compared using
!==
.– FMCorz
Nov 12 at 8:34
Not exactly. I'll provide an answer shortly.
– estus
Nov 12 at 8:35