#Initializing variable using localStorage

1 messages Β· Page 1 of 1 (latest)

solemn delta
#

I am currently working on an app that has a side menu, capable of opening and closing. By default I want it to be open so that new users can take note of its use and find their way around, but they have the option to close it for more space or when they get comfortable. Once it is closed, and the page is refreshed, I don't want it to be open again, but I want the menu to be closed. To do so, I was thinking of saving the menu state in localStorage and grabbing the value to initialize the menu.

I know with conventional react you can simply use localStorage like this

const isMenuOpen = Boolean(JSON.parse(localStorage.getItem("menu-open") || "true"));

In remix however I know localStorage can only be used in a useEffect hook.

const [isMenuOpen, setIsMenuOpen] = React.useState(true);
React.useEffect(() => {
  const menuState = Boolean(JSON.parse(localStorage.getItem("menu-open") || "true"));
  setIsMenuOpen(menuState);
}, []);

This leads to the menu being initialized as open, before changing to close (in the case described above). The UI experience is not the best. Is there a way to simply initialize the side menu state using the value in localStorage?

surreal moss
#

There’s no way to access LocalStorage server side

#

So you will have to delay the render until the component is on the browser

#

Or instead of using LocalStorage use cookies or a DB

solemn delta
#

Thanks for the quick reply. I already have a defer/Await tag used on this page, so I'll see how the whole thing will look like with the delayed menu 🀞

haughty chasm
#

Or use searchParams to keep open/close state. It will persist on refresh and remix has a hook for that that makes using that as easy as useState. It's useSearchParams.

Alternatively, you can use cookies to store your state as the server has access to it and it can be passed down from loader using useLoaderData

solemn delta
#

I think using searchParams might be the best way to go here. Thanks for the help, its greatly appreciated πŸ™

haughty chasm
surreal moss
#

What I would personally do it to keep it as a state and always start either closed or open, but if the user reloads return the the default one

solemn delta
solemn delta
#

@haughty chasm if I use the url params, then I will have to update the url every time the menu state is changed. Will updating the url params not cause a page reload?

surreal moss
#

let's say you want the default to be open, do let [isOpen, setIsOpen] = useState(true), if you want the default to be close use useState(false), this will render the same way on SSR and on hydration

surreal moss
haughty chasm
#

If you use Remix's useSearchParams hook, it won't

solemn delta
#

Even when I modify the url?

haughty chasm
#

Ah the loaders will be refetched but that's nomally not a bad thing. If you don't want that, use Remix's shouldRevalidate function

surreal moss
surreal moss
haughty chasm
#

By default, it won't reload the page, but will rerun loaders unless shouldRevalidate is used

surreal moss
#

if you use<Link>, <Form>, useNavigate or useSearchParams it will not cause a page reload, it will cause a client-side navigation

haughty chasm
solemn delta
#

Might need to try it out and play around with it a little. Sorry for all the small questions. I'm not familiar with frontend as a whole. I've only been doing this for 7weeks now πŸ˜…

surreal moss
#

I still think setting it to always be open or always be closed by default is way simpler, I'm not sure why you want to keep the menu open/closed state after the user reload

#

these kind of states are more temporal ones, that if the user close and open the app again it's expected to go back the the default

solemn delta
#

No, I already set it to default open. The problem is that once I get the actual state from localStorage (as of now), the menu will then close. So for the user, they will see the menu open and then close

surreal moss
#

my point is to don't use localStorage, leave it open, until the user manually close it

#

if you put the component in a parent route, the state will remain across navigations

#

so you can keep the state local to the component

solemn delta
#

I don't want that either. If the user is going to use the app often, they might not want to navigate the menu anymore and would prefer it close.

#

The menu is already in a parent route, the issue is when the page is reloaded

surreal moss
#

so if the user goes to another browser and login, it will have the same preference

solemn delta
#

Yeah, that could be a straightforward fix πŸ˜… I'll try to play around with everything that was mentioned here first. I don't really keep "user accounts" per-say, so that option won't really be easy to implement. Thanks for all the feedback once again πŸ™

