Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: functional components are not re-rendered if the provided props look the same #19104

Closed
danielo515 opened this issue Jun 9, 2020 · 8 comments

Comments

@danielo515
Copy link

On previous versions of react, functional components were always re-rendered unless they were memoized. With the latest version, if the props do not change the component does not re-render. While this is an improvement in general, there are certain scenarios where you want to force a re-render, for example to react to some user input that does not change the value, but requires to make operations on the dom. A great example is an input field, where you may need to reposition the cursor even if the input content is the same

React version: 16.13.0

Steps To Reproduce

  1. Create a controlled input component
  2. Handle the state from the parent, for example using useStateHook
  3. Make the state behave to set the input-value to the same value when the user inputs certain value, for example, behave normal for any input except when the user types "aa", then you set the value to always the same "aa" one, not allowing the user to enter more than two a
  4. Even if you add an useEffect inside the input component, the component is not re-rendered

Link to code example:

https://codepen.io/danielo515/pen/KKVdmye?editors=0010

The current behavior

The component does not get re-render if the provided props do not change, even if there is a useEffect hook inside.

The expected behavior

The functional component should re-render unless it is memoized, or at least take in account if there is an use effect hook.

While this may look stupid from the provided example, imagine a controlled number input that automatically formats the user input, adding two decimal positions at the end .00. When the user reaches the dot, he/she may expect to input a dot and let the cursor move after the existing one, however, because the final value hasn't changed (there text still ends at .00 thanks to automatic formatting) the component is not re-rendered, making impossible to re-position the cursor using useEffect hook.

As a workaround, you can add an use-state hook inside the controlled component, so you can store there the raw user input, but that only works until the user tries to input a value that doesn't change the internal state.

@danielo515 danielo515 added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Jun 9, 2020
@vkurchatkin
Copy link

This works just fine. If you call setState with the value equal to the current state, it might not rerender the current component because nothing have changes. This is expected

@danielo515
Copy link
Author

even on a functional component? It is not a class component or a pure component.
What about the scenario that I'm describing? Where you need to update certain part of the dom component with useEffect?

@illuminist
Copy link

In your example, as you set state for the hook to be just the same value, it just doesn't trigger any rerendering on any component at all. But if you add another useState hook to count rendering time and increase it in update function, the new hook will trigger rerendering in App component and in turn also rerender Input because Input isn't memoized as you stated.

  const [state,setState] = React.useState("")
  const [,setRenderCount] = React.useState(0)
  const update = val => {
    setRenderCount(i => i + 1)
    if(val === 'aaa') return setState('aa')
    setState(val)
  }

This's probably new information for you and you might find it useful to make whatever you need.

@danielo515
Copy link
Author

Hello @illuminist , thanks for providing a workaround. The thing is that I can not rely on the parent component state. Will that work if I add the sate on the Input itself? On my experience, it worked but I don't know if that is expected.
At this point, will it be better to just attach a normal event listener to the on change event ?

@illuminist
Copy link

illuminist commented Jun 10, 2020

Should be better to rely on change event instead as it has an access to the input element can manipulate its cursor directly.

Main reason is because of state doesn't change, there is no reason for a component to re-render. Maybe have another internal state for displaying while keep true value in parent state. But I don't know your real use case.

@danielo515
Copy link
Author

Hello @illuminist , my use-case is an input field that auto-formats as the user types. Because the auto formatting there are situations where the resulting value after user-input + format has not changed (for example, the user inputs a comma, and after auto format instead of ,, you have just one comma , but the cursor should be at the right of the comma now).
Having an internal state may be a solution, but it can not be the actual user input because without formatting it may be incorrect.

@illuminist
Copy link

So that you should have 2 state: one for display, and another for actual value.

The display one should be internal state, and actual value is in parent component. Mainly, because of input event will report its value, in this case, the display state. So in value change handler, you have to find a way to convert display value into actual value (or formatted value into actual value) for example: converting string "10,000" into number 10000. Then your component reports 10000 back to parent component and save "10,000" as its internal state. This way, even user inputs "10,000.00", the parent component will have same 10000 actual value, but input component will have "10,000.00" to display.

@eps1lon
Copy link
Collaborator

eps1lon commented Aug 20, 2020

On previous versions of react

You're probably referring to the experimental version of hooks where React didn't bail out when it saw identical states. This was changed in #14569 and is documented under "Bailing out of a state update".

This means your App component won't re-render if the state is identical which in turn will not re-render the Input component.

If you're referring to setState in class components then it isn't intended that a class components setState behaves the same as the state getter returned from useState.

If you rely on the component re-rendering on identical state values you can either switch back to class components or wrap the state value in an object e.g. setState({ value }) and destructure it on read e.g const [{ value }, setState] = React.useState().

@eps1lon eps1lon closed this as completed Aug 20, 2020
@eps1lon eps1lon added Resolution: Expected Behavior and removed Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug labels Aug 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants