LiveView. Implementing a Shopping Cart Component. How to load state from localStorage and forward it to Components

Hello :wave:

I’m trying to implement something similar to a shopping cart.
I’ve got two LV.

  • Page LV
  • SecondPage LV

They both need to render the CartComponent (in the future a full-featured cart, right now only a %{ total: X}.
The component is stateful and handles the events (see component-click event within CartComponent).

I originally followed this very interesting thread regarding not un-mounting components upon a LV change via routing, however I couldn’t make the proposed solution work. Regardless I’m giving it a try via localStorage instead, so I figure that’s a different solution.
My goal is by steps:

  1. User lands in either of the LVs (which make use of CartComponent)
  2. JS hooks load localStorage.shoppingCart --> forward it to LV
  3. LV receives the socketParams and forwards it to the CartComponent whose update re-renders with new value.
  4. Whatever new event in the cart logic --> CartComponent handles it and it is appropriately updated.

The problem I’m encountering is with the first render of all. Something like this happens.

  1. User lands in PageLive --> renders CartComponent with empty shoppingCart
  2. Second time PageLive is mounted when the connected?(socket) is true --> receives new shoppingCart from JS hook.
  3. CartComponent's update does fire and receives the updated shoppingCart post-socket connected.
  4. CartComponent's UI render does not update
  5. Manually try to fire a component-click event --> CartComponent handles and UI is properly updated.

This is the very test repo i’m running


Am I missing something to make that first update re-render?

Thanks!

This is client side storage, and you cannot trust it, therefore I am not fan of using the browser local storage for storing data.

So my advice is for you to figure out how you can make it work properly via backend, and not not via browser local storage.

Sorry not be able to help you with your way, but I am also new to Live View.

1 Like

Hey thanks for the advice!

The way I see it localStorage here is only a reflection of what has been processed and verified at the backend beforehand(ie every event and change is handled in BE).
It’s synced to localStorage in order to resume upon page change (and hence new LV mount) :slightly_smiling_face:
Or even across different visits to the same page.
I’d be happy to try a different architecture/implementation defly if you have any suggestions. Even though I’d still like to know why this doesn’t work upon first update.

your example is a bit broken - the js breaks, when localstorage is empty fix:
shoppingCart: JSON.parse(localStorage.shoppingCart || null) || "{}",

now when decoding the json you’ll get a map - not a struct…
so change the render in cartcomponent to Map.get(assigns.shoppingCart, "total")

notice “total” vs :total

likewise when you update do it in a map:

    shoppingCart = %{
      "total" => Enum.random(10..1000)
    }

this code is broken elixir

shoppingCart = %{}

if connected?(socket) do
  IO.puts("Component connected")
  shoppingCart = Jason.decode!(Map.get(assigns.socketParams, "shoppingCart"))
end

do:

shoppingCart = if connected?(socket) do
  IO.puts("Component connected")
  Jason.decode!(Map.get(assigns.socketParams, "shoppingCart"))
else
  %{}
end

also wrap your value in a html element (liveview needs this)

<div><%= Map.get(assigns.shoppingCart,"total") %></div>

then it should work…

ps: made a fork with the fixes if anybody finds this thread… https://github.com/petermm/component-test

4 Likes

Just to let you know that I’m still trying to tie up both LVs and the LiveComponent in sync with the same data after page changes via handle_info at LVs (didn’t manage to do so yet :confounded:).
Regardless your suggestions helped me a lot so far! Will update here as soon as I manage to find a solution, thanks!

Thank you @outlog your answer helped me debug the specific problem communicating
LiveView -> LiveComponent :slightly_smiling_face:

In parallel I also tried to solve a tangential problem here, which tries to have a common source of truth for all LVs that will consume the shoppingCart (basically I wanted to keep the localStorage-resumed shoppingCart in memory so every newly mounted LV could consume it without having to resume form localStorage again). Unfortunately I couldn’t land it in the way I intended, but I think I’m overcomplicating things so I’ll pivot my approach.
Regardless that’s a different story.

1 Like