React 18 is out!

Our latest major version includes out-of-the-box improvements like automatic batching, new APIs like startTransition, and streaming server-side rendering with support for Suspense.

3 Likes

React 18 works like 17 out of the box. The questions related to Meteor:

  1. How does it affect react-meteor-data? Any new features it can take advantage of?
  2. Does the new SSR api + Suspense work with Meteor SSR + dynamic import?

To this it should be mostly fine. There seems to be an issue if you use it in a specific way:

2 Likes

We are now encountering a problem wherein a page is being redirected to another page in SSR. This happens when the app is busy or with multiple simultaneous creation of SSR pages. We have tens of thousands of dynamic pages that can be deactivated. When a page is deactivated, it will be redirected (301) to another page.

My current hunch is that the context variable used during rendering is leaking from one page to another. We use the context variable for the redirect using meteor’s server-render package: Sink.redirect()

This does not happen on React 17.

P.S. This is still a hunch as we cannot replicate it in our development environment.

[Update] It was not about an issue with context. Seems we have an implementation not compatible with React 18 that we need to identify

Does SSR work nice now or what’s the dealio?

Supposedly, SSR (with dynamic import) will work with React 18 using Suspense + React Router 6 (no confirmation yet for Meteor)

Meteor SSR with dynamic import works with React 16.8 to React 17 using React Loadable (forked for meteor) + React Router 5

Meteor’s server environment is not a default node runtime, at least if you use meteor-collections and therefore fibers. While this is usually not an issue, it is a problem when using libraries that are not aware of fibers / co-routines.

React and probably other frontend-frameworks are not made with such environments in mind. So if you use fiber-code, e.g. collections inside your react-render function, unexpected things can happen.

I mention this, because this was indeed a problem with an earlier version of react: React.createContext with SSR leads to concurrency problems in environments with co-routines ¡ Issue #13854 ¡ facebook/react ¡ GitHub. It has been fixed coincidentally in react 16, but its very likely that problems like this will happen again with newer react versions (have not checked react17). I have not verified if your problem is related to that, but it very looks like this.

1 Like

That was also my initial hunch. Context + meteor fibers + React Concurrency looks like a recipe for this to happen.

What I did to test this out is to use the path of the request as the unique “key” to the context so any parallel execution will not overwrite the context of another path. It still worked on React 17 but I still get conflicting context in React 18.

That is why I concluded that the context implementation might not be an issue but just a side effect.

My next hunch is react-router’s redirect. V6 of react-router deprecated the Redirect component, specifically redirects on initial render. It mentioned in its documentation that this pattern is not to be supported by future react version (v18) although with not enough details

It looks like that the support for parallel render handling for context is now currently built just for suspense.

2 Likes

Hi all,

From the React 18 announcement a couple of things stood out: useSyncExternalStore and Server Components.

Server Components aren’t in the release but it seems like a cool concept. It is fun to watch the React team ooh and ahh as user inputs are reflected immediately on an entire site. Similarly the async fetch on the server and identical code working on client and server seems very Meteorish. I am not sure they will end up being that useful but it is another paradigm for solving waterfall problems.

For useSyncExternalStore, I have played with a quick implementation of how it could work with Meteor. It seems too simple so I wonder if others would be kind enough to point out what might be wrong or inefficient about doing it this way. Trying it out with a subscribe to display a list from Mongo, the component rendered twice which seems good. Even rendering 3 times would have been reasonable as the subscribe could not be ready on the first render and the minimongo fetch isn’t necessarily ready with the subscribe. There were extra reads of the store but I think that is the expected behaviour from the documentation of useSyncExternalStore. Anyway, this is my implementation:

const useReactiveStore = (reactiveFn, dependencies = []) => {
  const storeRef = useRef(null);
  const observersRef = useRef([]);
  const computation = useRef(null);
  const previousDependencies = useRef(dependencies);
  const subscribe = (cb) => {
    observersRef.current.push(cb);
    return () => observersRef.current.splice(observersRef.current.indexOf(cb), 1);
  }
  const get = () => storeRef.current;
  
  const dependenciesChanged = dependencies.some((d, ii)=> d !== previousDependencies.current[ii]);

  if (!computation.current || computation.current?.stopped || dependenciesChanged) {
    computation.current?.stop();
    computation.current = Tracker.autorun(() => {
      storeRef.current = reactiveFn();
      observersRef.current.forEach(cb => cb());
    });
    previousDependencies.current = dependencies;
  }

  // Clean up on unmount.  Maybe this should clear some of the refs also.
  useEffect(() => {
    return () => {
      computation.current?.stop();
    }
  }, []);

  return useSyncExternalStore(subscribe, get)
}

update: Reading and writing ref.current needs to be done inside useEffect not directly in the render. Refs are mutable stores. Moving the autorun inside useEffect means deferring reading Minimongo until after the first render.

1 Like

We are testing the package that you mentioned in that thread and seems it is working. We are still monitoring but so far we are not catching any issues in production

[Update]
We are still encountering cases of wrong context data. Lesser than before but it is still happening

@rjdavid Do you use renderToReadableStream function on server?

@minhna I have not moved to the new API, yet. I am just updating the React version so I am still using the React 17 API.

I tried v18, it works with renderToString as previous versions. But I have no idea how to use renderToReadableStream function as the document: https://reactjs.org/docs/react-dom-server.html#rendertoreadablestream

Do you implement SSR in your project with context?

what “context” do you mean? React Context?

Yes, React Context with SSR.

Yes, It works with React Context.

1 Like

In our case, the React Context with SSR is not working on parallel access i.e. multiple sessions rendering with context at the same time. The context values are leaking to other pages. It worked on single renders but not on high traffic parallel access

I haven’t tested that case yet. Making SSR works with complex site structure which has bunch of components and pub/sub, method calls, effects… is hard. It also consumes big resources (CPU/Ram) so I use cache to reduce the SSR works. It helps alot.