solemn delta
#

Hey @haughty chasm, I used the cookie idea that you mentioned, but every time I click the toggle button, it will redirect me to the home page. My action() and loader() functions are both in the root.tsx file since my side menu is also there
loader:

export let loader: LoaderFunction = async ({ request }) => {
  ...
  ...
  // Read session from cookie
  const session = await getSession(request.headers.get("Cookie"));

  const isMenuCollapsed = (session.get("sdi-menu-collapsed") as string) || "false";
  console.log("loader", session.get("sdi-menu-collapsed"));
  // Create new cookie string
  const cookie = await commitSession(session);
  return json(
    {
      isMenuCollapsed: isMenuCollapsed,
    }
    // {
    //   headers: {
    //     "Set-Cookie": cookie,
    //   },
    // }
  );
};

action:

export let action: ActionFunction = async ({ request }) => {
  console.log("IN ACTION");
  const form = await request.formData();
  // Read session from cookie
  const session = await getSession(request.headers.get("Cookie"));

  const isMenuCollapsed = form.get("sdi-menu-collapsed") || "false";
  form.forEach((val: FormDataEntryValue, key: string) => {
    console.log("ACTION - FORM - Key", key);
    console.log("ACTION - FORM - Val", val);
  });
  console.log("action", isMenuCollapsed);
  // Create new cookie string
  session.set("sdi-menu-collapsed", isMenuCollapsed);
  const cookie = await commitSession(session);
  return json(
    {},
    {
      headers: {
        "Set-Cookie": cookie,
      },
    }
  );
};
#

Here is what I have for my form submission (toggle menu state)

  const loaderData = useLoaderData() as unknown as { isMenuCollapsed: string };
  const initialMenuState = Boolean(JSON.parse(loaderData.isMenuCollapsed || "false"));
    const [isSideBarCollapsed, setIsSideBarCollapsed] = React.useState(initialMenuState);
  const [formInstance] = Form.useForm();
  const submit = useSubmit();
  const handleSubmit = (form: FormInstance) => {
    form.submit();
    submit({ "sdi-menu-collapsed": JSON.stringify(!isSideBarCollapsed) }, { method: "post" });
  };

  return(
    <Form
              onClick={() => {
                formInstance.setFieldValue("sdi-menu-collapsed", JSON.stringify(isSideBarCollapsed));
                toggleSidebar();
                handleSubmit(formInstance);
                console.log("form click");
              }}
              form={formInstance}
            >
              {isSideBarCollapsed ? "Show" : "Hide"}
            </Form>
  )

I'm not sure why, but when I submit the form I always get redirected to the home page. Am I missing something?

surreal moss
#

use a fetcher.Form

#

that way the submit of the form will not cause a navigation

#

<Form> and <form> cause a navigation to the URL it's submitting the form data

solemn delta
#

Oh ok, got it. Let me try that πŸƒβ€β™‚οΈ

#

@surreal moss I would just need to replace <Form/> with <fetcher.Form/> right?

surreal moss
#

yeah

let fetcher = useFetcher()
return <fetcher.Form>inputs here</fetcher.Form>
solemn delta
#

Hmm... the problem still persists πŸ€”

balmy bone
#

I did this with some default filters on a dashboard, I used a cookie

solemn delta
#

Can you elaborate? Right now my cookies work fine, but every form submission redirects me to the home page, which is not the desired behavior

balmy bone
#

ah I see, that might be trickier

haughty chasm
#

You're using useSubmit ?

balmy bone
#

I think you could use something like zustand since this is really just UI state

solemn delta
haughty chasm
#
 const fetcher = useFetcher()

  return (
    <fetcher.Form method="post" action="/your-root-path">
      <button type="submit">
      Toggle Menu
      </button>
    </fetcher.Form>
  )

Short example

solemn delta
#

Nice! that worked!

#

I've been stuck on this for a while now, really appreciate the help. Should've tried cookies sooner

haughty chasm
#

My pleasure))