react-archer: My ArcherElements are defined in another component, so I am getting "Could not find "unregisterChild" in the context of "

I have a component that looks a little something like:

<MyApp>
  <ArcherContainer>
    <ThirdPartyComponent itemRenderer={() => (<ContainsArcherElement />)}
  </ArcherContainer>
</MyApp>

And then ContainsArcherElement is similar to:

const ContainsArcherElement = () => (
 <SomethingElse>
   <ArcherElement /> // with all the right props
 </SomethingElse>
)

But I’m getting the full Could not find "unregisterChild" in the context of <ArcherElement>. Wrap the component in a <ArcherContainer>. but it should be wrapped, just not immediately.

Is there a way around this, and is it a bug or a feature?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 20 (15 by maintainers)

Most upvoted comments

@AncientSwordRage Context is not passed through into your render component, and React has no smart way to make that possible. The container could expose the context and you could manually pass it, but I don’t think that just works off the top of my head.

@AncientSwordRage Sorry, but I can only be so nice sometimes. When someone tells you that you’re confused and an idiot, and then goes on to reference a pull request that you contributed to as evidence, I’m going to tell them off. Engineers and their egos know no bounds.

The feature has to be added, but I can look into it later today.

@gurkerl83 Don’t talk down to me. If you actually knew what you were talking about, then you would know that React Redux is not built with context. I know that because Mark asked me to add my input on the v6.0 release, where they moved from subscriptions to context. That was a disaster, as it added back the old zombie children issues with legacy context, and they eventually removed it.

The authors are sorry that changes like the use of Children.only were built-in, but that was not a breaking change at all, the reasons are different when reading through the ticket #99.

No, they weren’t.

Please do not confuse the reducers parts of my rewrite with redux. There is no use of Redux at all. Reducers are a pretty new React feature.

You seem to be the only one that is confused.

@AncientSwordRage The importing/exporting is correct. This is going to sound obnoxious but I did talk to Dan Abramov about this API design before hooks came out, and I was exporting the context consumer like this in all the examples I gave.

@pierpo The withContainerContext API looks good. You can just export that itself as part of the public API, so you don’t have to pass refs. This is all valid JS.

I had a quick look at the source and it seems as though the context is exported and reimported. I’ll need to double check but I don’t see why that would impact this use case?

Where is it exported/reimported? It is indeed exported but only for the tests of the library.

@jfo84 is right, the only way I can think of is indeed exposing the context, but I have no idea how it would work out.

This:

  render() {
    const SvgArrows = this._computeArrows();

    return (
      <ArcherContainerContextProvider
        value={{
          registerTransitions: this._registerTransitions,
          unregisterTransitions: this._unregisterTransitions,
          registerChild: this._registerChild,
          unregisterChild: this._unregisterChild,
        }}
      >
        <div style={{ ...this.props.style, position: 'relative' }} className={this.props.className}>
          <svg style={this._svgContainerStyle()}>
            <defs>{this._generateAllArrowMarkers()}</defs>
            {SvgArrows}
          </svg>

          <div style={{ height: '100%' }} ref={this._storeParent}>
            {this.props.children}
          </div>
        </div>
      </ArcherContainerContextProvider>
    );
  }

should become… this?

  withContainerContext = (children: React$Node) => {
    return (
      <ArcherContainerContextProvider
        value={{
          registerTransitions: this._registerTransitions,
          unregisterTransitions: this._unregisterTransitions,
          registerChild: this._registerChild,
          unregisterChild: this._unregisterChild,
        }}
      >
        {children}
      </ArcherContainerContextProvider>
    );
  };

  render() {
    const SvgArrows = this._computeArrows();

    return this.withContainerContext(
      <div style={{ ...this.props.style, position: 'relative' }} className={this.props.className}>
        <svg style={this._svgContainerStyle()}>
          <defs>{this._generateAllArrowMarkers()}</defs>
          {SvgArrows}
        </svg>

        <div style={{ height: '100%' }} ref={this._storeParent}>
          {this.props.children}
        </div>
      </div>,
    );
  }

so that you can do

<MyApp>
  <ArcherContainer ref={this.storeContainerRef}>
    {/* do a cleaner access with null checks, this is just a quick example */}
    <ThirdPartyComponent itemRenderer={() => (this.containerRef.current.withContainerContext(<ContainsArcherElement />))}
  </ArcherContainer>
</MyApp>

I think this would work 🤔 I haven’t tried it out, maybe I’m wrong.

But I’m not sure what the impact of the change above would have on ArcherContainer in general 🧐