astoilkov / use-local-storage-state

React hook that persists data in localStorage
MIT License
1.17k stars 41 forks source link

add defaultServerValue property #74

Closed BatuhanTopcu closed 1 week ago

BatuhanTopcu commented 2 weeks ago

Reason

we have a modal to defaults to open if local storage value does not exists. If value for opened before is undefined we want to open modal. using defaultValue to set key to false causes modal to flashes on top that. we tried setting different values for client and server in defaultValue using it as function.

const [state, setState] = useLocalStorageState(
    'key',
    {
      defaultValue() {
        if (isServer) {
          return "server default value";
        }
        return "client default value";
      },
    },
  );

code above prevents modal first opening then closing as flash but now causes hydration errors. fix is using getServerSnapshot to differ client and server defaults. getServerSnapshot function runs during SSR and first hydration so it prevents that errors. See docs

What is done

astoilkov commented 2 weeks ago

Hey, thanks for your contribution!

Is it ok for me to ask some questions? I'm not sure I understand. Can you give me a code snippet example that doesn't work with the current API that will work if there is a getServerSnapshot option?

I'm just trying to imagine your codebase and I can't. For example, I guess I will write the following code if I will need to do what I understand you are describing:

const [showModal, setShowModal] = useLocalStorageState('show-modal', {
  defaultValue: false
})

if (!showModal) {
  return null
}

return <div>modal</div>

How should I change the example below so it now needs a new option in order to work property (or you can just show a different example, if that's easier)?

Thanks in advance!

astoilkov commented 2 weeks ago

Also, I see two people liked the PR — @kadirgombel and @damla.

Do you have the same use case as well? Can you explain your need for such a property?

Thanks!

BatuhanTopcu commented 2 weeks ago

Hello, they are my coleagues.

Example:

const showWelcomeModal, setShowWelcomeModal] = useLocalStorageState('welcome-modal', {
  defaultValue: true // we want to show welcome if value does not exist in local storage
})

if (!showModal) {
  return null
}

return <div>welcome</div>

code below has an issue server always shows modal with SSR response but if welcome-modal exists in local storage when client takes over it hides modal. so when I tried to change default value on defaultValue based on environment it fixes that but then throws hydration error.

Thank you!

astoilkov commented 2 weeks ago

Sorry, I still don't understand. I will try to break this down and give you a CodeSandbox that you can edit so we are on the same page.

Can you edit this CodeSandbox so you make the modal div flicker?

Also, do you know that you can conditionally give a different defaultValue as you tried but using useSyncExternalStore() hook?:

const isServerOrHydrating = useSyncExternalStore(
  () => {
    return () => {};
  },
  () => false,
  () => true
);
const [showWelcomeModal, setShowWelcomeModal] = useLocalStorageState('welcome-modal', {
      defaultValue() {
        if (isServerOrHydrating) {
          return "server default value";
        }
        return "client default value";
      },
})

I hope you understand me. I want to understand it before merging it.

BatuhanTopcu commented 2 weeks ago

Hello, yep there is no problem. https://codesandbox.io/p/devbox/suspicious-meninsky-forked-qf2pgw I added two cases:

1) User saw onboarding modal before, so shouldn't be saw it again If I set defaultValue: false and user's first enter, its written to localStorage in getSnapshot side of this lib so user can't see modal at all. If I set defaultValue: true and if user entered before, modal is rendered on server then removed from client (Flicker issue I meant)

2) I did not know isServerOrHydrating hack you said It looked promised but has the same issue

const [showWelcomeModal] = useLocalStorageState("case-2", {
    defaultValue: () => {
      if (isServerOrHydrating) return false;
      return true;
    },
 });

this will work in users recurrent enters to page without flicker so it is fixing that issue. but in the first entrance defaultValue: false written to localStorage while hydration so user not see modal at all.

I tried to simulate these states with useLayoutEffect but not sure that works, you can remove them and set local storage by yourself

I hope it was understandable; thanks for your time.

astoilkov commented 2 weeks ago

Nice! I think I got it.

So what you are trying to accomplish is this — https://codesandbox.io/p/devbox/suspicious-meninsky-forked-yz8zfr?workspaceId=d4f0087a-bf2e-4c09-a4d6-c87fd0d77ed8, right?

The example shows how to show the modal but not render it on server or while hydrating to avoid flicker and hydration errors. If you manually call localStorage.setItem('show-welcome-modal, 'false')`, this will still not render the modal on the server or while hydrating and won't show it on the client as well.

That's it right?

BatuhanTopcu commented 2 weeks ago

Hello again,

I can't access the codesandbox but you are right, If I use

const [showWelcomeModal] = useLocalStorageState("case-2", {
    defaultValue: () => {
      if (isServerOrHydrating) return false;
      return true;
    },
 });

there will be no flicker and hydration error but issue with this is defaultValue sets given value to localStorage so user can't see the modal at all

astoilkov commented 2 weeks ago

Sorry, here's the link again, it should work now — https://codesandbox.io/p/devbox/suspicious-meninsky-forked-yz8zfr?workspaceId=d4f0087a-bf2e-4c09-a4d6-c87fd0d77ed8.

BatuhanTopcu commented 2 weeks ago

yep it's what I accomplish thank you! so should I use this hackish solution or can we merge this PR? thanks a lot!

astoilkov commented 1 week ago

I'll merge the PR. After I took the time to understand it, it seems like a good idea.

Thanks for your contribution!

I will try to make a release soon.

BatuhanTopcu commented 1 week ago

Thanks a lot for spending your time, This is my first contribution to open-source, and that felt awesome!

astoilkov commented 1 week ago

The release is ready.

It was fun for me as well! Your first contribution is really well done, keep the good work!