leptos-rs / leptos

Build fast web applications with Rust.
https://leptos.dev
MIT License
15.31k stars 599 forks source link

porting over some react code to my project, having hydration issues #2584

Closed Upbolt closed 2 months ago

Upbolt commented 2 months ago

This is just a question, as there's probably a solution to this, but I'm not sure what's really going on here

I've got this React code:

// test.tsx
"use client";

import { useEffect, useState } from "react";

export function Foo() {
    const [foo, setFoo] = useState<HTMLDivElement | null>(null);
    const isFormControl = foo ? Boolean(foo.closest("form")) : true;

    console.log(isFormControl);
    useEffect(() => {
        console.log(isFormControl);
    }, [isFormControl]);

    return (
        <>
            <div ref={(node) => setFoo(node)}>hello world</div>
            {isFormControl && <div>hello world 2</div>}
        </>
    );
}

// HomePage.tsx
import { Foo } from "./test";

export default function HomePage() {
  return (
    <>
      {/* <form> */}
      <Foo />
      {/* </form> */}
      {/* <form> */}
      <Foo />
      {/* </form> */}
    <>
  );
}

Which I've ported like so:

#[component]
fn Foo() -> impl IntoView {
  let foo_ref = NodeRef::<html::Div>::new();

  let is_form_control = move || {
    if let Some(foo) = foo_ref.get()  {
      foo.closest("form").ok().flatten().is_some()
    } else {
      true
    }
  };

  logging::log!("{}", is_form_control());

  Effect::new(move |_| {
    logging::log!("{}", is_form_control());
  });

  view! {
    <div node_ref=foo_ref>"hello world"</div>
    <Show when=is_form_control>
      <div>"hello world 2"</div>
    </Show>
  }
}

#[component]
fn HomePage() -> impl IntoView {
  view! {
      // <form>
        <Foo />
      // </form>
      // <form>
        <Foo />
      // </form>
  }
}

The React code (using Next.js) works fine, but it doesn't seem like the case with the Leptos port because there are hydration issues 🤔

I've also tested this with Solid.js (with SolidStart) just to see if I'm thinking of the reactive system in the wrong way, but Solid also doesn't have any hydration issues

// test.tsx
import { Show, createEffect, createSignal } from "solid-js";

export default function Foo() {
    const [foo, setFoo] = createSignal<HTMLDivElement | null>(null);
    const isFormControl = () => (foo() ? Boolean(foo()!.closest("form")) : true);

    console.log(isFormControl());
    createEffect(() => {
        console.log(isFormControl());
    });

    return (
        <>
            <div ref={node => setFoo(node)}>hello world</div>
            <Show when={isFormControl()}>
                <div class="test">hello world 2?</div>
            </Show>
        </>
    );
}

// HomePage.tsx
import { Foo } from "./test";

export default function HomePage() {
    return (
        <>
          {/* <form> */}
          <Foo />
          {/* </form> */}
          {/* <form> */}
          <Foo />
          {/* </form> */}
        </>
    );
}

(albeit my Solid.js code doesn't seem to work properly when a form is detected, isFormControl always evaluates to false; at least there aren't any hydration issues 🤣)

So I'm wondering if I'm actually doing anything wrong here or if something more is going on?