Skip to content

Instantly share code, notes, and snippets.

@heridev
Last active August 5, 2022 15:49
Show Gist options
  • Save heridev/1c81073f344ecc1dbc2cdf741a0e30db to your computer and use it in GitHub Desktop.
Save heridev/1c81073f344ecc1dbc2cdf741a0e30db to your computer and use it in GitHub Desktop.
Another good example of React context for a component and the declaration looks great, but at the same time it adds complexity that it could be handled directly by passing the options down only for the Component that needs it and at the same time make it clear on the traditional flow passing properties down

App.js

import React from "react";
import Accordion from "./components/Accordion";
import faqData from "./data";
export default function App() {
  return (
    <Accordion>
      <Accordion.Title>Frequently Asked Questions</Accordion.Title>
      <Accordion.Wrapper>
        {faqData.map((item) => (
          <Accordion.Item key={item.id}>
            <Accordion.ItemHeader>{item.header}</Accordion.ItemHeader>
            <Accordion.Body>{item.body}</Accordion.Body>
          </Accordion.Item>
        ))}
      </Accordion.Wrapper>
    </Accordion>
  );
}

Accordion.js

import React, { useState, useContext, createContext } from "react";
import {
  Container,
  Inner,
  Item,
  Body,
  Wrapper,
  Title,
  Header
} from "./Accordion.styles";

const ToggleContext = createContext();

export default function Accordion({ children, ...restProps }) {
  return (
    <Container {...restProps}>
      <Inner>{children}</Inner>
    </Container>
  );
}

Accordion.Title = function AccordionTitle({ children, ...restProps }) {
  return <Title {...restProps}>{children}</Title>;
};

Accordion.Wrapper = function AccordionFrame({ children, ...restProps }) {
  return <Wrapper {...restProps}>{children}</Wrapper>;
};

Accordion.Item = function AccordionItem({ children, ...restProps }) {
  const [toggleShow, setToggleShow] = useState(true);
  const toggleIsShown = (isShown) => setToggleShow(!isShown);

  return (
    <ToggleContext.Provider value={{ toggleShow, toggleIsShown }}>
      <Item {...restProps}>{children}</Item>
    </ToggleContext.Provider>
  );
};

Accordion.ItemHeader = function AccordionHeader({ children, ...restProps }) {
  const { toggleShow, toggleIsShown } = useContext(ToggleContext);

  return (
    <Header onClick={() => toggleIsShown(toggleShow)} {...restProps}>
      {children}
    </Header>
  );
};

Accordion.Body = function AccordionBody({ children, ...restProps }) {
  const { toggleShow } = useContext(ToggleContext);
  return (
    <Body className={toggleShow ? "open" : ""} {...restProps}>
      <span>{children}</span>
    </Body>
  );
};

Accordion.style.js

import styled from "styled-components";

export const Container = styled.div`
  display: flex;
  background: #6867ac;
  border-bottom: 8px solid #ffbcd1;
  font-family: "Inter", sans-serif;
`;

export const Wrapper = styled.div`
  margin-bottom: 40px;
`;

export const Inner = styled.div`
  display: flex;
  padding: 70px 45px;
  flex-direction: column;
  max-width: 815px;
  margin: auto;
`;

export const Title = styled.h1`
  font-size: 33px;
  line-height: 1.1;
  margin-top: 0;
  margin-bottom: 8px;
  color: white;
  text-align: center;
`;

export const Item = styled.div`
  color: white;
  margin: auto;
  margin-bottom: 10px;
  max-width: 728px;
  width: 100%;
  &:first-of-type {
    margin-top: 3em;
  }
  &:last-of-type {
    margin-bottom: 0;
  }
`;

export const Header = styled.div`
  display: flex;
  flex-direction: space-between;
  cursor: pointer;
  border: 1px solid #ce7bb0;
  border-radius: 8px;
  box-shadow: #ce7bb0;
  margin-bottom: 1px;
  font-size: 22px;
  font-weight: normal;
  background: #ce7bb0;
  padding: 0.8em 1.2em 0.8em 1.2em;
  user-select: none;
  align-items: center;
`;

