Neste artigo, criaremos um aplicativo da web rastreador de condicionamento físico usando React e Firebase, duas tecnologias que nos permitem desenvolver aplicativos da web com alta eficiência.
Este artigo permitirá que você crie aplicativos full-stack com React e Firebase por conta própria. Se você conhece os fundamentos do React, você deve estar pronto para começar. Caso contrário, sugiro abordá-los primeiro.
Observe que você pode encontrar o aplicativo concluído aqui e o código-fonte para este projeto aqui .
Configurando o projeto
Vamos começar substituindo uma nova configuração do aplicativo Create React com o pacote craco npm . Tailwind precisa do pacote craco para sobrescrever a configuração padrão Criar aplicativo React.
Vamos configurar o roteamento também. Forneceremos às nossas rotas um parâmetro extra chamado layout
para que possam envolver a página com o layout correto:
função RouteWrapper ({page: Page, layout: Layout,... rest}) { Retorna (( )} /> ); }
Adicionaremos autenticação e outras rotas posteriormente neste artigo. Dentro do App.js
, retornaremos nosso roteador. Vamos continuar com as sessões de usuário.
Autenticação de sessões de usuário com React Context
Gostaríamos de saber se um usuário é autenticado de qualquer lugar em nosso aplicativo sem ter que passar essa informação por vários componentes. Para fazer isso, usaremos a API de contexto do React. Vamos envolver todo o nosso aplicativo com o contexto de autenticação para que possamos acessar o usuário autenticado no momento de qualquer lugar em nosso aplicativo.
Primeiro, vamos criar o contexto de autenticação.
Podemos criar um novo contexto chamando:
const AuthContext=createContext ()
Em seguida, iremos fornecê-lo a outros componentes, como:
{children}
No nosso caso, queremos nos inscrever como usuário autenticado do Firebase. Fazemos isso chamando o método onAuthStateChanged ()
em nossa função de autenticação do Firebase exportada:
auth.onAuthStateChanged (usuário=> {…});
Isso nos dará o usuário autenticado no momento. Se o estado de um usuário mudar, como uma entrada ou saída, queremos atualizar nosso provedor de contexto de acordo. Para lidar com essa mudança, usaremos o gancho useEffect
.
Nosso AuthContext.jsx
fica assim:
... função de exportação AuthProvider ({filhos}) { const [usuário, setUser]=useState (nulo); const [carregando, setLoading]=useState (true); useEffect (()=> { const unsubscribe=auth.onAuthStateChanged ((usuário)=> { setUser (usuário); setLoading (false); }); retornar cancelar assinatura; }, []); valor const={ do utilizador, }; Retorna ({! carregando && filhos} ); }
Podemos chamar o gancho useAuth ()
para retornar o valor de contexto atual de qualquer lugar em nosso aplicativo. O valor de contexto que estamos fornecendo agora conterá mais funções posteriormente, como login e logout.
Criação de formulários de login e inscrição
Para poder fazer o login do usuário, precisamos usar um método chamado signInWithEmailAndPassword ()
que reside no objeto auth que estamos exportando em nosso arquivo Firebase.
Em vez de acessar esse método diretamente, podemos adicioná-lo ao nosso provedor AuthContext
para que possamos combinar facilmente os métodos de autenticação com o usuário autenticado. Adicionaremos a função signIn ()
ao nosso provedor AuthContext
, assim:
função signIn (e-mail, senha) { retornar auth.signInWithEmailAndPassword (e-mail, senha); } valor const={ do utilizador, entrar, }; Retorna ({! carregando && filhos} );
Em nossa página de login, agora podemos acessar facilmente o método signIn ()
com nosso gancho useAuth ()
:
const {signIn}=useAuth ();
Se o usuário fizer login com sucesso, nós o redirecionaremos para o painel, que fica no caminho do roteador doméstico. Para verificar isso, usaremos um bloco try-catch.
Você deve receber uma mensagem de erro informando que nenhum usuário foi encontrado, pois ainda não estamos inscritos. Se sim, ótimo! Isso significa que nossa conexão com o Firebase está funcionando.
Habilitando a autenticação do Google
Primeiro, habilite a autenticação do Google dentro do console do Firebase. Em seguida, adicione a função signInWithGoogle
ao contexto de autenticação:
function signInWithGoogle () { return auth.signInWithPopup (googleProvider); }
A seguir, importaremos googleProvider
de nosso arquivo Firebase:
export const googleProvider=new firebase.auth.GoogleAuthProvider ();
De volta à nossa página de login, adicionaremos o seguinte código para fazer isso funcionar:
const handleGoogleSignIn=async ()=> { tentar { setGoogleLoading (true); aguardar signInWithGoogle (); history.push ("/"); } catch (erro) { setError (error.message); } setGoogleLoading (false); };
Vamos continuar criando nosso aplicativo de treino.
Criação de opções de treino no aplicativo de condicionamento físico
Selecionando um exercício
Vamos criar o componente real, chamado SelectExercise
. Queremos realizar duas coisas dentro deste componente. Primeiro, queremos renderizar uma lista de exercícios que o usuário criou e que podem ser adicionados ao seu treino. Em segundo lugar, queremos dar ao usuário a opção de criar um novo exercício.
O redutor de treino envolve todo o nosso aplicativo com o estado de treino para que possamos acessá-lo de qualquer lugar em nosso aplicativo. Cada vez que o usuário muda seu treino, localStorage
é atualizado, bem como todos os componentes que estão inscritos no estado.
Estamos separando o treino do provedor de despacho porque alguns componentes só precisam acessar o estado ou despacho:
const WorkoutStateContext=createContext (); const WorkoutDispatchContext=createContext (); exportar const useWorkoutState=()=> useContext (WorkoutStateContext); exportar const useWorkoutDispatch=()=> useContext (WorkoutDispatchContext); exportar const WorkoutProvider=({children})=> { const [treinoState, dispatch]=useReducer (rootReducer, inicializador); //Persiste o estado do treino na atualização do treino useEffect (()=> { localStorage.setItem ("treino", JSON.stringify (treinoState)); }, [treinoState]); Retorna (); }; {crianças}
Agora podemos despachar ações para nosso redutor. Este é um conceito do Redux-significa simplesmente que queremos que o estado do treino mude com um valor que fornecemos.
Nossa função addExercise
se parece com isto:
exercício
const={ exercícioName, conjuntos: {[uuidv4 ()]: DEFAULT_SET}, }; Despacho({ tipo:"ADD_EXERCISE", carga útil: {exercícioId: uuidv4 (), exercício}, });
Ele despacha a ação ADD_EXERCISE
para nosso redutor, que adicionará o exercício dado ao nosso estado. Usando Immer , nosso redutor ficará assim:
export const rootReducer=produzir ((rascunho, {tipo, carga útil})=> { switch (tipo) { ... case ACTIONS.ADD_EXERCISE: draft.exercises [payload.exerciseId]=payload.exercise; pausa; …
Para nossos exercícios, em vez de usar uma matriz de objetos, usaremos objetos de objetos .
case ACTIONS.UPDATE_WEIGHT: draft.exercises [payload.exerciseId].sets [payload.setId].weight= payload.weight; pausa;
Isso é muito mais eficiente do que filtrar uma matriz cada vez que atualizamos o estado, porque o redutor sabe exatamente qual item atualizar.
SelectExercise
também deve ser capaz de adicionar um exercício ao banco de dados. Portanto, primeiro precisamos acessar nosso banco de dados Firestore.
Esta é a funcionalidade para salvar um novo exercício no banco de dados:
const {usuário}=useAuth (); ... const saveExercise=async ()=> { if (! exercícioName) { return setError ("Por favor, preencha todos os campos"); } setError (""); tentar { esperar database.exercises.add ({ exercícioName, userId: user.uid, createdAt: database.getCurrentTimestamp (), }); toggleShowCreateExercise (); } catch (errar) { setError (err.message); } };
Também queremos recuperar a lista de exercícios que estão armazenados no banco de dados que o usuário criou. Não queremos envolver todo o nosso aplicativo com esses exercícios, então vamos mantê-lo localmente em nosso componente SelectExercise
.
Para recuperar os exercícios do banco de dados, não precisamos da API de contexto. Para fins de aprendizagem, criaremos um gancho personalizado que usa o gancho useReducer
para gerenciar o estado. Dessa forma, temos um gerenciamento de estado eficiente para recuperar uma lista atualizada de exercícios sempre que o usuário os solicita.
function useWorkoutDb () { const [treinoDbState, dispatch]=useReducer (redutor, initialState); const {usuário}=useAuth (); useEffect (()=> { despachar ({tipo: ACTIONS.FETCHING_EXERCISES}); return database.exercises .where ("userId","==", user.uid) .onSnapshot ((instantâneo)=> { Despacho({ tipo: ACTIONS.SET_EXERCISES, carga útil: snapshot.docs.map (formatDocument), }); }); }, [do utilizador]); useEffect (()=> { despachar ({tipo: ACTIONS.FETCHING_WORKOUTS}); return database.workouts .where ("userId","==", user.uid) .onSnapshot ((instantâneo)=> { Despacho({ tipo: ACTIONS.SET_WORKOUTS, carga útil: snapshot.docs.map (formatDocument), }); }); }, [do utilizador]); return treinoDbState; }
Você pode notar a diferença entre nosso outro useReducer
, onde estamos usando objetos de objetos e Immer para alterar o estado.
Agora você deve conseguir adicionar um exercício e vê-lo na lista. Impressionante! Vamos continuar com o cronômetro de treino.
Construindo o cronômetro de treino
Para o cronômetro, criaremos um gancho personalizado chamado useTimer
. Definiremos um intervalo a cada segundo para atualizar a variável numérica secondsPassed
. Nossas funções de parar e pausar limpam o intervalo para começar de 0
novamente. A cada segundo, atualizaremos a hora dentro do localStorage
do usuário também para que o usuário possa atualizar a tela e ainda ter o cronômetro funcionando corretamente.
function useTimer () { const countRef=useRef (); const [isActive, setIsActive]=useState (false); const [isPaused, setIsPaused]=useState (false); const [secondsPassed, setSecondsPassed]=useState ( persist ("get","timer") || 0 ); useEffect (()=> { const persistedSeconds=persist ("get","timer"); if (persistedSeconds> 0) { startTimer (); setSecondsPassed (persistedSeconds); } }, []); useEffect (()=> { persist ("set","timer", secondsPassed); }, [secondsPassed]); const startTimer=()=> { setIsActive (true); countRef.current=setInterval (()=> { setSecondsPassed ((segundos)=> segundos + 1); }, 1000); }; const stopTimer=()=> { setIsActive (false); setIsPaused (false); setSecondsPassed (0); clearInterval (countRef.current); }; const pauseTimer=()=> { setIsPaused (true); clearInterval (countRef.current); }; const resumeTimer=()=> { setIsPaused (false); startTimer (); }; Retorna { segundos Passados, está ativo, isPaused, startTimer, stopTimer, pauseTimer, resumeTimer, }; }
O cronômetro deve estar funcionando agora. Vamos continuar com o esquema de treino real.
Criando o esquema de treino no React
Em nosso aplicativo, queremos que o usuário seja capaz de:
- Adicionar e remover exercícios
- Adicionar e remover conjuntos
- Para cada conjunto, adicione peso e repetições
- Para cada conjunto, marque como acabado ou não acabado
Podemos atualizar nosso treino despachando ações para o redutor que fizemos anteriormente. Para atualizar o peso, despacharíamos a seguinte ação:
despacho ({ tipo:"UPDATE_WEIGHT", carga útil: { exercícioId, setId, newWeight, }, });
Nosso redutor então atualizará o estado de acordo:
case ACTIONS.UPDATE_WEIGHT: draft.exercises [payload.exerciseId].sets [payload.setId].weight= payload.weight;
O redutor sabe qual registro atualizar, pois damos a ele exercícioId
e setId
:
Despacho({ tipo:"TOGGLE_FINISHED", carga útil: { exercícioId, setId, }, }) } />
Criação de painéis
O painel consiste em dois gráficos: total de exercícios e calorias queimadas por dia. Também queremos exibir a quantidade total de exercícios e as calorias de hoje, desta semana e deste mês.
Isso significa que queremos recuperar todos os exercícios do banco de dados, que podemos obter em nosso gancho useWorkoutDb ()
:
const {isFetchingWorkouts, workouts}=useWorkoutDb ();
Já podemos exibir a quantidade total de treinos:
{isFetchingWorkouts? 0: workouts.length}
Calorias queimadas por dia, semana e mês
Se o exercício mudou e tem pelo menos um exercício, queremos recalcular as calorias:
useEffect (()=> { if (! isFetchingWorkouts && workouts.length) { calcCalories (); } }, [treinos])
Para cada treino, vamos verificar se a data é a mesma de hoje, nesta semana ou neste mês.
const formattedDate=new Date (createdAt.seconds * 1000); const day=format (formattedDate,"d");
Nesse caso, atualizaremos as calorias de acordo, multiplicando-as pelos minutos passados desse exercício:
const newCalories=CALORIES_PER_HOUR * (secondsPassed/3600); if (dayOfYear===day) { setCalories ((calorias)=> ({ ... calorias, hoje: calorias.today + newCalories, })); } }
Tabela de treino
Queremos um gráfico de linhas simples com os meses no eixo xe o número de calorias no eixo y. Também é bom estilizar a área abaixo da linha, então usaremos recarregamentos AreaChart
componente. Simplesmente passamos um array de dados:
Vamos formatar a matriz de dados a seguir. Para informar aos recarregadores que precisa usar o mês para o eixo x, adicionaremos
dentro de nosso AreaChart
.
Para que isso funcione, precisamos usar este formato:
[{mês:"fevereiro", valor: 13},...]
Queremos mostrar a quantidade de exercícios nos últimos três meses, mesmo que não tenha havido exercícios nesses meses. Então, vamos preencher uma matriz com os últimos três meses usando date-fns
e definindo o valor para 0
.
const [data, setData]=useState ([]); deixe lastMonths=[]; const addEmptyMonths=()=> { const hoje=nova data (); para (deixe i=2; i>=0; i--) { const mês=formato (subMês (hoje, i),"LLL"); lastMonths.push (mês); setData ((dados)=> [... dados, {mês, quantidade: 0}]); } };
Criação do gráfico de calorias
Para o gráfico de calorias, queremos mostrar o número de calorias por dia na última semana. Semelhante ao WorkoutChart
, preenchemos nossos dados com uma matriz dos dias da última semana com 0
calorias por dia.
let lastDays=[]; const addEmptyDays=()=> { const hoje=nova data (); para (seja i=6; i>=0; i--) { dia const=formato (subDias (hoje, i),"E"); lastDays.push (dia); setData ((dados)=> [... dados, {dia, calorias: 0}]); } };
Para cada treino, verificaremos se ele ocorreu nos últimos sete dias. Em caso afirmativo, calcularemos o número de calorias queimadas nesse exercício e o adicionaremos à nossa matriz de dados:
const addCaloriesPerDay=()=> { para (const {createdAt, secondsPassed} de workouts) { const day=format (new Date (createdAt.seconds * 1000),"E"); índice const=lastDays.indexOf (dia); if (índice!==-1) { calorias const=CALORIES_PER_HOUR * (segundos Passados /3600); setData ((dados)=> { dados [índice].calorias=dados [índice].calorias + parseInt (calorias); dados de retorno; }); } } };
Se você salvar um novo treino, deverá ver as estatísticas e os gráficos do painel sendo atualizados.
Parabéns, você criou seu aplicativo React fitness! Obrigado por seguir este tutorial.
Você pode encontrar o aplicativo concluído aqui: fitlife-app.netlify.app . O código-fonte pode ser encontrado aqui: github.com/sanderdebr/FitLife/
A postagem Crie um rastreador de condicionamento físico com React e O Firebase apareceu primeiro no LogRocket Blog .