最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Is there any practical way to call `React.createContext()` within a component? - Stack Overflow

programmeradmin0浏览0评论

Let's say I want to create a UI ponent for an "accordion" (a set of collapsible panels). The parent ponent controls the state of which panels are open, while the child panels should be able to read the context to determine whether or not they're open.

const Accordion = ({ children }) => {
  const [openSections, setOpenSections] = useState({})

  const isOpen = sectionId => Boolean(openSections[sectionId])

  const onToggle = sectionId => () =>
    setOpenSections({ ...openSections, [sectionId]: !openSections[sectionId] })

  const context = useMemo(() => createContext(), [])
    // Can't tell children to use *this* context

  return (
    <context.Provider value={useMemo(() => ({ isOpen, onToggle }), [isOpen, onToggle])}>
      {children}
    </context.Provider>
  )
}

const AccordionSection = ({ sectionId, title, children }) => {
  const { isOpen, onToggle } = useContext(context)
    // No way to infer the right context

  return (
    <>
      <button onClick={onToggle(sectionId)}>{isOpen(sectionId) ? 'Close' : 'Open'}</button>
      {isOpen && children}
    </>
  )
}

The only way I could think of acplishing this would be to have Accordion run an effect whenever children changes, then traverse children deeply and find AccordionSection ponents, while not recursing any nested Accordion ponents -- then cloneElement() and inject context as a prop to each AccordionSection.

This seems not only inefficient, but I'm not even entirely sure it will work. It depends on children being fully hydrated when the effect runs, which I'm not sure if that happens, and it also requires that Accordion's renderer gets called whenever deep children change, which I'm not sure of either.

My current method is to create a custom hook for the developer implementing the Accordion. The hook returns a function which returns the isOpen and onToggle functions which have to manually be passed to each rendered AccordionSection. It works and is possibly more elegant than the children solution, but requires more overhead as the developer needs to use a hook just to maintain what would otherwise be state encapsulated in Accordion.

Let's say I want to create a UI ponent for an "accordion" (a set of collapsible panels). The parent ponent controls the state of which panels are open, while the child panels should be able to read the context to determine whether or not they're open.

const Accordion = ({ children }) => {
  const [openSections, setOpenSections] = useState({})

  const isOpen = sectionId => Boolean(openSections[sectionId])

  const onToggle = sectionId => () =>
    setOpenSections({ ...openSections, [sectionId]: !openSections[sectionId] })

  const context = useMemo(() => createContext(), [])
    // Can't tell children to use *this* context

  return (
    <context.Provider value={useMemo(() => ({ isOpen, onToggle }), [isOpen, onToggle])}>
      {children}
    </context.Provider>
  )
}

const AccordionSection = ({ sectionId, title, children }) => {
  const { isOpen, onToggle } = useContext(context)
    // No way to infer the right context

  return (
    <>
      <button onClick={onToggle(sectionId)}>{isOpen(sectionId) ? 'Close' : 'Open'}</button>
      {isOpen && children}
    </>
  )
}

The only way I could think of acplishing this would be to have Accordion run an effect whenever children changes, then traverse children deeply and find AccordionSection ponents, while not recursing any nested Accordion ponents -- then cloneElement() and inject context as a prop to each AccordionSection.

This seems not only inefficient, but I'm not even entirely sure it will work. It depends on children being fully hydrated when the effect runs, which I'm not sure if that happens, and it also requires that Accordion's renderer gets called whenever deep children change, which I'm not sure of either.

My current method is to create a custom hook for the developer implementing the Accordion. The hook returns a function which returns the isOpen and onToggle functions which have to manually be passed to each rendered AccordionSection. It works and is possibly more elegant than the children solution, but requires more overhead as the developer needs to use a hook just to maintain what would otherwise be state encapsulated in Accordion.

Share Improve this question asked Jan 30, 2020 at 17:25 M MillerM Miller 5,66210 gold badges48 silver badges69 bronze badges 6
  • 3 Not sure why you create the context in the parent and not outside of it... (or why wrap it with useMemo) – Sagiv b.g Commented Jan 30, 2020 at 17:29
  • 1 I agree with @Sagivb.g. Why you want to keep it inside the ponent? Just move it outiside, export it and import it by other ponents – Vencovsky Commented Jan 30, 2020 at 17:36
  • context is not just for parent to children, can be used anywhere. just think it as a state wrapper. – Bhojendra Rauniyar Commented Jan 30, 2020 at 17:37
  • The point is that there's nothing globally unique, like each Accordion maintains its own state of which sections are open. Multiple instances of the accordion require their own context. But the nature of React context is that you can't just instantiate new context instances on the fly when a "root" ponent gets rendered. – M Miller Commented Jan 31, 2020 at 18:03
  • I think this is just not something that can be practically done in React. For example, Redux requires you to specify the name of the store if you want more than Redux store in the app, on every connected ponent which reads from the non-default store. So I think the same methodology is required, i.e. if a ponent serves multiple contexts, the children need to provide the unique identifier, rather than just having magic access to the context of its nearest provider parent. Made a POC here: codesandbox.io/s/sweet-hofstadter-b42iy – M Miller Commented Jan 31, 2020 at 19:51
 |  Show 1 more ment

1 Answer 1

Reset to default 9

React.createContext will return an object that holds 2 ponents:

  1. Provider
  2. Consumer

These 2 ponents can share data, the Consumer can "grab" the context data from the nearest Provider up the tree (or use the useContext hook instead of rendering a Consumer).

You should create the context object outside the parent ponent and use it to render a Consumer inside your children ponents (or use the useContext hook).

Simple example:

const myContext = createContext();

const Accordion = ({ children }) => {
  // ...
  return (
    <myContext.Provider value={...} >
      {children}
    </myContext.Provider>
  )
}


const AccordionSection = (...) => {
  const contextData = useContext(myContext);
  // use the data of your context as you wish
  // ...
}

Note that i used the useContext hook instead of rendering the Consumer, its up to you if you want to use the hook or the Consumer.

You can see more examples and get more details from the docs

发布评论

评论列表(0)

  1. 暂无评论