near / bos-web-engine

Improved execution layer for NEAR decentralized frontend components
https://roc-docs.near.dev/
25 stars 6 forks source link

Support passing children to sandboxed Components #301

Open calebjacob opened 4 months ago

calebjacob commented 4 months ago

It's a very common React pattern to pass children into a component. You'd set up your component to take in a children: ReactNode prop like so:

import type { ReactNode } from 'react';
import s from './styles.module.css';

type Props = {
  children?: ReactNode;
};

function Parent({ children }: Props) {
  return (
    <div className={s.wrapper}>
      {children}
    </div>
  );
}

export default Parent as BWEComponent<Props>;

You would then expect to use <Parent> like so:

import Parent from './Parent';
import s from './styles.module.css';

function Root() {
  return (
    <div className={s.wrapper}>
      <Parent>
        <p>Hello!</p>
      </Parent>
    </div>
  );
}

export default Root as BWEComponent;

However, this exact syntax isn't currently supported due to all props needing to be passed through an explicit props object. This will be resolved here: https://github.com/near/bos-web-engine/issues/261

Screen Shot 2024-02-21 at 10 36 30 AM

Until that issue is resolved, you can use this syntax - and everything will work if we only pass in native DOM components (eg: p, div, etc):

<Parent props={{
  children: <p>Hello!</p>
}} />

However, it's very common that we'd need to pass in our own component as a child. Let's set up a new component:

import s from './styles.module.css';

function Child() {
  return (
    <div className={s.wrapper}>
      <p>I am a child</p>
    </div>
  );
}

export default Child as BWEComponent;

Then we update our root component to pass <Child> to <Parent> like so:

import Parent from './Parent';
import Child from './Child';
import s from './styles.module.css';

function Root() {
  return (
    <div className={s.wrapper}>
      <Parent props={{
        children: (
          <div>
            <Child />
            <p>Hello there...</p>
          </div>
        )
      }} />
    </div>
  );
}

export default Root as BWEComponent;

The engine throws the following error saying Child is an unrecognized component:

Screen Shot 2024-02-21 at 10 43 35 AM

This is a very common use case when building out more robust React applications. For a more concrete example, imagine you needed to build a <Theme> component that can be used to wrap multiple pages/components and provide CSS variables to all of its children. In an ideal world, the BOS engine would support the following:

import type { ReactNode } from 'react';
import s from './styles.module.css';

type Props = {
  children?: ReactNode;
};

function Theme({ children }: Props) {
  return (
    <div className={s.wrapper}>
      {children}
    </div>
  );
}

export default Theme as BWEComponent<Props>;
.wrapper {
  --primary-color: red;
}
import Theme from './Theme';
import Child from './Child';
import s from './styles.module.css';

function PageOne() {
  return (
    <Theme>
      <p className={s.text}>Hello!</p>
      <Child />
    </Theme>
  );
}

export default PageOne as BWEComponent;
.text {
  color: var(--primary-color);
}
andy-haynes commented 3 months ago

This is working for trusted Components since the introduction of the Babel plugin. Sandboxed Components still have some difficulty, since <Component /> is a stub and not an actual React Component. It doesn't look like much work to get it in parity though.

import Child from './Child';
import Parent from './Parent';
import s from './styles.module.css';

function Root() {
  return (
    <div className={s.wrapper}>
      <Parent bwe={{trust:{mode:'trusted'}}}>
        <Child />
        <p>Hello!</p>
      </Parent>

    </div>
  );
}

export default Root as BWEComponent;
mpeterdev commented 3 months ago

candidate to kick out to P1