Intre Dasting
Intre Dasting

Reputation: 3

Access props/attributes from child component with hooks

I'm trying to create a feature to easily hide/show all items (subcomponents). By using useState I am able to set whether or not all items are hidden/shown. By using useEffect I am able to toggle items that are hidden/shown. I'm having issues accessing the props in the subcomponent to determine whether or not a an item has already been expanded. I wish I could explain this better, but hopefully this coding example will paint a better picture.

index.js

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "semantic-ui-css/semantic.min.css";
import { Button } from "semantic-ui-react";
import Item from "./Item";

const Services = props => {
  const [allExpanded, setAllExpanded] = useState(false);

  return (
    <>
      <p>
        <Button onClick={() => setAllExpanded(false)} content="Hide all" />
        <Button onClick={() => setAllExpanded(true)} content="Show all" />
      </p>
      <p>
        <Item expanded={allExpanded} />
        <Item expanded={allExpanded} />
        <Item expanded={allExpanded} />
      </p>
    </>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<Services />, rootElement);

Item.js

import React, { useState, useEffect } from "react";
import { Accordion } from "semantic-ui-react";

const Item = props => {
  const [expanded, setExpanded] = useState(props.expanded);

  useEffect(() => {
    setExpanded(props.expanded);
  }, [props.expanded]);

  return (
    <Accordion styled>
      <Accordion.Title
        onClick={() => {
          setExpanded(!expanded);
        }}
      >
        <p>{expanded ? "- Hide Item" : "+ Show Item"}</p>
      </Accordion.Title>
      <Accordion.Content active={expanded}>Lorem ipsum...</Accordion.Content>
    </Accordion>
  );
};

export default Item;

CodeSandbox

To replicate my current bug, click any "+ Show Item", then click "Hide All". It will not hide everything, however clicking "Show All", then "Hide All" will hide everything.

Upvotes: 0

Views: 842

Answers (3)

Maiya
Maiya

Reputation: 960

Here's a codeandsandbox, forked from yours:

https://codesandbox.io/s/competent-wildflower-n0hb8

I changed it so that instead of having something like this:

let [allExpanded, setAllExpanded] = useState(true) 

You have something like this:

let [whichExpanded, setWhichExpanded] = useState({0: true, 1:true, 2: true})

Then, on for your callback to expand/collapse all buttons, you have this:

<button onClick=()=>{
    let newState = {}
    for(let order in whichEpanded){
       newState[order] = false //change every key to false
    }
    setAllExpanded(newState)
}> hide all</button>

Then, I passed down an "order" prop to your items. The "order" prop is used as an argument to a callback function that I pass down, so when you click on each item, it updates the whichExpanded state, to toggle the visibility of just that one item.

 // pass this to eac of the items:
 const setIndividualItemExpanded = order => {
    let newItemsExpandedState = { ...itemsExpanded };
    newItemsExpandedState[order] = !newItemsExpandedState[order];
    setItemsExpanded(newItemsExpandedState);
  };

Each item component:

  <Item
      expanded={itemsExpanded[0]} //is reading from the state
      order={0}
      setExpanded={setIndividualItemExpanded}
    />

Then, you can remove the useState from the rendered component and just update with the "setExpanded" prop

(See complete code in codesandbox pasted at top)

Upvotes: 0

Filipp Sher
Filipp Sher

Reputation: 128

Since you are handling the expanded state of your accordions on the top level, I suggest you just pass down the expanded state and the 'toggler' to your items. index.js will handle the logic and your Item component will be presentational.

Here's a fork of your CodeSandbox.

It doesn't look great and probably the item state and toggling can (and probably should) be moved elsewhere (for example a separate reducer with the usage of the useReducer hook)

If you are planning to create these components dynamically, IMO this is the easiest way to go.

If you still want to go your way, you can refactor your Item to a class component and use Refs to get their current state, however I not recommend this approach.

Hope this helps!

Upvotes: 0

wrsx
wrsx

Reputation: 789

You're facing this issue because your parent component actually has three possible states:

  • All expanded
  • All collapsed
  • Neither all expanded or collapsed

To reflect the third state, you could use null/undefined (and pass the setter down into your children components).

Updated example here: https://codesandbox.io/s/competent-villani-i6ggh

Upvotes: 1

Related Questions