Mike Willis
Mike Willis

Reputation: 1508

Components rendered in different tabs are accidentally sharing state?

I have a component which, among other things, has a controlled text field used for filtering, and the value of the text field is stored in the component's state.

I discovered yesterday that if I render multiple instances of that component inside tab panes of semantic-ui-react's Tab component, the states seem to be shared. Meaning, if I type into Tab A's text field then switch to Tab B, the text field there has the same content as Tab A, even though I never typed into it.

However, if I render multiple instances outside semantic-ui-react's Tab component, the state is not shared. Typing text into one field has no effect on any other field.

Ideally I do not want the components to share state (or do whatever weird thing they're doing to have the same value). I want the value of each component's text field to be independent of the other.

I feel like it has something to do with the components not being rendered until I change to their tab, but I can't get much further than that. I'm not sure if this is a problem specific to semantic-ui-react - in fact I suspect if I create my own tab component it might have the same behavior. Demo code is below, and I set up a basic codepen that illustrates the problem, can anyone shed some light on the subject?

codepen demo: https://codepen.io/MikeWillis/pen/XWMXRoJ

js code:

const Filter = (props) => {
  const [filter,setFilter] = useState("");
  return (
    <React.Fragment>
      <label>
        {props.name}:{" "}
        <input
          type="search"
          value={filter}
          onChange={(event)=>setFilter(event.target.value)}
          />
       </label>
     </React.Fragment>
  )
};

const TabbedFilters = (props) => {
  const panes = [
    { menuItem: 'Filter A', render: () => <Tab.Pane><Filter name="Filter A" /></Tab.Pane> },
    { menuItem: 'Filter B', render: () => <Tab.Pane><Filter name="Filter B" /></Tab.Pane> }
  ];

  return (
    <div style={{border: "1px solid #ccc", margin: "5px", padding: "3px"}}>
      <p><b>These filters WILL share state. Not cool.</b></p>
      <Tab panes={panes} />
     </div>
  )
};

const UnTabbedFilters = (props) => {
  return (
    <div style={{border: "1px solid #ccc", margin: "5px", padding: "3px"}}>
      <p><b>These filters will NOT share state. Cool, that makes sense.</b></p>
      <div style={{marginLeft: "15px"}}>
        <Filter name="Filter A" />
        <br />
        <Filter name="Filter B" />
      </div>
    </div>
  )
};

const AllFilters = (props) => {
  return (
    <React.Fragment>
      <UnTabbedFilters />
      <TabbedFilters />
    </React.Fragment>
  )
};


ReactDOM.render( <AllFilters />,document.getElementById("root"));

Upvotes: 1

Views: 1190

Answers (1)

Andrew Gillis
Andrew Gillis

Reputation: 3875

Add the key prop. This will help React diff the DOM properly.

const TabbedFilters = (props) => {
  const panes = [
    { menuItem: 'Filter A', render: () => <Tab.Pane><Filter key="filter-a" name="Filter A" /></Tab.Pane> },
    { menuItem: 'Filter B', render: () => <Tab.Pane><Filter key="filter-b" name="Filter B" /></Tab.Pane> }
  ];

  return (
    <div style={{border: "1px solid #ccc", margin: "5px", padding: "3px"}}>
      <p><b>These filters WILL share state. Not cool.</b></p>
      <Tab panes={panes} />
     </div>
  )
};

Upvotes: 2

Related Questions