question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[terra-responsive-element] Does not honor proper React.useEffect behavior

See original GitHub issue

Bug Report

Description

terra-responsive-element doesn’t work well when used with React.useEffect, specifically when creating an effect that focuses on an element when mounting, e.g. focus on a button:

function Example() {
  const buttonRef = React.useRef(null);

  React.useEffect(() => {
    const { current: button } = buttonRef;
    button.focus();
  }, []); // only on mount (empty dep list means on mount)

  return (
    <button type="button" ref={buttonRef}>A button with a ref</button>
  );
}

This pattern is common when using modals (i.e. terra-modal-manager) to shift focus into the modal (from the body) when opened (mounted) for a11y and proper focus management.

Some consumers are using terra-responsive-element in the following way:

// pretend that this was mounted using `disclosureManager.disclose({ ... })`
function ModalContent() {
  const titleRef = React.useRef(null);

  React.useEffect(() => {
    const { current: title } = titleRef;
    if (title) {
      // this will never fire, using this example setup
      title.focus(); 
    } else {
      console.error("No title ref to focus");
    }
  }, []);

  const tinyContent = (
    <div>
      <h1 tabIndex="0" ref={titleRef}>Tiny Title Here</h1>
      <p>Tiny content here</p>
      <button type="button">Some other focusable thing</button>
    </div>
  );

  const largeContent = (
    <div>
      <h1 tabIndex="0" ref={titleRef}>Large Title Here</h1>
      <p>Large content here</p>
      <button type="button">Some other focusable thing</button>
    </div>
  );

  return (
    <ResponsiveElement
      responsiveTo="window"
      tiny={tinyContent}
      large={largeContent}
    />
  );
}

I have reservations about this pattern (should probably hoist the ResponsiveElement above or use the React.useContext(ActiveBreakpointContext) from terra-breakpoints), but it doesn’t work as expected.

Steps to Reproduce

Check out the code/deployments below:

Here’s a minimal (working?, not as expected tho) example using terra-responsive-element:

Another example that works as expected, using terra-breakpoints inside terra-modal-manager (as described in the example above):

Additional Context / Screenshots

Expected Behavior

React.useEffect() (especially, React.useEffect(() => { ... }, []) expected behavior should be honored.

Possible Solution

I would probably suggest either deprecating ResponsiveElement in preference for using terra-breakpoints ActiveBreakpointContext (like in the example repo I linked above), since it can be heavy when used in actual apps (at least as I have seen consumers use it) or providing guidance on better patterns of use (e.g. hoisting above, like state, and using it like you would with ActiveBreakpointContext).

Environment

  • Component Name and Version: terra-responsive-element 5.10.0

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:8 (8 by maintainers)

github_iconTop GitHub Comments

1reaction
StephenEssercommented, Oct 10, 2019

We may want to address the point you made in the summary of this issue first and determine if we want to deprecate the terra-responsive-element completely.

Components using the terra-responsive-element with responsiveTo set to window should be able to uplift onto the terra-breakpoints package and remove the dependency on the responsive element all together. But components and projects using the responsive element to be responsive to their parent containers may have a hard time finding an alternative solution to the responsive element.

I think there is value in keeping the controlled responsive element around. The onChange and onResize callbacks open a lot of functionality for responsive components. It acts as an abstraction and wrapper for a consistent resize observer. The uncontrolled responsive element also has some valid use-cases, but most situations should be using the controlled component or could be converted into a controlled component to achieve the same functionality.

I don’t believe the issues mentioned above can be resolved with the uncontrolled version of the component. If we consider this issue a real bug we may want to deprecate it. At a minimum if we choose to keep the uncontrolled responsive element we’d need to document the caveats to using it.

1reaction
StephenEssercommented, Oct 9, 2019

This is likely a side effect of the same issue here: https://github.com/cerner/terra-core/issues/2673

The uncontrolled responsive element doesn’t know which element to render until it’s mounted and calculated the available size.

At first glance I don’t think this is something that can be resolved with the uncontrolled responsive element. However I’d be interested in seeing if the issue is the same with the controlled responsive element.

Would you be willing to recreate these examples with the controlled responsive element and see if the same issues are encountered?

https://engineering.cerner.com/terra-ui/components/terra-responsive-element/responsive-element/responsive-element

Example from the doc site:

import React, { useState } from 'react';
import Placeholder from 'terra-doc-template/lib/Placeholder';
import ResponsiveElement from 'terra-responsive-element';

const BreakpointExample = () => {
  const [breakpoint, setBreakpoint] = useState('');

  return (
    <ResponsiveElement onChange={value => setBreakpoint(value)}>
      <Placeholder title={breakpoint} />
    </ResponsiveElement>
  );
};

export default BreakpointExample;
Read more comments on GitHub >

github_iconTop Results From Across the Web

React.useEffect Hook – Common Problems and How to Fix ...
We are building a React app and we want to display the user name of the current user in one of our components....
Read more >
reactjs - UseEffect doesn't behave as expected - Stack Overflow
Yes, if you add a dependency to an useEffect hook that ultimately updates that dependency value then this will cause render looping.
Read more >
Using the Effect Hook - React
Why is useEffect called inside a component? Placing useEffect inside the component lets us access the count state variable (or any props) right...
Read more >
The last guide to the useEffect Hook you'll ever need
Understanding how the useEffect Hook works, and why it requires a wholesale shift in mindset, is essential to writing modern React code.
Read more >
The tricky behavior of useEffect hook in React 18 - Medium
You can read more about New Strict Mode Behaviors in React 18, ... you don't have a good place to cache the result...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found