This guide walks you through building the Emoji Personality Test App using Vite with the React (JavaScript) template. By the end, you’ll understand how each part works and how to complete the handleClick function to move emojis between columns, track the count, and reveal results.
.overall-emojis-container: lays out the three buttons horizontally
.overall-emoji-lists-container: holds the two list columns
.individual-emoji-list-container: styles each column
.results-modal-container: full-screen overlay for results
No changes are needed here unless you want to customize visuals.
Adjusting Emoji Size in Lists
If your emoji list items overflow, reduce their font sizes in style.css:
/* Liked emojis (larger) */
.individual-emoji-list-container:first-of-type li {
font-size: 30px; /* was 45px */
}
/* Unselected emojis (smaller) */
.individual-emoji-list-container:last-of-type li {
font-size: 20px; /* was 25px */
line-height: 40px; /* adjust to match size */
}
Step 5. Main Logic (AppEmojiPersonality.jsx)
The following is the complete AppEmojiPersonality.jsx file with comments highlighting each part:
import React from 'react'
import ResultsModal from './components/ResultModal'
import EmojiLists from './components/EmojiLists'
import emojis from './data/emojis'
import { nanoid } from 'nanoid'
import './style.css'
export default function App() {
// State to track emojis liked by the user
const [likedEmojis, setLikedEmojis] = React.useState([])
// State to track emojis the user didn't click
const [passedEmojis, setPassedEmojis] = React.useState([])
// Current set of three emojis to display
const [currentEmojis, setCurrentEmojis] = React.useState(getRandomEmojis)
// Control whether the results modal is shown
const [showResults, setShowResults] = React.useState(false)
// Control whether the final results are ready (after animation)
const [resultsReady, setResultsReady] = React.useState(false)
/**
* handleClick — invoked when an emoji button is clicked.
* 1. Add clicked emoji to likedEmojis.
* 2. Add the other two emojis to passedEmojis.
* 3. Refresh currentEmojis via getRandomEmojis().
*/
function handleClick(event) {
// Determine which emoji was clicked
const clicked = event.target.innerText
// 1️⃣ Add the clicked emoji to the end of likedEmojis array
setLikedEmojis((prev) => [...prev, clicked])
// 2️⃣ Collect the other two emojis and add them to passedEmojis
const others = currentEmojis.filter((e) => e !== clicked)
setPassedEmojis((prev) => [...prev, ...others])
// 3️⃣ Generate three new random emojis for the next round
setCurrentEmojis(getRandomEmojis)
}
/**
* getRandomEmojis — returns an array of three random emojis
* picked from the data/emojis.js array.
*/
function getRandomEmojis() {
function chooseRandomEmoji() {
return emojis[Math.floor(Math.random() * emojis.length)]
}
return new Array(3).fill('').map(() => chooseRandomEmoji())
}
/** Show the results modal */
function getResults() {
setShowResults(true)
}
/** Reset all state for a new test */
function reset() {
setLikedEmojis([])
setPassedEmojis([])
setShowResults(false)
setResultsReady(false)
}
// When showResults becomes true, start a timer to trigger the "resultsReady" state
React.useEffect(() => {
if (showResults) {
setTimeout(() => {
setResultsReady(true)
}, 2000)
}
}, [showResults])
// Utility to generate list items with unique keys
function generateListItems(element) {
return <li key={nanoid()}>{element}</li>
}
return (
<div className="wrapper">
{/* Counter showing how many emojis have been liked out of 10 */}
<div className="results-counter">{likedEmojis.length} / 10</div>
{/* Modal component for displaying final results */}
<ResultsModal
showResults={showResults}
getResults={getResults}
resultsReady={resultsReady}
reset={reset}
generateListItems={generateListItems}
likedEmojis={likedEmojis}
/>
<h1>Emoji Personality Test</h1>
{/* Show three emoji buttons until 10 selections, then show Get Results */}
{likedEmojis.length < 10 ? (
<div className="overall-emojis-container">
<button onClick={handleClick}>{currentEmojis[0]}</button>
<button onClick={handleClick}>{currentEmojis[1]}</button>
<button onClick={handleClick}>{currentEmojis[2]}</button>
</div>
) : (
!showResults && (
<button className="get-results-button" onClick={getResults}>
Get Results
</button>
)
)}
{/* Lists showing liked vs. passed emojis */}
<EmojiLists
likedEmojis={likedEmojis}
passedEmojis={passedEmojis}
generateListItems={generateListItems}
/>
</div>
)
}
Main Logic (Step 5)
State Initialization: Five useState hooks manage distinct parts of the UI:
likedEmojis and passedEmojis collect user choices over time.
currentEmojis holds the three emojis displayed each round, initialized by calling getRandomEmojis.
showResults and resultsReady control the flow and timing of the results modal.
Emojis Selection Flow (handleClick):
Identify Clicked Emoji: using event.target.innerText.
Update Liked List: appends the clicked emoji to the existing array (setLikedEmojis(prev => [...prev, clicked])).
Update Passed List: filters out the clicked emoji and appends the remaining two (setPassedEmojis(prev => [...prev, ...others])).
Next Round: refreshes currentEmojis by re-invoking getRandomEmojis, triggering a re-render with new choices.
Randomization (getRandomEmojis):
Chooses three emojis independently by randomly indexing into the imported emojis array.
Ensures every round offers fresh, unpredictable options.
Results Cycle:
Trigger: After 10 selections (reflected by likedEmojis.length), the Get Results button becomes visible.
Timing: Clicking it sets showResults true, and an effect hook starts a 2 s timer before marking resultsReady — enabling a simple "loading" animation via CSS.
Utility Helpers:
generateListItems ensures each list item has a unique key, preventing React warnings and enabling proper reconciliation.
// Some code
export default function ResultsModal({
showResults,
resultsReady,
getResults,
reset,
likedEmojis,
generateListItems,
}) {
if (!showResults) return null
return (
<div className="results-modal-container">
<div className="modal-inner-container bounce-top">
{resultsReady ? (
<ul>{likedEmojis.map(generateListItems)}</ul>
) : (
<p onClick={getResults} className="get-results-button">
Get Results
</p>
)}
{resultsReady && (
<>
<p>You have a great personality! </p>
<p onClick={reset} className="try-again-button">
Try Again
</p>
</>
)}
</div>
</div>
)
}
Explanation for Display Components (Step 6)
Props-Driven Rendering:
Both EmojiLists and ResultsModal receive state and helper functions via props — keeping them stateless and reusable.
Conditional UI (ResultsModal):
Returns null until showResults is true.
Inside, it toggles between two states: a Get Results button (waiting on user click) and the final emoji list (once resultsReady is true), plus a Try Again button to reset.
Flex Layout:
<div className="overall-emoji-lists-container"> uses CSS flexboxes to lay out columns side by side.
Each <ul> wraps emojis with appropriate font sizes for visual hierarchy.
Separation of Concerns:
Core logic resides in the main App component; display-only components simply render based on props, simplifying testing and maintenance.
Display Components (Section 6)
Props-Driven Rendering:
Both EmojiLists and ResultsModal receive state and helper functions via props — keeping them stateless and reusable.
Conditional UI (ResultsModal):
Returns null until showResults is true.
Inside, it toggles between two states: a Get Results button (waiting on user click) and the final emoji list (once resultsReady is true), plus a Try Again button to reset.
Flex Layout:
<div className="overall-emoji-lists-container"> uses CSS flexboxes to lay out columns side by side.
Each <ul> wraps emojis with appropriate font sizes for visual hierarchy.
Separation of Concerns:
Core logic resides in the main App component; display-only components simply render based on props, simplifying testing and maintenance.
Step 7. Run & Test
Start the dev server:
npm run dev
Interact with the three emojis.
Observe:
Emojis move into the correct columns.
Counter increments from 0 → 10.
At 10, click Get Results, then Try Again to reset.