# React Descendants

_Questions? Ask in [Slack #vcc-ui](https://vcc-consumer-digital.slack.com/archives/CCD2PPD0S)_

> @volvo-cars/react-descendants

<Badgen name="@volvo-cars/react-descendants" />

A React hook that helps keep track of the indices of descendant components. It's useful for places where the exact rendering order of a descendent component is unknown. It allows each descendant component to know its index relative other components or renders of this specific component.

This way we can use component composition any way we like while knowing the exact index of each component:

```jsx
<View>
  /*index 0*/
  <AccordionComp>1</AccordionComp>
  /*index 1*/
  <AccordionComp>2</AccordionComp>
  <Block>
    /*index 2*/
    <AccordionComp>3</AccordionComp>
  </Block>
  <Block>
    <Block>
      /*index 3*/
      <AccordionComp>4</AccordionComp>
    </Block>
  </Block>
</View>
```

## Installation

<Install  name="@volvo-cars/react-descendants"/>

> 💡 This package includes Typescript definitions

## Usage

We first need to wrap the components which we want to know the rendering index for with a `DescendantsIndexProvider` and consume the `index` with `useDescendantIndex`. The hook will return a `ref` that needs to be passed to the react element to keep track off.

### Simple

```jsx live noInline
const { useDescendantIndex, DescendantsIndexProvider } =
  createDescendantsIndexContext();

const Component = () => {
  const { ref, index } = useDescendantIndex();
  return (
    <Block ref={ref}>
      <Text>Index:{index}</Text>
    </Block>
  );
};

const Wrapper = () => {
  return (
    <DescendantsIndexProvider>
      <Component />
      <View>
        <Component />
      </View>
      <Spacer />
      <View>
        <View>
          <Component />
        </View>
      </View>
    </DescendantsIndexProvider>
  );
};
render(<Wrapper />);
```

### Advanced

Example below shows how the component indices update with mounting and unmounting of components. Try clicking on each component.

```jsx live noInline
const { useDescendantIndex, DescendantsIndexProvider } =
  createDescendantsIndexContext();

const Component = ({ onClick, children }) => {
  const { ref, index } = useDescendantIndex();
  return (
    <Block ref={ref} onClick={onClick}>
      <Text>
        Component {children} | index:{index}
      </Text>
    </Block>
  );
};

const Wrapper = () => {
  const [hidden, setHidden] = React.useState([]);
  return (
    <DescendantsIndexProvider>
      {hidden.length ? (
        <Link onClick={() => setHidden([])}>Show all</Link>
      ) : null}
      {hidden.includes(1) ? null : (
        <Component onClick={() => setHidden([...hidden, 1])}>1</Component>
      )}
      <View>
        {hidden.includes(2) ? null : (
          <Component onClick={() => setHidden([...hidden, 2])}>2</Component>
        )}
      </View>
      <Spacer />
      <View>
        <View>
          {hidden.includes(3) ? null : (
            <Component onClick={() => setHidden([...hidden, 3])}>3</Component>
          )}
        </View>
      </View>
      <View>
        <View>
          {hidden.includes(4) ? null : (
            <Component onClick={() => setHidden([...hidden, 4])}>4</Component>
          )}
        </View>
      </View>
      <View>
        <View>
          {hidden.includes(5) ? null : (
            <Component onClick={() => setHidden([...hidden, 5])}>5</Component>
          )}
        </View>
      </View>
    </DescendantsIndexProvider>
  );
};
render(<Wrapper />);
```

### Nested Providers

It's also possible to nest providers however we like, each hook will update its corresponding context value without affecting other contexts.

```jsx live noInline
const { useDescendantIndex, DescendantsIndexProvider } =
  createDescendantsIndexContext();
const {
  useDescendantIndex: useDescendantIndexAccordionComp,
  DescendantsIndexProvider: DescendantsIndexProviderAccordionComp,
} = createDescendantsIndexContext();

const Component = ({ onClick, children }) => {
  const { ref, index } = useDescendantIndex();
  return (
    <Block ref={ref} onClick={onClick}>
      <Text>
        Component {children} | index:{index}
      </Text>
    </Block>
  );
};
const AccordionComp = ({ onClick, children }) => {
  const { ref, index } = useDescendantIndexAccordionComp();
  return (
    <Block ref={ref} onClick={onClick}>
      <Text>
        AccordionComp {children} | index:{index}
      </Text>
    </Block>
  );
};

const Wrapper = () => {
  const [hidden, setHidden] = React.useState([]);
  const [hiddenAccordionComp, setHiddenAccordionComp] = React.useState([]);
  return (
    <DescendantsIndexProvider>
      {hidden.length ? (
        <Link
          onClick={() => {
            setHidden([]);
            setHiddenAccordionComp([]);
          }}
        >
          Show all
        </Link>
      ) : null}
      {hidden.includes(1) ? null : (
        <Component onClick={() => setHidden([...hidden, 1])}>1</Component>
      )}
      <View>
        {hidden.includes(2) ? null : (
          <Component onClick={() => setHidden([...hidden, 2])}>2</Component>
        )}
      </View>
      <Spacer />
      <View>
        <View>
          {hidden.includes(3) ? null : (
            <Component onClick={() => setHidden([...hidden, 3])}>3</Component>
          )}
          <DescendantsIndexProviderAccordionComp>
            {hiddenAccordionComp.includes(1) ? null : (
              <AccordionComp
                onClick={() =>
                  setHiddenAccordionComp([...hiddenAccordionComp, 1])
                }
              >
                1
              </AccordionComp>
            )}
            <View>
              {hiddenAccordionComp.includes(2) ? null : (
                <AccordionComp
                  onClick={() =>
                    setHiddenAccordionComp([...hiddenAccordionComp, 2])
                  }
                >
                  2
                </AccordionComp>
              )}
              <View>
                {hidden.includes(4) ? null : (
                  <Component onClick={() => setHidden([...hidden, 4])}>
                    4
                  </Component>
                )}
              </View>
            </View>
          </DescendantsIndexProviderAccordionComp>
        </View>
      </View>
      <View>
        <View>
          {hidden.includes(5) ? null : (
            <Component onClick={() => setHidden([...hidden, 5])}>5</Component>
          )}
        </View>
      </View>
    </DescendantsIndexProvider>
  );
};
render(<Wrapper />);
```
