Managing State

Learn about how to manage the data.

About Manage State

A manage state implies that the parameters data are stored on a thread level, and this data can be accessed anytime after the first conversation.

Parameters are variables that were set during a conversation. However, whenever a session expires or resets, the variable's data is lost.

To ensure that the variable's data are intact, Khoros Flow uses a simple storage mechanism that can manage stateful data.

Count Code example

The example below shows how to store the data count.

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}`)
}

Saving a Value

Use the state.save() method to save key-value data. The code below is an example of saving a value.

async payload => {

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

Fetching a Value

Use the get() method to fetch a value using its key. The code below is an example of fetching the watched key.

async payload => {

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

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

📘

Note

If no data exists get() returns a null value.

Removing values

Use the remove() method to remove a specific key value.
The code below shows how to remove the watched key.

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

Use the clear() method to clear all the stored values.
The code below shows how to clear the conversation.

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

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 the below code action example, assume the user already has a score parameter.

In the Code Action below, you can:

  • Create a score object with the score parameter value and the userId parameter to track the user’s score.
  • Retrieve the scores from the leaderboard collection.
    • If that parameter value is empty, 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.
  • 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, set the parameters to display the full leaderboard or the user’s position 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)
  }
}

Finally, reset the full leaderboard with the state.clear() function and collectionName.

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

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