S
SolidJS13mo ago
Sem

How to handle hydration on client side?

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?
1 Reply
Sem
SemOP13mo ago
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;
}
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 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;
}
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 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;
}
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>
</>
);
}
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>
</>
);
}
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 (
...
);
}
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 (
...
);
}
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
Want results from more Discord servers?
Add your server