How to persist state in Xstate state machines in react?
Asked Answered
H

3

5

I have a working cart state machine to add items in cart I'm using reactjs. On refreshing page, context is not persisted.I`m new to state machines and would like to persist state in my app.Below is my cartMachine .Please help.Thank you.

export const cartMachine = Machine(
  {
    id: "cart",
    initial: "idle",
    context: {
      ItemsInCart: [],
    },
    states: {
      idle: {
        on: {
          ADD: {
            target: "idle",
            actions: ["addProductsToCart"],
          },
        },
      },
    },
  },
  /* actions */
  {
    actions: {
      addProductToCart: assign((context, event) => {
        const { ItemsInCart } = context;
        const { item } = event;
        let checkIfProductInCart = ItemsInCart.find(({ id }) => id == item.id);
        let canAddToCart = checkIfProductInCart;

        if (!canAddToCart) {
          ItemsInCart.push({ ...item });
        }
      }),
    },
  }
);
Hass answered 21/5, 2020 at 12:17 Comment(0)
J
7

The answer from @muhammad-ali answers your particular question. When you do a browser refresh any data/application state which was just in memory is gone. You will need to find other solutions to persist data between refresh permanently. This has nothing to do with XState or React though. Since your goal seems to be to store the items in cart so if the user comes back to the site he still has the same cart, you will either have to use localStorage or use a backend + datastore to persist data for good and retrieve the data via an API when you load the application/cart page.

The rest of the answer drifts a bit off from the original question, but maybe gives some insights on how machine state is actually persisted in memory using XState machines.

A machine itself does not persist state (not even in memory to a degree). Example which shows this is that if you do a machine transition (passing in the initial state and the action you want to perform) and then read the machine state after that it will still be in the original state. An XState machine is basically just something which can perform an operation (as a transition) and this transition returns the new state to you.

Using your machine in the question:

const cartMachine = Machine({ ... })

const newState1 = cartMachine.transition(cartMachine.initialState, {type: 'ADD', item: {...})
console.log(newState1.context.ItemsInCart)  // Will contain item

const newState2 = cartMachine.transition(cartMachine.initialState, {type: 'ADD', item: {...})
console.log(newState2.context.ItemsInCart)  // Will still only contain one item (from the last ADD operation)

// can not read the current state from machine, only initialState is available
console.log(cartMachine.initialState.context.ItemsInCart)  // will still be []

// You will have to persist state yourself
const newState1 = cartMachine.transition(cartMachine.initialState, {type: 'ADD', item: {...})
// Pass the new state into the machine
const newState2 = cartMachine.transition(newState1, {type: 'ADD', item: {...})
console.log(newState2.context.ItemsInCart)  // Will now contain two items

So a machine never persist state. You have two choices to achieve this though.

  1. Store the new state after each transition somewhere in React state. XState machine state is JSON serializable, so you can store in React state without problems.

  2. Use a machine service to persist state (https://xstate.js.org/docs/guides/interpretation.html). As long you use the same instance of that service each transition is persisted in memory till the service stops. Example for your machine:

import { Machine, assign, interpret } from 'xstate'

const cartMachine = Machine({ ... })

const cartService = interpret(cartMachine)
cartService.start()

cartService.send({type: 'ADD', item: {...})
console.log(cartService.state.context.ItemsInCart)  // contains item

cartService.send({type: 'ADD', item: {...})
console.log(cartService.state.context.ItemsInCart)  // contains 2 items
Jew answered 23/5, 2020 at 5:45 Comment(0)
F
5

There is a pretty good example in the xState documentation that shows how they suggest using localStorage:

You can persist and rehydrate state with useMachine(...) via options.state:

// ...

// Get the persisted state config object from somewhere, e.g. localStorage
const persistedState = 
  JSON.parse(localStorage.getItem('some-persisted-state-key') || 
  someMachine.initialState;

const App = () => {
  const [state, send] = useMachine(someMachine, {
    state: persistedState // provide persisted state config object here
  });

  // state will initially be that persisted state, not the machine's initialState

  return (/* ... */)
}
Ferula answered 18/5, 2021 at 19:20 Comment(0)
B
0

You have to save your state context in local storage of browser in order to persist it even on page refresh. It has really simple API, you can read more about it at: https://www.w3schools.com/jsref/prop_win_localstorage.asp

Buckler answered 21/5, 2020 at 12:32 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.