MichalZalecki / react-portal-universal

Generic wrapper for React's createPortal allowing for rendering portals on the server
ISC License
65 stars 13 forks source link

Support concurrent rendering with SC-inspired API #11

Open jesstelford opened 5 years ago

jesstelford commented 5 years ago

Fixes #5, and I think #2.

Heavily inspired by styled-components, this PR completely changes the API, but should make things concurrent-safe:

// CLIENT
import { UniversalPortal, prepareClientPortals } from  "react-portal-universal";

const Head = ({ children }) => (
  // pass selector for a document.querySelector
  // instead of a DOM node like in createPortal
  <UniversalPortal selector="head">{children}</UniversalPortal>
);

class App extends React.Component {
  render() {
    return (
      <article>
        <Head>
          <title>Hello, World!</title>
          <meta name="description" content="Lorem ipsum..." />
        </Head>
        <h1>Hello, World!</h1>
        <p>
          Lorem ipsum sit doloret um.
        </p>
      </article>
    );
  }
}

// remove static markup and allow React
// to render only actual components
prepareClientPortals();

ReactDOM.render(<App />, document.querySelector("#root"));
// SERVER

const { ServerPortal } = require("react-portal-universal/server");

const portals  = new ServerPortal();
const element  = portals.collectPortals(<App />);
const body     = ReactDOMServer.renderToString(element));
const template = fs.readFileSync(path.resolve("build/index.html"), "utf8");
const html     = template.replace("<div id=\"root\"></div>", `<div id="root">${body}</div>`);
const markup   = portals.appendUniversalPortals(html);

res.status(200).send(markup);

Notes:

  1. I've published this as @jesstelford/react-portal-universal for testing out
  2. I have no idea how to update the tests, so I haven't touched them.
  3. Feedback is appreciated! <3
jesstelford commented 5 years ago

Here's an example of using it with next.js: https://github.com/zeit/next.js/tree/canary/examples/with-portals-ssr

davidpatrick commented 4 years ago

Do you either of you need assistance in getting this pushed through?

wmertens commented 4 years ago

EDIT: oh duh, the below is exactly what you do 🤦‍♂

Why not use context to pass the per-render portal state around?

Wrap the app with the Context.Provider and that removes the new ServerPortal() call.

export const PortalContext = React.createContext()

const UniversalPortalProvider = ({children, store}) => (
  <PortalContext.Provider value={store}>
    {children}
  </PortalContext.Provider>
)

if there's a store, it means it got passed by the SSR, and the portals use it, otherwise they're on the client.

svobik7 commented 3 years ago

Will this PR be merged? Can I help?

MichalZalecki commented 3 years ago

@svobik7 Thank you for your interest. I don’t actively maintain this so if you want a quick fix feel free to fork it and in the meantime I try to find some time to look into that. Sine I neglected this project I’ll need a moment or two to get back on track

If you just make sure this actually solves your problem that’s great