1

I'm using useState hook in a custom hook.

I'm calling the setValue function that is returned from the useState twice:

1) After an onChange event

2) After the component was notified of a change from the server.

The flow of events is:

  • onChange event (in the component) triggers setValue = rerender
  • I'm using a useEffect (=after rerender) hook to update the server of the change - API function is called to update the server
  • I have a custom service which receives the server's response and notifies the component
  • When notified the component calls setValue again but in this case, the value is the same so no need to rerender.

My problem is the component gets rerendered after it was notified of the changes even though the value received is the same.

My code:

Gain Component

import * as React from 'react';
import { useDSPParamUpdate } from '../../../Hooks/useDSPParamUpdate';
import { ControlParamProps } from '../..';

const Gain = (props: ControlParamProps) => {

  let min: number = 0;
  let max: number = 0;


  const { paramId, controlId } = props;
  const { param, value, setValue } = useDSPParamUpdate({ controlId, paramId })

  if (param && param.range && param.range.length >= 2) {
    min = param.range[0];
    max = param.range[1];
  }

  /*calls the setValue from the hook*/
  const handleChange = (event: any) => {
    const newValue = event.target.value;
    setValue(newValue);
  }


  return (
    <div className="gain">
      {max}
      <input className="dsp-action range-vertical" type="range"
        min={min}
        max={max}
        value={value}
        onChange={handleChange} />
      {min}
    </div>
  );
}

export default Gain;

useDSPParamUpdate - custom hook

    import * as React from 'react';
        import { ControlParamProps } from '../dsp';
        import { dspService } from '../../core/services/dsp.service';

        export function useDSPParamUpdate(props: ControlParamProps) {

            const initValue = ...
            const [value, setValue] = React.useState(initValue);

            function updateDevice() {
                // calls some API func to update the server (sends the value)
            }

            // subscribes to server changes
            React.useEffect(() => {
                    // subscribrs to server notifications
                    let unsubscribe = dspService.subscribe((newVal) => setValue(newVal));
                return () => {
                    unsubscribe();
                };
            }, []);


            // sends data to the server after update
            React.useEffect(() => {
                updateDevice();
            }, [value]);

            return { param, value, setValue };
        }
worc
  • 3,654
  • 3
  • 31
  • 35
neomib
  • 3,503
  • 4
  • 17
  • 27
  • 1
    There are optimizations that avoid re-rendering in some cases when the same value is passed to a state setter, but there is no guarantee that it won't re-render. For more details, see my answer here: https://stackoverflow.com/questions/54715188/react-hook-rendering-an-extra-time/54716601#54716601 – Ryan Cogswell Feb 20 '19 at 19:17

1 Answers1

1

Typically it's not an issue if render() is called extra time.

But if you want you may guard calling setValue() by checking if value is the same

let unsubscribe = dspService.subscribe(
    (newVal) => 
        newVal !== value && setValue(newVal)
);  

Maybe slightly verboose way but it's the same approach as typically used in componentDidUpdate

Note that useState does not provide any logic like shouldComponentUpdate has. So if you want to make it in more declarative way you have to refactor your component to be class-based accessor of PureComponent.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
  • I tried this but when I debugged it I saw the `value` always had the initial value...What do you mean by "Typically it's not an issue if render() is called extra time."? From what I know extra rendering is a performance issue... – neomib Feb 20 '19 at 14:53
  • unfortunately I cannot find an article where it's measured. But under the hood `render()` does not make operations on DOM so it should be really fast(until you don't have complex math here). And since DOM is updated only if there is difference between "real DOM vs virtual DOM" I strongly believe you don't need to care about extra `render()` calls. – skyboyer Feb 20 '19 at 15:11
  • As for "`value` always has inital value" it's really weird since you finally output it so you should mention already if it was not ever changed – skyboyer Feb 20 '19 at 15:13
  • It is weird..I think it is a bug...Thanks anyway – neomib Feb 20 '19 at 16:28