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 returnnull
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
Name | Type | Description |
---|---|---|
key | string | Key used to save data |
value | object | Either an object, array, or string to store |
collectionName | string | Optional. 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
Name | Type | Description |
---|---|---|
key | string | Key used to find the data |
collectionName | string | Optional. 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
Name | Type | Description |
---|---|---|
key | string | Key of the data to delete |
collectionName | string | Optional. 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)
}
}
Updated 10 months ago