Understanding the Problem
This assessment involves creating a React application with various components for managing information about fruits. The app requires:
- Setting up routes in App.jsx
- Creating a Navigation component with NavLinks
- Creating a FruitsIndex component that lists fruits
- Creating a FruitShow component that displays details about a specific fruit
- Implementing a FruitForm component with validation and form submission
- Creating a FavoriteFruit component that links to the user's favorite fruit
- Creating a SetFavoriteFruit component that allows changing the favorite fruit
Devising a Plan
- Create App.jsx with defined routes
- Implement Navigation.jsx with NavLink components
- Implement FruitsIndex.jsx to display a list of fruits with links
- Implement FruitShow.jsx to display specific fruit details
- Enhance FruitForm.jsx with form controls and validation
- Implement FavoriteFruit.jsx component using context
- Enhance SetFavoriteFruit.jsx component using context
App.jsx
import { Routes, Route } from "react-router-dom";
import Navigation from "./components/Navigation";
import FruitsIndex from "./components/FruitsIndex";
import FruitShow from "./components/FruitShow";
import FruitForm from "./components/FruitForm";
import FavoriteFruit from "./components/FavoriteFruit";
import SetFavoriteFruit from "./components/SetFavoriteFruit";
import fruits from "./mockData/fruits.json";
function App() {
return (
<>
<Navigation />
<Routes>
<Route path="/" element={<FruitsIndex fruits={fruits} />} />
<Route path="/fruits/new" element={<FruitForm fruits={fruits} />} />
<Route path="/fruits/:fruitId" element={<FruitShow fruits={fruits} />} />
<Route path="/fav-fruit" element={<FavoriteFruit fruits={fruits} />} />
<Route path="/set-fav-fruit" element={<SetFavoriteFruit fruits={fruits} />} />
<Route path="*" element={<h2>Page Not Found</h2>} />
</Routes>
</>
);
}
export default App;
Key points:
- The Navigation component is rendered on every page
- All components receive the fruits data as a prop
- Notice the route order - "/fruits/new" before "/fruits/:fruitId"
- A catch-all route for invalid URLs displays "Page Not Found"
Navigation.jsx
import { NavLink } from "react-router-dom";
function Navigation() {
return (
<nav>
<NavLink to="/">Home</NavLink>
<NavLink to="/fruits/new">Enter a Fruit</NavLink>
<NavLink to="/fav-fruit">Favorite Fruit</NavLink>
<NavLink to="/set-fav-fruit">Set Favorite Fruit</NavLink>
</nav>
);
}
export default Navigation;
Key points:
- Uses NavLink instead of Link to enable active styling
- The exact text content is important for tests to pass
- NavLink elements are wrapped in a nav element for semantic HTML
FruitsIndex.jsx
import { Link } from "react-router-dom";
function FruitsIndex({ fruits }) {
return (
<div>
<h2>Fruits Index</h2>
{fruits.map(fruit => (
<div key={fruit.id}>
<Link to={`/fruits/${fruit.id}`}>{fruit.name}</Link>
</div>
))}
</div>
);
}
export default FruitsIndex;
Key points:
- An h2 with exactly "Fruits Index" text
- Maps through the fruits array to create links
- Each link points to the fruit's detail page
- Uses the fruit's name as the link text
FruitShow.jsx
import { useParams } from "react-router-dom";
function FruitShow({ fruits }) {
const { fruitId } = useParams();
const fruit = fruits.find(fruit => fruit.id === fruitId);
return (
<div>
<h2>{fruit.name}</h2>
<p>Color: {fruit.color}</p>
<p>Sweetness: {fruit.sweetness}</p>
<p>Seeds: {fruit.seeds}</p>
</div>
);
}
export default FruitShow;
Key points:
- Uses useParams to get the fruitId from the URL
- Finds the fruit with the matching ID
- Displays the fruit's name in an h2
- Shows color, sweetness, and seeds information
- Sweetness format must be "Sweetness: [value]"
FruitForm.jsx
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
const COLORS = [
"red",
"orange",
"yellow",
"green",
"blue",
"purple"
];
function FruitForm({ fruits }) {
const navigate = useNavigate();
// State for form fields
const [name, setName] = useState("");
const [sweetness, setSweetness] = useState(1);
const [color, setColor] = useState("orange");
const [seeds, setSeeds] = useState("yes");
// State for validation errors
const [errors, setErrors] = useState({
name: "",
sweetness: ""
});
// Validation function
useEffect(() => {
const validateForm = () => {
const newErrors = {
name: "",
sweetness: ""
};
// Name validation
if (name.length < 3) {
newErrors.name = "Name must be 3 or more characters";
} else if (name.length > 20) {
newErrors.name = "Name must be 20 characters or less";
} else if (fruits.some(fruit => fruit.name.toLowerCase() === name.toLowerCase())) {
newErrors.name = "Name already exists";
}
// Sweetness validation
if (sweetness < 1 || sweetness > 10) {
newErrors.sweetness = "Sweetness must be between 1 and 10";
}
setErrors(newErrors);
};
validateForm();
}, [name, sweetness, fruits]);
// Check if form is valid
const isFormValid = !errors.name && !errors.sweetness;
// Handle form submission
const handleSubmit = (e) => {
e.preventDefault();
// Create fruit object
const fruitData = {
name,
sweetness,
color,
seeds
};
// Log the fruit data
console.log(fruitData);
// Navigate to home page
navigate("/");
};
return (
<form
className="fruit-form"
onSubmit={handleSubmit}
>
<h2>Enter a Fruit</h2>
<label>
Name
<input
type="text"
name="name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
<p className="errors">{errors.name}</p>
<label>
Select a Color
<select
value={color}
onChange={(e) => setColor(e.target.value)}
>
{COLORS.map(color => (
<option
key={color}
value={color}
>
{color}
</option>
))}
</select>
</label>
<label>
Sweetness
<input
type="number"
name="sweetness"
value={sweetness}
onChange={(e) => setSweetness(e.target.value)}
/>
</label>
<p className="errors">{errors.sweetness}</p>
<label>
<input
type="radio"
value="no"
name="seeds"
checked={seeds === "no"}
onChange={() => setSeeds("no")}
/>
No Seeds
</label>
<label>
<input
type="radio"
value="yes"
name="seeds"
checked={seeds === "yes"}
onChange={() => setSeeds("yes")}
/>
Seeds
</label>
<button
type="submit"
disabled={!isFormValid}
>
Submit Fruit
</button>
</form>
);
}
export default FruitForm;
Key points:
- Creates controlled inputs for all form fields
- Initial values: empty string for name, 1 for sweetness, "orange" for color, "yes" for seeds
- Validation runs on initial render and after every input change
- Validation rules:
- Name must be 3-20 characters
- Name must be unique
- Sweetness must be between 1 and 10
- Submit button is disabled when there are validation errors
- On submission: logs form data and navigates to home page
FavoriteFruit.jsx
import { useContext } from "react";
import { Link } from "react-router-dom";
import { FavFruitContext } from "../context/FavFruitContext";
const FavoriteFruit = ({ fruits }) => {
const { favFruitId } = useContext(FavFruitContext);
const favoriteFruit = fruits.find(fruit => fruit.id === favFruitId);
return (
<div>
<h2>Favorite Fruit</h2>
<Link to={`/fruits/${favFruitId}`}>{favoriteFruit.name}</Link>
</div>
);
}
export default FavoriteFruit;
Key points:
- Uses useContext to get the favFruitId from FavFruitContext
- Finds the fruit with the matching ID
- Displays an h2 with exactly "Favorite Fruit" text
- Creates a Link to the favorite fruit's detail page
- Uses the fruit's name as the link text
SetFavoriteFruit.jsx
import { useContext } from "react";
import { FavFruitContext } from "../context/FavFruitContext";
const SetFavoriteFruit = ({ fruits }) => {
const { favFruitId, setFavFruitId } = useContext(FavFruitContext);
const handleChange = (e) => {
setFavFruitId(e.target.value);
};
return (
<div className="set-fav-fruit">
<h2>Select your Favorite Fruit</h2>
<label>
<select
value={favFruitId}
onChange={handleChange}
>
{fruits.map(fruit => (
<option
key={fruit.id}
value={fruit.id}
>
{fruit.name}
</option>
))}
</select>
</label>
</div>
);
}
export default SetFavoriteFruit;
Key points:
- Uses useContext to get favFruitId and setFavFruitId from FavFruitContext
- Creates a controlled select input with the current favFruitId
- Updates the favFruitId when the selection changes
- Maintains the exact heading text required by the tests
FavFruitContext.jsx
This file is already provided but included here for completeness:
import { createContext, useState } from 'react';
export const FavFruitContext = createContext();
export default function FavFruitProvider(props) {
const [favFruitId, setFavFruitId] = useState('1');
return (
<FavFruitContext.Provider
value={{
favFruitId,
setFavFruitId
}}
>
{props.children}
</FavFruitContext.Provider>
)
}
Key points:
- Creates a context for sharing the favorite fruit ID
- Initial state is set to "1" (the first fruit)
- Provides the context value to child components
Key React Concepts
Routes and Navigation
React Router provides client-side routing capabilities:
- Routes/Route: Define mappings between URL paths and components
- NavLink: Special link that knows when it's active (gets an "active" class)
- Link: Basic navigation without the active state
- useParams: Hook to access URL parameters (like fruitId)
- useNavigate: Hook for programmatic navigation
Forms and Validation
React forms typically use controlled components:
- Controlled inputs: Form inputs whose values are controlled by state
- Real-time validation: Using useEffect to validate as the user types
- Form submission: Managing form data and handling submission
Context API
React's Context API allows sharing state across components:
- createContext: Creates a context to share data
- Context.Provider: Wraps components to provide context
- useContext: Hook to access context in components
Common Gotchas
Route Order Matters
In React Router, routes are evaluated in order. More specific routes like "/fruits/new" must come before wildcard routes like "/fruits/:fruitId".
Exact Text Content
Tests often check for specific text content. For example, the heading must be exactly "Favorite Fruit" not "My Favorite Fruit".
Form Validation Timing
Validation must run on initial render and after every input change, not just during form submission.
Default Values
Pay attention to the required default values for inputs (color="orange", seeds="yes", etc).
Testing
To test the application:
- Run
npm installto install dependencies - Run
npm testto run all tests - Run
npm test 1-Routesto test specific components - Run
npm run devto view the app in a browser
The tests check for:
- Correct routing configuration
- Proper navigation links
- Correct display of fruit data
- Form validation and submission
- Context usage