Manage State

Params are variables that are set within a conversation. But, whenever a session is reset or expires, these variables are lost.

Using a simple storage mechanism you can manage stateful data. Data is stored on a thread level and can be accessed even weeks after the first conversation took place.

Code example

async payload => {

  // Get the current count, or 0
  let count = await state.get('count') || 0

  // Save the current count
  await state.save('count', count + 1 )

  // Show or speak the current count
  return new Message(`Count is ${count}`)
}

Save a Value

Storing data can be done using the state.save() method. Provide a key and value to save:

async payload => {

  await state.save('watched', {
    season: 1,
    episode: 2,
    serie: 'game-of-thrones'
  })
}

Get a Value

Fetch a value by key using the get() method:

async payload => {

  // Could be null if not found
  const watched = await state.get('watched')

  if(watched) {
    // ....
  }
}

📘

Note

If no data exists get() will return null

Removing values

Remove a specific value by key:

async payload => {
  // Remove aa specific key
  await state.remove('watched')
}

Clear all values stored for this thread:

async payload => {
  // Remove all data for this conversation
  await state.clear()
}

Reference

state.save(key, value)

Save data in state

Returns: Promise - Promise that resolves with the value being stored

Properties

NameTypeDescription
keystringKey used to save data
valueobjectEither an object, array, or string to store
collectionNamestringOptional. Custom collection name to share state across multiple users

state.get(key)

Retrieve data from the state by key.

Returns: Promise - Promise that resolved with the value being stored or null if the key does not exist.

Properties

NameTypeDescription
keystringKey used to find the data
collectionNamestringOptional. Custom collection name to find shared state from other users

state.remove(key)

Remove data from the state by key.

Returns: Promise - Resolves with the key of the deleted data

Properties

NameTypeDescription
keystringKey of the data to delete
collectionNamestringOptional. Name of custom collection to delete

state.clear()

Clear all data from the state.

Returns: undefined

Shared state example

Let's say you have a simple quiz bot, and you want to let users submit their score to a leaderboard. You can use a collection to let users get a reference to the scores of other users.

In this example, we assume the user already has a score parameter. In the Code Action below, we create a score object with the value of that param, as well as the userId so we can keep track of whose score it is. Next, we retrieve the scores from the leaderboard collection. If that's empty, we create a new empty array. If the user already has a score on the leaderboard, we override the value with the new score. Otherwise, we add the new score. At last, we save the shared state again in the leaderboard collection.

async payload => {
  try {

    console.log("payload", payload);

    const score = {
      score: payload.params.score[payload.params.score.length - 1].value,
      userId: payload.user.userId ? payload.user.userId: payload.user.name
    }

    let scores = await state.get('scores', 'leaderboard');

    if (scores === null) scores = [];

    const existingScoreIndex = scores.findIndex((scoreInState) => scoreInState.userId === score.userId);

    if (existingScoreIndex !== -1) {
      scores[existingScoreIndex].score = score.score;
    } else {
      scores.push(score);
    }

    console.log("Storing new scores to state", scores);

    await state.save('scores', scores, 'leaderboard');

  } catch(err) {
    console.error(err)
  }
}

In another Code Action, you could set parameters to display the full leaderboard or the position of the user within a reply:

async payload => {
  try {

    let scores = await state.get('scores', 'leaderboard');
    console.log("scores", scores);

    let fullLeaderboardObj = null;
    let leaderboardPlayerResult = "You are not on the leaderboard yet. Answer the quiz questions to get a score on the leaderboard.";

    if (scores?.length > 0) {
      const sortedScores = scores.sort((a, b) => b.score - a.score);
      console.log("sortedScores", sortedScores);

      const userId = payload.user.userId ? payload.user.userId : payload.user.name;
      let playerFound = false;
      let playerRank;
      let playerScore;
      fullLeaderboard = "";

      for (let i = 0; i < sortedScores.length; i++) {
        if (sortedScores[i].userId === userId) {
          playerFound = true;
          playerRank = i + 1;
          playerScore = sortedScores[i].score;
        }

        fullLeaderboard += `${sortedScores[i].userId} - ${sortedScores[i].score} points\n\n`;
      }

      console.log("fullLeaderboard", fullLeaderboard);
      fullLeaderboardObj = [{
        value: fullLeaderboard,
        match: fullLeaderboard
      }]

      leaderboardPlayerResult = "";

      if (playerFound) leaderboardPlayerResult = `You are on rank ${playerRank} of all ${sortedScores.length} players on the leaderboard with ${playerScore} points.`
    }

    console.log("leaderboardPlayerResult", leaderboardPlayerResult);

    return {
      params: {
        ...payload.params,
        leaderboardPlayerResult: [{
          value: leaderboardPlayerResult,
          match: leaderboardPlayerResult
        }],
        fullLeaderboard: fullLeaderboardObj
      }
    }
  } catch(err) {
    console.error(err)
  }
}

At last, you can also reset the full leaderboard by passing the collectionName in the state.clear() function:

async payload => {
  try {
    
    await state.clear("leaderboard");
     
  } catch(err) {
    console.error(err)
  }
}