Repository: tanstack/query
File: packages/react-query-next-experimental/src/HydrationStreamProvider.tsx (TypeScript)

Code before:

      // Flush stream
      stream.length = 0

      const html: Array<string> = [
        `window[${idJSON}] = window[${idJSON}] || [];`,
        `window[${idJSON}].push(${serializedCacheArgs});`,
      ]
      return (
        <script
          key={count.current++}
          dangerouslySetInnerHTML={{
            __html: html.join(''),
          }}
        />
      )
    })
    // </server stuff>

    // <client stuff>
    const onEntriesRef = React.useRef(props.onEntries)
    React.useEffect(() => {
      onEntriesRef.current = props.onEntries
    })

    // Setup and run the onEntries handler on the client only, but do it during
    // the initial render so children have access to the data immediately
    // This is important to avoid the client suspending during the initial render
    // if the data has not yet been hydrated.
    // @ts-expect-error window isn't defined on the server
    if (typeof window !== 'undefined' && !window[id]?.initialized) {
      const win = window as any;
      // Client: consume cache:
      const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {
        const entries = serializedEntries.map((serialized) => transformer.deserialize(serialized));
        onEntriesRef.current(entries);
      };

      const winStream: Array<Serialized<TShape>> = win[id] ?? [];

      onEntries(...winStream);

      win[id] = {
        initialized: true,
        push: onEntries,
      };
    }
    // </client stuff>

    return (
      <context.Provider value={{ stream, id }}>

Review comment (bug):
I'm not super on top of the `react-query-next-experimental` library so not sure if use cases and edge cases align, but, I thought I'd note that it might be worth checking out this comment (and implementation) in the `HydrationBoundary` for how we handle hydration there: https://github.com/TanStack/query/blob/main/packages/react-query/src/HydrationBoundary.tsx#L35-L49

Short version, what we do there is that if data is not already in the cache, we hydrate in render, if it is, we hydrate in an effect so we don't cause side effects inside of render.

I'm not sure how likely this is to happen with the streaming library, if it can at all, but I thought I'd mention it.

Code after:
        .map((entry) => JSON.stringify(entry))
        .join(',')

      // Flush stream
      stream.length = 0

      const html: Array<string> = [
        `window[${idJSON}] = window[${idJSON}] || [];`,
        `window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,
      ]
      return (
        <script
          key={count.current++}
          dangerouslySetInnerHTML={{
            __html: html.join(''),
          }}
        />
      )
    })
    // </server stuff>

    // <client stuff>
    // Setup and run the onEntries handler on the client only, but do it during
    // the initial render so children have access to the data immediately
    // This is important to avoid the client suspending during the initial render
    // if the data has not yet been hydrated.
    if (!isServer) {
      const win = window as any
      if (!win[id]?.initialized) {
        // Client: consume cache:
        const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {
          const entries = serializedEntries.map((serialized) =>
            transformer.deserialize(serialized),
          )
          props.onEntries(entries)
        }

        const winStream: Array<Serialized<TShape>> = win[id] ?? []

        onEntries(...winStream)

        win[id] = {
          initialized: true,
          push: onEntries,
        }
      }
    }
    // </client stuff>

    return (
      <context.Provider value={{ stream, id }}>