#How to handle hydration on client side?

19 messages · Page 1 of 1 (latest)

timid lagoon
#

I'm using localstorage to save my local items. Component renders on server if I press F5 and throws an hydration error and it's logically, because server component doesn't have an elements from client sides and it's handled by isServer.

But main question: how do I handle this hydration error? How can I manage to synchronize states between server & client?

#

That's how my hook looks like:


export function createLocalProjectStore() {
  const [projects, setProjects] = createStore<LocalStoreProject[]>([]);
  onMount(() => {
    if (isServer) return;
    const localState = isServer
      ? "[]"
      : localStorage.getItem("projects") || "[]";

    console.log("sets", isServer, localState);
    setProjects(JSON.parse(localState));
  });

  createEffect(() => {
    if (isServer) return;
    localStorage.setItem("projects", JSON.stringify(projects));
  });

  return [projects, setProjects] as const;
}
#

Maybe there is some way to mark only one route not to use SSR?

#

The only way I make it works is to setTimeout for getting items from localstorage, to make items synchronized between each one. But it looks like a very tricky approach to this situation and I'm sure there is more appropriate way to do this kind of stuff

timid lagoon
#

tried. Not solves an issue

#

For now best and working way is to wrap content in onMount in setTimeout

#

Such way worked for me

#

That's all code you need to reproduce this functionality. Here is my current build:

export function createLocalProjectStore() {
  const [projects, setProjects] = createStore<LocalStoreProject[]>([]);
  const [isInit, setIsInit] = createSignal(true);

  if (!isServer) {
    onMount(() => {
      setTimeout(() => {
        const localState = localStorage.getItem("projects") || "[]";

        setProjects(JSON.parse(localState));
        setIsInit(false);
      });
    });
    createEffect(() => {
      if (isServer || isInit()) return;
      localStorage.setItem("projects", JSON.stringify(projects));
    });
  }

  return [projects, setProjects, isInit] as const;
}
#

That's the only way I handle it works

#

Without timeout clientslide renders more items than serverside and hydration error appears

#

Not even onMount help with it

timid lagoon
#

I changed it like this:

export function createLocalProjectStore() {
  if (isServer) {
    return [[], ()=>{}] as const
  }
  
  const [projects, setProjects] = createStore<LocalStoreProject[]>([]);
  const [isInit, setIsInit] = createSignal(true);

  if (!isServer) {
    onMount(() => {
      const localState = localStorage.getItem("projects") || "[]";

      setProjects(JSON.parse(localState));
      setIsInit(false);
    });
    createEffect(() => {
      if (isServer || isInit()) return;
      localStorage.setItem("projects", JSON.stringify(projects));
    });
  }

  return [projects, setProjects, isInit] as const;
}

and start receive hydration errors

#

So this is not a solution

#

And the route where I'm using it:


const [projects, setProjects] = createLocalProjectStore();

function createProject() {
  setProjects((projects) => [
    ...projects,
    {
      id: Math.random().toString(36).slice(2, 9),
      name: "New Project",
      dateCreated: Date.now(),
      dateModified: Date.now(),
    },
  ]);
}

function NoProjects() {
  return (
    <Flex direction="column" class="center w-full">
      <h2>Looks like there is no projects yet</h2>
      <Button onclick={createProject}>Let's create one!</Button>
    </Flex>
  );
}

export default function ProjectList() {
  const dateFormat = d3.utcFormat("%d/%m/%Y %H:%M");
  return (
    <>
      <Show when={projects.length < 1}>
        <NoProjects />
      </Show>
      <Show when={projects.length > 0}>
        <Flex class="w-full" direction="column">
          <Button onclick={createProject} type="primary" class="m-xxs mr-auto">
            Create new project
          </Button>
          <Flex direction="column" class="p-xxs w-full" gap="xs">
            <For each={projects}>
              {(project) => (
                <Button
                  href={`/p/${project.id}`}
                  type="transparent"
                  class="w-full"
                >
                  <Flex class="w-full" gap="xl">
                    <p>{project.name}</p>
                    <p class="ml-auto">
                      {dateFormat(new Date(project.dateModified))}
                    </p>
                  </Flex>
                </Button>
              )}
            </For>
          </Flex>
        </Flex>
      </Show>
    </>
  );
}

timid lagoon
#

Finally - figured out that I should use useRouteData correct.

Docs helped

#

Correct way:

export function routeData() {
  const projects = createLocalProjectStore();
  return projects;
}

function createProject() {
  const [projects, setProjects] = useRouteData<typeof routeData>();
  setProjects((projects) => [
    ...projects,
    {
      id: Math.random().toString(36).slice(2, 9),
      name: "New Project",
      dateCreated: Date.now(),
      dateModified: Date.now(),
    },
  ]);
}

function NoProjects() {
  return (
   ...
  );
}

export default function ProjectList() {
  const [projects, setProjects] = useRouteData<typeof routeData>();
  const dateFormat = d3.utcFormat("%d/%m/%Y %H:%M");
  const data = useRouteData();
  return (
   ...
  );
}
timid lagoon
#

Yeah I have one. This is just temporary decision for this case

#

Yea, totally agree. But the main point was to use routeData

#

Which is kinda hidden in docs