2
votes

I have a set of buttons in a child component where when clicked set a corresponding state value true or false. I have a useEffect hook in this child component also with dependencies on all these state values so if a button is clicked, this hook then calls setFilter which is passed down as a prop from the parent...

const Filter = ({ setFilter }) => {

  const [cycling, setCycling] = useState(true);
  const [diy, setDiy] = useState(true);

  useEffect(() => {
    setFilter({
      cycling: cycling,
      diy: diy
    });
  }, [cycling, diy]);

  return (
    <Fragment>
      <Row>
        <Col>
          <Button block onClick={() => setCycling(!cycling)}>cycling</Button>
        </Col>
        <Col>
          <Button block onClick={() => setdIY(!DIY)}>DIY</Button>
        </Col>
      </Row>
    </Fragment>
  );
};

In the parent component I display a list of items. I have two effects in the parent, one which does an initial load of items and then one which fires whenever the filter is changed. I have removed most of the code for brevity but I think the ussue I am having boils down to the fact that on render of my ItemDashboard the filter is being called twice. How can I stop this happening or is there another way I should be looking at this.

const ItemDashboard = () => {

  const [filter, setFilter] = useState(null);

  useEffect(() => {
    console.log('on mount');
  }, []);

  useEffect(() => {
    console.log('filter');
  }, [filter]);

  return (
    <Container>..
      <Filter setFilter={setFilter} />
    </Container>
  );
}
2
Why don't you control all state variables in parent component ? And what is the main issue here ? Doesn't it update the parent's state with setFilter method ? - Süleyman GÜNDÜZ
Yes it does update the parents state but the effect on the filter state fires twice on load and in my code the filter effects a load of items from a database twice. - jonesy

2 Answers

1
votes

Yes, you can, useEffect in child component which depends on the state is also how you typically implement a component which is controlled & uncontrolled:

const NOOP = () => {};

// Filter
const Child = ({ onChange = NOOP }) => {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    onChange(counter);
  }, [counter, onChange]);

  const onClick = () => setCounter(c => c + 1);

  return (
    <div>
      <div>{counter}</div>
      <button onClick={onClick}>Increase</button>
    </div>
  );
};

// ItemDashboard
const Parent = () => {
  const [value, setState] = useState(null);

  useEffect(() => {
    console.log(value);
  }, [value]);

  return <Child onChange={setState} />;
};

Edit stoic-snow-snu7o

1
votes

I'm guessing, you're looking for the way to lift state up to common parent.

In order to do that, you may bind event handlers of child components (passed as props) to desired callbacks within their common parent.

The following live-demo demonstrates the concept:

const { render } = ReactDOM,
      { useState } = React
      
const hobbies = ['cycling', 'DIY', 'hiking'] 

const ChildList = ({list}) => (
  <ul>
    {list.map((li,key) => <li {...{key}}>{li}</li>)}
  </ul>
)
      
const ChildFilter = ({onFilter, visibleLabels}) => (
  <div>
  {
    hobbies.map((hobby,key) => (
      <label {...{key}}>{hobby}
        <input 
          type="checkbox" 
          value={hobby}
          checked={visibleLabels.includes(hobby)}
          onChange={({target:{value,checked}}) => onFilter(value, checked)}
        />
      </label>))
  }
  </div>
)
      
const Parent = () => {
  const [visibleHobbies, setVisibleHobbies] = useState(hobbies),
        onChangeVisibility = (hobby,visible) => {
          !visible ?
          setVisibleHobbies(visibleHobbies.filter(h => h != hobby)) :
          setVisibleHobbies([...visibleHobbies, hobby])       
        }
   return (
      <div>
        <ChildList list={visibleHobbies} />
        <ChildFilter onFilter={onChangeVisibility} visibleLabels={visibleHobbies} />
      </div>
   )
}

render (
  <Parent />,
  document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>