Equivalent to componentDidUpdate using React hooks










4














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].










share|improve this question























  • 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










  • 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















4














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].










share|improve this question























  • 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










  • 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













4












4








4


1





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].










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 12 at 8:32

























asked Nov 12 at 4:24









FMCorz

867610




867610











  • 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










  • 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










  • 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










  • 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












3 Answers
3






active

oldest

votes


















0














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.






share|improve this answer






















  • 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










  • 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


















0














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






share|improve this answer




























    0














    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

    );





    share|improve this answer




















      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
      );



      );













      draft saved

      draft discarded


















      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









      0














      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.






      share|improve this answer






















      • 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










      • 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















      0














      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.






      share|improve this answer






















      • 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










      • 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













      0












      0








      0






      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.






      share|improve this answer














      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.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Nov 12 at 10:07

























      answered Nov 12 at 9:11









      estus

      66.2k2198208




      66.2k2198208











      • 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










      • 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
















      • 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










      • 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















      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













      0














      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






      share|improve this answer

























        0














        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






        share|improve this answer























          0












          0








          0






          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






          share|improve this answer












          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







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 16 at 12:36









          Bear-Foot

          20818




          20818





















              0














              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

              );





              share|improve this answer

























                0














                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

                );





                share|improve this answer























                  0












                  0








                  0






                  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

                  );





                  share|improve this answer












                  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

                  );






                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Nov 21 at 6:27









                  Morgan Cheng

                  28.1k52151212




                  28.1k52151212



























                      draft saved

                      draft discarded
















































                      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.




                      draft saved


                      draft discarded














                      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





















































                      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







                      這個網誌中的熱門文章

                      How to read a connectionString WITH PROVIDER in .NET Core?

                      Node.js Script on GitHub Pages or Amazon S3

                      Museum of Modern and Contemporary Art of Trento and Rovereto