Feedback Page
Now that we have the base for our feedback page, let's add some more logic! I'll break down each piece individually, then show the completed example at the end.
Creating Feedback
We want to have a text input allowing users to add feedback, as well as a button to submit feedback. Then, we can create the feedback on the client-side using Firebase. First, let's make the Firebase query.
lib/db.js
export function createFeedback(data) { return firestore.collection('feedback').add(data)}
Feedback Form
Now, we need a form on the client-side to accept input. Since we've already set up Chakra UI, this becomes much easier! We only want to show the text input if a user is logged in. We can utilize our useAuth
hook for this.
pages/p/[siteId].js
import { useRef } from 'react'import { Box, FormControl, FormLabel, Input, Button } from '@chakra-ui/core'
import { useAuth } from '@/lib/auth'
// ...Rest of the file, redacted to focus on the return
const FeedbackPage = ({ initialFeedback }) => { const auth = useAuth() const inputEl = useRef(null)
return ( <Box display="flex" flexDirection="column" width="full" maxWidth="700px" margin="0 auto" > {auth.user && ( <Box as="form"> <FormControl my={8}> <FormLabel htmlFor="comment">Comment</FormLabel> <Input ref={inputEl} id="comment" placeholder="Leave a comment" /> <Button mt={4} type="submit" fontWeight="medium"> Add Comment </Button> </FormControl> </Box> )} // Only render the feedback if it exists {initialFeedback && initialFeedback.map((feedback) => ( <Feedback key={feedback.id} {...feedback} /> ))} </Box> )}
export default FeedbackPage
Feedback Component
The Feedback
component is a purely presentational component. It uses date-fns
to parse our ISO timestamp and format it. First, install date-fns
.
$ yarn add date-fns
Then, create the Feedback
component.
components/Feedback.js
import React from 'react'import { Box, Heading, Text, Divider } from '@chakra-ui/core'import { format, parseISO } from 'date-fns'
const Feedback = ({ author, text, createdAt }) => ( <Box borderRadius={4} maxWidth="700px" w="full"> <Heading size="sm" as="h3" mb={0} color="gray.900" fontWeight="medium"> {author} </Heading> <Text color="gray.500" mb={4} fontSize="xs"> {format(parseISO(createdAt), 'PPpp')} </Text> <Text color="gray.800">{text}</Text> <Divider borderColor="gray.200" backgroundColor="gray.200" mt={8} mb={8} /> </Box>)
export default Feedback
Saving Feedback
Finally, we need to save the feedback using the createFeedback
function we made earlier. By adding an onSubmit
handler to our form, this will get triggered when the button with type="submit"
is clicked. Let's step through onSubmit
.
- First, we prevent the default behavior of a form reloading the page.
- We create the new feedback object to save to the database.
- We clear out the text input.
- We use React State to add the new feedback to the list so it appears it was added instantly.
- We actually save the feedback in Firestore.
pages/p/[siteId].js
import { useEffect, useRef, useState } from 'react'import { useRouter } from 'next/router'import { Box, FormControl, FormLabel, Input, Button } from '@chakra-ui/core'
import Feedback from '@/components/Feedback'import { useAuth } from '@/lib/auth'import { createFeedback } from '@/lib/db'import { getAllFeedback, getAllSites } from '@/lib/db-admin'
export async function getStaticProps(context) { const siteId = context.params.siteId const { feedback } = await getAllFeedback(siteId)
return { props: { initialFeedback: feedback, }, revalidate: 1, }}
export async function getStaticPaths() { const { sites } = await getAllSites() const paths = sites.map((site) => ({ params: { siteId: site.id.toString(), }, }))
return { paths, fallback: true, }}
const FeedbackPage = ({ initialFeedback }) => { const auth = useAuth() const router = useRouter() const inputEl = useRef(null) const [allFeedback, setAllFeedback] = useState([])
useEffect(() => { setAllFeedback(initialFeedback) }, [initialFeedback])
const onSubmit = (e) => { e.preventDefault()
const newFeedback = { author: auth.user.name, authorId: auth.user.uid, siteId: router.query.siteId, text: inputEl.current.value, createdAt: new Date().toISOString(), provider: auth.user.provider, status: 'pending', }
inputEl.current.value = '' setAllFeedback((currentFeedback) => [newFeedback, ...currentFeedback]) createFeedback(newFeedback) }
return ( <Box display="flex" flexDirection="column" width="full" maxWidth="700px" margin="0 auto" > {auth.user && ( <Box as="form" onSubmit={onSubmit}> <FormControl my={8}> <FormLabel htmlFor="comment">Comment</FormLabel> <Input ref={inputEl} id="comment" placeholder="Leave a comment" /> <Button mt={4} type="submit" fontWeight="medium"> Add Comment </Button> </FormControl> </Box> )}
{allFeedback && allFeedback.map((feedback) => ( <Feedback key={feedback.id || new Date().getTime().toString()} {...feedback} /> ))} </Box> )}
export default FeedbackPage