Visão Geral
O hook useExperiment é a forma principal de implementar experimentos A/B no Testly.
Ele gerencia automaticamente:
✅ Atribuição determinística de variantes
✅ Registro automático de impressões
✅ Cache local para consistência
✅ Estados de loading e erro
Uso Básico
import { useExperiment } from '@testlyjs/react' ;
function MyComponent () {
const { variant , loading , error , convert } = useExperiment ( 'experiment-id' );
if ( loading ) return < div > Carregando... </ div > ;
if ( error ) return < div > Erro ao carregar experimento </ div > ;
return (
< div >
{ variant === 'variant-b' ? (
< button onClick = { () => convert ( 'clicked' ) } > Variante B </ button >
) : (
< button onClick = { () => convert ( 'clicked' ) } > Variante A (Controle) </ button >
) }
</ div >
);
}
Assinatura
const {
variant ,
loading ,
error ,
convert
} = useExperiment ( experimentId , options ? );
Parâmetros
Parâmetro Tipo Obrigatório Descrição experimentIdstring✅ Sim ID único do experimento criado no dashboard optionsobject❌ Não Opções de configuração do experimento
Opções
interface UseExperimentOptions {
fallback ?: string ; // Variante padrão antes de carregar (opcional)
onAssignment ?: ( variant : string ) => void ; // Callback quando variante é atribuída (opcional)
}
Retorno
interface UseExperimentResult {
variant : string | null ; // Chave da variante atribuída
variantId : string | null ; // UUID interno da variante
experimentId : string | null ; // UUID interno do experimento
loading : boolean ; // true enquanto carrega
error : Error | null ; // Erro se houver falha
convert : ( eventName : string ) => Promise < void >; // Função para registrar conversão
}
Exemplos Práticos
Exemplo 1: Teste de CTA
import { useExperiment } from '@testlyjs/react' ;
export function HeroSection () {
const { variant , loading , error , convert } = useExperiment ( 'homepage-hero-cta' );
if ( loading ) {
return (
< section className = "hero" >
< h1 > Transforme seu produto com dados </ h1 >
< div className = "skeleton-button" />
</ section >
);
}
if ( error ) {
console . error ( 'Erro no experimento:' , error );
// Fallback para versão original
return (
< section className = "hero" >
< h1 > Transforme seu produto com dados </ h1 >
< button onClick = { () => window . location . href = '/signup' } >
Comece Grátis
</ button >
</ section >
);
}
const handleClick = () => {
convert ( 'hero_cta_clicked' );
window . location . href = '/signup' ;
};
return (
< section className = "hero" >
< h1 > Transforme seu produto com dados </ h1 >
{ variant === 'variant-b' ? (
< button onClick = { handleClick } className = "cta-primary" >
Experimente Agora 🚀
</ button >
) : (
< button onClick = { handleClick } className = "cta-primary" >
Comece Grátis
</ button >
) }
</ section >
);
}
Exemplo 2: Teste de Layout
import { useExperiment } from '@testlyjs/react' ;
export function PricingSection () {
const { variant , convert } = useExperiment ( 'pricing-layout' );
const handleSelectPlan = ( planName : string ) => {
convert ( 'plan_selected' );
// Redirecionar para checkout
};
// Layout de 3 colunas (Variante B)
if ( variant === 'variant-b' ) {
return (
< section className = "pricing-3-col" >
< PricingCard plan = "Free" onSelect = { handleSelectPlan } />
< PricingCard plan = "Pro" onSelect = { handleSelectPlan } featured />
< PricingCard plan = "Enterprise" onSelect = { handleSelectPlan } />
</ section >
);
}
// Layout de 2 colunas (Controle)
return (
< section className = "pricing-2-col" >
< PricingCard plan = "Free" onSelect = { handleSelectPlan } />
< PricingCard plan = "Pro" onSelect = { handleSelectPlan } featured />
</ section >
);
}
Exemplo 3: Teste de Múltiplas Variantes
import { useExperiment } from '@testlyjs/react' ;
export function ProductHero () {
const { variant , convert } = useExperiment ( 'hero-headline' );
const headlines = {
'control' : 'A/B Testing para Founders Devs' ,
'variant-b' : 'Valide suas Ideias com Dados Reais' ,
'variant-c' : 'Experimentos A/B sem Complicação' ,
'variant-d' : 'Teste. Aprenda. Melhore.'
};
const handleCTA = () => {
convert ( 'cta_clicked' );
window . location . href = '/signup' ;
};
return (
< section className = "hero" >
< h1 > { headlines [ variant ] || headlines . control } </ h1 >
< p > Implemente testes A/B em 5 minutos com nosso SDK React. </ p >
< button onClick = { handleCTA } >
Começar Grátis
</ button >
</ section >
);
}
Exemplo 4: Teste Condicional
import { useExperiment } from '@testlyjs/react' ;
export function FeatureSection () {
const { variant , convert } = useExperiment ( 'social-proof-test' );
return (
< section className = "features" >
< h2 > Por que escolher o Testly? </ h2 >
< FeatureList />
{ /* Mostrar social proof apenas na Variante B */ }
{ variant === 'variant-b' && (
< div className = "social-proof" >
< div className = "testimonials" >
< Testimonial author = "João Silva" role = "CTO @ StartupXYZ" />
< Testimonial author = "Maria Santos" role = "Founder @ SaaSCo" />
</ div >
< button onClick = { () => convert ( 'social_proof_cta_clicked' ) } >
Ver Mais Depoimentos
</ button >
</ div >
) }
</ section >
);
}
Comportamento do Hook
Primeira Renderização
Hook é executado
Verifica cache local
Se não encontrar, chama a API
API atribui variante de forma determinística
Impressão é registrada automaticamente
Variante é salva no cache
Componente re-renderiza com a variante
Renderizações Subsequentes
Hook busca variante do cache
Retorna imediatamente (sem chamada à API)
Não registra nova impressão
Mantém consistência da experiência
O registro de impressão é automático e acontece apenas na primeira vez que o usuário vê o experimento. Você não precisa fazer nada!
Conversões
A função convert() permite registrar quando o usuário completa a ação desejada.
Sintaxe
convert ( eventName : string ): Promise < void >
Parâmetros
Parâmetro Tipo Descrição eventNamestringNome do evento de conversão (ex: 'clicked', 'signed_up')
Exemplos de Conversão
// Conversão simples
< button onClick = { () => convert ( 'button_clicked' ) } >
Clique Aqui
</ button >
// Conversão com ação posterior
< button onClick = {async () => {
await convert ( 'form_submitted' );
router . push ( '/thank-you' );
} } >
Enviar Formulário
</ button >
// Conversão com tratamento de erro
< button onClick = {async () => {
try {
await convert ( 'purchase_completed' );
showSuccessMessage ();
} catch ( error ) {
console . error ( 'Falha ao registrar conversão:' , error );
// A ação continua mesmo se o tracking falhar
}
} } >
Finalizar Compra
</ button >
Boas práticas para nomes de eventos :
Use snake_case: button_clicked, form_submitted
Seja descritivo: hero_cta_clicked > click
Use verbos no passado: clicked, submitted, completed
Estados de Loading e Erro
Loading State
Sempre implemente um estado de loading para evitar Layout Shift:
const { variant , loading } = useExperiment ( 'test' );
if ( loading ) {
return < Skeleton /> ; // Placeholder com mesmo tamanho
}
Error State
Sempre tenha um fallback para erros:
const { variant , error } = useExperiment ( 'test' );
if ( error ) {
// Log do erro (opcional)
console . error ( 'Erro no experimento:' , error );
// Retornar versão original/segura
return < OriginalVersion /> ;
}
Importante : Nunca deixe seu app quebrar por causa de um experimento. Sempre implemente fallbacks que mantêm a funcionalidade.
Múltiplos Experimentos
Você pode rodar vários experimentos no mesmo componente:
export function ProductPage () {
const heroTest = useExperiment ( 'hero-test' );
const pricingTest = useExperiment ( 'pricing-test' );
const ctaTest = useExperiment ( 'cta-test' );
return (
< div >
< Hero
variant = { heroTest . variant }
onConvert = { heroTest . convert }
/>
< Pricing
variant = { pricingTest . variant }
onConvert = { pricingTest . convert }
/>
< CTA
variant = { ctaTest . variant }
onConvert = { ctaTest . convert }
/>
</ div >
);
}
Cada experimento é independente. Um usuário pode ver Variante A no hero e Variante B no pricing simultaneamente.
Boas Práticas
1. Sempre implemente Loading e Error
// ✅ Bom
const { variant , loading , error } = useExperiment ( 'test' );
if ( loading ) return < Skeleton /> ;
if ( error ) return < Fallback /> ;
// ❌ Ruim (pode quebrar o app)
const { variant } = useExperiment ( 'test' );
return < div > { variant } </ div > ;
// ✅ Bom (descritivo)
if ( variant === 'variant-b' ) { ... }
if ( variant === 'control' ) { ... }
// ❌ Ruim (confuso)
if ( variant === 'v2' ) { ... }
if ( variant === 'new' ) { ... }
3. Isolar lógica de experimento
// ✅ Bom (fácil de remover depois)
const { variant , convert } = useExperiment ( 'test' );
const buttonText = variant === 'variant-b'
? 'Experimente Agora'
: 'Comece Grátis' ;
return < button onClick = { () => convert ( 'clicked' ) } > { buttonText } </ button > ;
// ❌ Ruim (lógica espalhada)
return variant === 'variant-b' ? (
< div >
< h1 > Título B </ h1 >
< button > Experimente Agora </ button >
< p > Copy B </ p >
</ div >
) : (
< div >
< h1 > Título A </ h1 >
< button > Comece Grátis </ button >
< p > Copy A </ p >
</ div >
);
4. Testar apenas UMA variável
// ✅ Bom (testa apenas o texto)
const buttonText = variant === 'variant-b' ? 'Texto B' : 'Texto A' ;
// ❌ Ruim (muda texto + cor + tamanho)
if ( variant === 'variant-b' ) {
return < button className = "big blue" > Texto B </ button > ;
}
TypeScript
O hook é totalmente tipado:
import { useExperiment } from '@testlyjs/react' ;
// Tipos básicos são inferidos automaticamente
const { variant , loading , error , convert } = useExperiment ( 'test' );
// Você pode especificar o tipo da variante
type MyVariants = 'control' | 'variant-b' | 'variant-c' ;
const { variant } = useExperiment < MyVariants >( 'test' );
// Agora variant tem autocomplete com os valores possíveis
if ( variant === 'variant-b' ) { // ✅ Autocomplete funciona
// ...
}
Troubleshooting
variant retorna null — causa mais comum
A causa número 1 : a API Key no TestlyProvider não corresponde à organização onde o experimento foi criado.O SDK envia a API Key para o servidor, que busca o experimento dentro daquela organização. Se você usar a key de uma conta diferente, o servidor não encontra o experimento e retorna null. Checklist :
Abra app.testly.com.br/settings → copie a API Key (tk_live_...)
Confirme que essa mesma key está no TestlyProvider do seu app
Confirme que o slug em useExperiment('...') é exatamente igual ao slug do dashboard (case-sensitive)
Confirme que o experimento está em estado running no dashboard (não draft, paused ou completed)
Ative o debug para inspecionar a resposta do servidor: < TestlyProvider apiKey = "..." config = { { debug: true } } >
Possíveis causas :
Problema de rede ou firewall bloqueando o endpoint
API Key inválida (formato incorreto)
Experimento inativo — retorna null imediatamente, não fica em loading
Solução :
Verifique o console do navegador por erros de rede
Confirme que a API Key tem o formato tk_live_...
Ative debug: true e verifique os logs
Conversões não são registradas
Checklist :
✅ Você está chamando convert('event_name')?
✅ O nome do evento é uma string válida?
✅ Você registrou impressão (automático) antes?
✅ Debug mode está ativo para ver logs?
Teste :convert ( 'test_event' ). then (() => {
console . log ( '✅ Conversão registrada' );
}). catch ( err => {
console . error ( '❌ Erro:' , err );
});
Variante muda a cada refresh
Isso não deveria acontecer! O Testly usa cache local para garantir consistência. Possíveis causas :
localStorage está sendo limpo
Navegação privada/anônima
Diferentes userId sendo gerados
Solução :
Verifique se não há código limpando localStorage
Use um userId fixo para testar
Próximos Passos
Exemplos Práticos Veja implementações reais de A/B tests
Configuração Opções avançadas do SDK
Debugging Guia completo de troubleshooting