Cookies in Phoenix LiveView not taken into account only after the second F5 refresh

I am experiencing a strange thing.

I have a JavaScript code that checks whether you have a certain cookie name in your cookies in your browser. If no, a message is shown. When you click on a button the cookie is created and on a browser refresh the message is hidden.

It’s a simple code , nothing tricky.

I am not sending anything to the server after the click, everything is loaded from templates just once when I enter the page in the browser.

The cookie is set and when I exit the browser and start it again Phoenix somehow doesn’t register it. I tried to refresh Firefox and Chrome via F5 once and nothing. Tried it again and then the cookie was somehow registered and the code behaved as it should. Then I can refresh as many times as I want and it works as supposed to. But in Chrome it is inconsistent more and sometimes it doesn’t show as it should. Firefox is more consistent it seems.

But if I end the browser session and close Firefox/Chrome and then reopen it and, again, I see what shouldn’t be there because Phoenix for some reason doesn’t accept the cookie.

I have checked if there is a bug in the JavaScript code with VS Code liveserver extension in a plain html file, I mean the same javascript code, and there it is working flawlesly. No refreshing, everythign as expected on the first go. I can close the browser and then it rememberes and loads everything as expected on the first try.

Any idea how to fix this issue, it’s driving me insane.

1 Like

Session cookie set by Phoenix has the HttpOnly set by default. so your javascript cannot access it: [Using HTTP cookies - HTTP | MDN}

If you just want to read or write a small piece of data in Javascript, [Window.localStorage - Web APIs | MDN] is much easier to use than a cookie.

But I can alert the value of the cookie.

e.g.

let showTips = Cookies.get(‘showTips’);
alert(showTips);

this code will show the value I have stored in this cookie. This works.

But when I try this:

<div id="user-tips">
    Drink more water and you will feel better.
    <button onclick="hideTips()">Hide tips</button>
</div>

<script>

function hideTips() {
    Cookies.set('showTips', 'false', { expires: 365, path: '' });
    alert(Cookies.get('showTips'))
}

document.addEventListener('DOMContentLoaded', function(event) {
  let showTips = Cookies.get('showTips');
  alert(showTips);

  if (showTips == false) {
    document.getElementById("user-tips").style.display = "none";
  } else {
    document.getElementById("user-tips").style.display = "block";
  }

})

</script>

Only the alert and button click works. So, the library for working with cookies I use is working ok.
So, the cookie is set.

But the if check doesn’t work for some reason. Like it can’t find the id=“user-tips” div or something.

That’s not phoenix session cookie. You need to clearly state what exactly you want to achieve, or no one can help you.

You said liveView. In LiveView, the DOMContentLoaded is only triggered once at the static rendering, so I am not sure what do you want to achieve.

Like I said, the simplest way to stash and retrieve a small piece of data client side is not cookie, but localStorage.

localStorage is not a safe storage. Can’t use that. This hideTips example is just an example, I have more sensitive information stored there in real app.

Still, I think there is some weird misunderstanding.

The cookies are stored in the browser, doing a simple javascript check should be done on the user’s client too. There is no need for a server to do any checking in this case, Jsut show or hide some div based on your cookies. What is so hard about that?

Define safe. Cookie can be tampered with too.

1 Like

The only tamper-proof way of storing and retrieving a piece of info at the client side is to do it from the server side with crypto signing, like with JWT.

1 Like

I can alert the cookie value, that is not an issue, it seems. The problem is that I can’t select an element for some reason.

  if (showTips == false) {
    document.getElementById("user-tips").style.display = "none";
    alert(showTips);
  } else {
    document.getElementById("user-tips").style.display = "block";
    alert(showTips);
  }

the alert will work in every case, but getElementById not. Any idea why?

If you can’t getElementById, it means you don’t have this element id in the DOM. Drop down to a javascript console to try yourself.

You need to use a phx-hook to look for the element since it may or may not exists at DOMContentLoaded time as LiveView is patching the DOM and controlling the lifecycle of elements, so try something like:

<div id="user-tips" phx-hook="Tips">
    Drink more water and you will feel better.
    <button onclick="hideTips()">Hide tips</button>
</div>
let Hooks = {}
Hooks.Tips = {
  mounted(){
    let showTips = !!Cookies.get("showTips")
    alert(showTips)

    if(showTips){
      this.el.style.display = "block"
    } else {
      this.el.style.display = "none"
    }
  }
}
 
let liveSocket = new LiveSocket("/live", Socket, {
  hooks: Hooks, params: {_csrf_token: csrfToken}
})
1 Like

How will the code know that

this.el.

is user-tips id?

Do I need to put the Hook.Tips code inside assets/js/app.js file? Or can I put it in my def render function in my component .ex file (live/components/tips_component.ex)?

EDIT: Ok, so I find out that I have to put an data attribute to my existing div and that is then .Tips in Hooks.Tips and this.el later as well. I will try it now and let you know if it works.

Perhaps it would be useful to have some simple examples in the docs for this.

Because this.el is the element that triggered the hook

Do I have to put the Hook code in the central app.js file? Can i just put it in my component ex file?

put in with your app.js file. You will already see a section with

let liveSocket = new LiveSocket("/live", Socket, {
...

Thanks, it finally worked. But man it’s a complicated process to do something such simple ;D.

The docs have examples :slight_smile:
https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks

Yeah, I see. I have to reread it a couple of times until it sinks in.