Home > Back-end >  Why do 2 same div container produce different result when navigating through keyboard on radio butto
Why do 2 same div container produce different result when navigating through keyboard on radio butto

Time:06-30

I would like to wrap radio button group with 200px width. And I expect keyboard navigation to behave the same, which is to move between radio buttons through right/left arrow back and forth. Weird thing is if I implement parent div by wrapping this Container component const Container = ({ children }) => ( <div style={{ width: "200px" }}>{children}</div>);, after moving to different radio button once, it stops. I can't no longer navigate radio buttons. But if I implement parent div using plain html code <div style={{ width: "200px"}}>, keyboard navigation works fine. When I inspect using dev tools, semantically, they both appear the same. So I'm having trouble figuring out why Container component impacts keyboard navigation while hard coding in html doesn't. Anybody knows why? https://codesandbox.io/s/radio-button-example-3t8l80?file=/App.js

import { Stack, RadioButton } from "@shopify/polaris";
import { useState, useCallback } from "react";

function RadioButtonExample() {
  const [value, setValue] = useState("disabled");

  const handleChange = useCallback(
    (_checked, newValue) => setValue(newValue),
    []
  );

  const Container = ({ children }) => (
    <div style={{ width: "200px" }}>{children}</div>
  );

  return (
    <Container> // keyboard navigation doesn't work properly with this
      {/* <div style={{ width: "200px"}}> */} // keyboard navigation works fine
      <Stack vertical>
        <RadioButton
          label="Accounts are disabled"
          helpText="Customers will only be able to check out as guests."
          checked={value === "disabled"}
          id="disabled"
          name="accounts"
          onChange={handleChange}
        />
        <RadioButton
          label="Accounts are optional"
          helpText="Customers will be able to check out with a customer account or as a guest."
          id="optional"
          name="accounts"
          checked={value === "optional"}
          onChange={handleChange}
        />
      </Stack>
      {/* </div> */}
    </Container>
  );
}

export default RadioButtonExample;

CodePudding user response:

The reason for this to happen is because your Container component is part of the RadioButtonExample component. Every time a state or prop gets updated, the Container component gets created as a new component. To prevent this, take your Container component outside of the RadioButtonExample like this:

import { Stack, RadioButton } from "@shopify/polaris";
import { useState, useCallback } from "react";


const Container = ({ children }) => (
  <div style={{ width: "200px" }}>{children}</div>
);

function RadioButtonExample() {
  const [value, setValue] = useState("disabled");

  const handleChange = useCallback(
    (_checked, newValue) => setValue(newValue),
    []
  );

  return (
    <Container>
      <Stack vertical>
        <RadioButton
          label="Accounts are disabled"
          helpText="Customers will only be able to check out as guests."
          checked={value === "disabled"}
          id="disabled"
          name="accounts"
          onChange={handleChange}
        />
        <RadioButton
          label="Accounts are optional"
          helpText="Customers will be able to check out with a customer account or as a guest."
          id="optional"
          name="accounts"
          checked={value === "optional"}
          onChange={handleChange}
        />
       </Stack>
    </Container>
  );
}

export default RadioButtonExample;

A different solution would be to wrap the container in a useMemo to make sure it stays the same. This would give you something like this:

const Container = useMemo(() => ({ children }) => (
    <div style={{ width: "200px" }}>{children}</div>
  ), []);

Both will work, but I would suggest going for the first solution.

  • Related