Render Cycles
In Frog, you may see that your frame handler may get invoked twice.
There are a maximum of two render cycles for .frame
handlers:
- one for the "main" frame endpoint to render intents & derive state, and
- one for the "OG image" frame endpoint to render the frame image.
If we want to enforce idempotency (ie. invoke something once), we can distinguish between the two cycles by using the cycle
property on context.
For example, we may want to perform a side-effect to update a database:
import { Button, Frog } from 'frog'
import { db } from './db'
export const app = new Frog()
app.frame('/', (c) => {
const { buttonValue, cycle } = c
if (cycle === 'main') db.save(buttonValue)
return c.res({
image: (
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
Selected: {buttonValue}
</div>
),
intents: [
<Button value="apple">Apple</Button>,
<Button value="banana">Banana</Button>,
<Button value="mango">Mango</Button>
]
})
})
It is worth noting that the deriveState
function is idempotent by default, and it is only invoked once.
import { Button, Frog } from 'frog'
import { db } from './db'
export const app = new Frog({
initialState: {
count: 0,
number: 0
}
})
app.frame('/', (c) => {
const { buttonValue, cycle, deriveState } = c
const state = deriveState((previousState) => {
previousState.number = Math.random()
previousState.count++
})
`state.number` will be equal between render cycles: (cycle = "main") `state.number`: 0.270822354849678, `state.count`: 1 (cycle = "image") `state.number`: 0.270822354849678, `state.count`: 1