export const Body = styled.div`
  font-size: 18px;
  font-weight: normal;
  line-height: normal;
  background: #ce7bb0;
  margin: 0.5rem;
  border-radius: 8px;
  box-shadow: #ce7bb0;
  white-space: pre-wrap;
  user-select: none;
  overflow: hidden;
  &.open {
    max-height: 0;
    overflow: hidden;
  }
  span {
    display: block;
    padding: 0.8em 2.2em 0.8em 1.2em;
  }
`;

Live mode https://codesandbox.io/embed/xenodochial-lovelace-i9fcw?fontsize=14&hidenavigation=1&theme=dark

App.js

import React from "react";
import Accordion from "./components/Accordion";
import faqData from "./data";
export default function App() {
  return (
    <Accordion>
      <Accordion.Title>Frequently Asked Questions</Accordion.Title>
      <Accordion.Wrapper>
        {faqData.map((item) => (
          <Accordion.Item key={item.id} item={item}/>
        ))}
      </Accordion.Wrapper>
    </Accordion>
  );
}

Accordion.js

import React, { useState } from "react";
import {
  Container,
  Inner,
  Item,
  Body,
  Wrapper,
  Title,
  Header
} from "./Accordion.styles";

export default function Accordion({ children, ...restProps }) {
  return (
    <Container {...restProps}>
      <Inner>{children}</Inner>
    </Container>
  );
}

Accordion.Title = function AccordionTitle({ children, ...restProps }) {
  return <Title {...restProps}>{children}</Title>;
};

Accordion.Wrapper = function AccordionFrame({ children, ...restProps }) {
  return <Wrapper {...restProps}>{children}</Wrapper>;
};

Accordion.Item = function AccordionItem({ item }) {
  const [toggleShow, setToggleShow] = useState(true);
  const toggleIsShown = (isShown) => setToggleShow(!isShown);

  return (
    <Item>
      <Accordion.ItemHeader
        toggleShow={toggleShow}
        toggleIsShown={toggleIsShown}
      >
        {item.header}
      </Accordion.ItemHeader>
      <Accordion.Body toggleShow={toggleShow}>{item.body}</Accordion.Body>
    </Item>
  );
};

Accordion.ItemHeader = function AccordionHeader({
  children,
  toggleIsShown,
  toggleShow
}) {
  return <Header onClick={() => toggleIsShown(toggleShow)}>{children}</Header>;
};

Accordion.Body = function AccordionBody({
  children,
  toggleShow,
  ...restProps
}) {
  return (
    <Body className={toggleShow ? "open" : ""} {...restProps}>
      <span>{children}</span>
    </Body>
  );
};

Accordion.style.js

import styled from "styled-components";

export const Container = styled.div`
  display: flex;
  background: #6867ac;
  border-bottom: 8px solid #ffbcd1;
  font-family: "Inter", sans-serif;
`;

export const Wrapper = styled.div`
  margin-bottom: 40px;
`;

export const Inner = styled.div`
  display: flex;
  padding: 70px 45px;
  flex-direction: column;
  max-width: 815px;
  margin: auto;
`;

export const Title = styled.h1`
  font-size: 33px;
  line-height: 1.1;
  margin-top: 0;
  margin-bottom: 8px;
  color: white;
  text-align: center;
`;

export const Item = styled.div`
  color: white;
  margin: auto;
  margin-bottom: 10px;
  max-width: 728px;
  width: 100%;
  &:first-of-type {
    margin-top: 3em;
  }
  &:last-of-type {
    margin-bottom: 0;
  }
`;

export const Header = styled.div`
  display: flex;
  flex-direction: space-between;
  cursor: pointer;
  border: 1px solid #ce7bb0;
  border-radius: 8px;
  box-shadow: #ce7bb0;
  margin-bottom: 1px;
  font-size: 22px;
  font-weight: normal;
  background: #ce7bb0;
  padding: 0.8em 1.2em 0.8em 1.2em;
  user-select: none;
  align-items: center;
`;

export const Body = styled.div`
  font-size: 18px;
  font-weight: normal;
  line-height: normal;
  background: #ce7bb0;
  margin: 0.5rem;
  border-radius: 8px;
  box-shadow: #ce7bb0;
  white-space: pre-wrap;
  user-select: none;
  overflow: hidden;
  &.open {
    max-height: 0;
    overflow: hidden;
  }
  span {
    display: block;
    padding: 0.8em 2.2em 0.8em 1.2em;
  }
`;

Live mode https://codesandbox.io/s/accordion-with-no-context-passing-down-properties-directly-t9ktpi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment