Passing State
In the previous lesson, we learned how to work with text input from the user.
Let's improve the UX a bit more: we'll color the button red if the person answers incorrectly and green if they answer correctly. To start, we'll take the code we created in the last lesson but to simplify the example, we'll remove the arbitrary answer button and return the correct answer to the list.
You can continue in question.tsx
or create a new file.
// Import showToast action
import {showToast} from '@app/ui'
const question = {
label: 'The Capital of Great Britain',
answers: ['New York', 'Liverpool', 'London'],
correct: 'London',
}
// Screen with 3 buttons and question
const questionScreen = app.screen('/', async function(ctx, req) {
return <screen title="Question">
<text class="section">{question.label}</text>
{question.answers.map(answer =>
<button
onClick={buttonHandler.apiCall({ value: answer })}
class={["secondary", "section"]}
title={answer}
/>
)}
</screen>
})
// Handler for button
const buttonHandler = app.apiCall('/check', async function(ctx, req) {
return (req.body.value === question.correct)
? showToast("Correct")
: showToast(req.body.value + ' is not correct');
})
To recap, this code shows a question and its answer options. When clicking on the correct option, it displays the message "Correct"; when clicking on an incorrect one, it shows an error message.
So, our task is to make it so that when clicking on the answer button, it changes color. In the classic React approach, this state change happens on the client side—React updates the state immediately.
In our case, it's important to understand one thing:
There is no client-side state in Chatium
If we want to change the screen's state, we must issue a new screen from the server. We can change the state either by modifying the data or simply changing the URL.
For example, we can add something to the URL query segment—the part that comes after the question mark in the address.
In the screen code, you can look at the req.query
object. It contains the query from the HTTP request. So if we pass ?q=value1
in the address, we can access this value in the screen code via req.query.q
.
Let's modify the button click handler so that it returns us to the screen with the response indication.
// Handler for button
const buttonHandler = app.apiCall('/check', async function(ctx, req) {
const messageAction = (req.body.value === question.correct)
? showToast("Correct")
: showToast(req.body.value + ' is not correct');
const redirectAction = questionScreen.navigate(
{queryParams: {'answered': req.body.value}, replace: true}
);
return [messageAction, redirectAction];
})
In this code, we declare two actions and return them in an array. The application will execute them sequentially: first showing the message, then performing the navigation.
Note the second argument in navigate
. Here we pass {replace: true}
, which replaces the current screen instead of opening a new one.
Now let's display the value of answered
on the screen to ensure everything is working correctly.
We'll add the output of the answer below the question.
<text class="section">{question.label}</text>
{req.query?.answered && <text class="section">
Answered: {req.query.answered}
</text>}
Notice the construction {req.query?.answered && ...}
. It allows displaying the <text>...</text>
tag only if req.query
exists and req.query.answered
contains something.
Now let's transfer this logic to color the button.
Currently, each answer button has two classes: {['secondary', 'section']}
. We'll replace this code so that instead of 'secondary'
, there is a variable responsible for the color.
{question.answers.map(answer => {
let buttonClass = "secondary"
if (req.query && req.query.answered == answer) {
buttonClass = answer == question.correct ?
"success" : "danger"
}
return <button
onClick={buttonHandler.apiCall({ value: answer })}
class={[buttonClass, "section"]}
title={answer}
/>
}
)}
This code renders the buttons on the screen. The one clicked by the user will be colored: assigned the class "success" if this answer option is correct or the class "danger" if it is not.