DEV Community

Cover image for State Adventures: My First React Project
Christine Contreras
Christine Contreras

Posted on

State Adventures: My First React Project

I am finally finished with my second phase of Flatiron. Like at the end of every phase, we had to create a project that encompasses all of our learnings so far. Last phase, I created a project that incorporated Javascript and APIs (you can view it here). This phase, I learned how to use the React framework.

What I Used In My Project

  • React framework
  • The National Park Service API is used to pull in national park information
  • Weatherstack API is used to pull in current weather for each national park
  • Google Maps API is used to show campgrounds for each national park
  • JSON Server was used in this project to save each national park
  • React Router DOM used to create navigation in the single-page app
  • Material UI to style my app

Project Overview

During the pandemic, when everything started closing down, I got interested in spending more time in nature. I have always wanted to go to more national parks but didn’t know where to start. To solve that, I created the State Adventures app, which lets you search national parks by state, see park overviews, and view campsites.

See National Parks by State

-When the app loads, an automatic call to the National Parks API is requested and four random national parks are generated for the user. This allows content to populate before the form is used.
See default parks on load

//snippet of home component
 export class Home extends Component {
   state = {
       selectedParks: [],
       selectedState: null,
       defaultParks: [],
   }

   componentDidMount() {
       //fetch default parks
       fetch(`https://developer.nps.gov/api/v1/parks?api_key=${apiKey}&limit=50`)
       .then(response => response.json())
       .then(json => {
           const newArray = []
           for(let i = 0; i < 4; i++) {
               newArray.push(json.data[Math.floor(Math.random()*json.data.length)])
           }
           this.setState({
           defaultParks: newArray,
           })

       })
   }
Enter fullscreen mode Exit fullscreen mode

-The hero of the app contains a controlled form that lets you choose a state to view. Once the form is submitted, the state is sent back to the parent component in a callback function to make another request to the National Parks API. The national parks are each populated on their own card and the state chosen is added to the page title.

Use form to see national parks by state

//controlled form component
export class Form extends Component {
   state = {
       stateAbbreviations: [
           'AL','AK','AS','AZ','AR','CA','CO','CT','DE','DC','FM','FL','GA',
           'GU','HI','ID','IL','IN','IA','KS','KY','LA','ME','MH','MD','MA',
           'MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND',
           'MP','OH','OK','OR','PW','PA','PR','RI','SC','SD','TN','TX','UT',
           'VT','VI','VA','WA','WV','WI','WY'
          ],
          selectedState: ""
   }

   handleChange = (event) => {
       this.setState({
           [event.target.name]: event.target.value
       })
   }

   handleSubmit = (event) => {
       event.preventDefault()
       this.props.fetchingParks(this.state.selectedState)
   }


   render() {
       return (
           <form className="hero-form" onSubmit={this.handleSubmit}>
               <ButtonGroup>
                   <FormControl variant="outlined" color="secondary">
                       <InputLabel id="select-state">Select State</InputLabel>
                       <Select
                       labelId="select-state"
                       name="selectedState"
                       value={this.state.selectedState}
                       onChange={this.handleChange}
                       >
                           {
                           this.state.stateAbbreviations.map(state => (
                               <MenuItem value={state} key={state}>{state}</MenuItem>
                           ))
                           }
                       </Select>
                   </FormControl>

                   <Button
                   type="submit"
                   variant="contained"
                   color="primary"
                   size="large"
                   >
                       Find Parks
                   </Button>
               </ButtonGroup>
           </form>
       )
   }
}

//snippet of fetching Parks API from home component
fetchParksApi = (stateAbbr = null) => {
       //fetch parks by state
       fetch(`https://developer.nps.gov/api/v1/parks?stateCode=${stateAbbr}=&api_key=${apiKey}`)
       .then(response => response.json())
       .then(json => {
         this.setState({
           selectedParks: json.data,
           selectedState: stateAbbr
         })

       })
     }
Enter fullscreen mode Exit fullscreen mode

See Details of a National Park

-If you click “view park,” you will be taken to an overview page about the national park. You’ll see information about the park, park hours, park directions, entrance fees, activities, and current park weather.
Overview of national park

-When the overview component is rendered, an API call is made to the Weatherstack API to pull in current weather based on the park's address. The rest of the information on the page comes from the National Parks Service API.

//API call for Weather
export class ParkWeather extends Component {
   state = {
       temp: null,
       tempDetails: null
   }

   componentDidMount(){
       const validAddress = this.props.address.find(a => a.type === "Physical")

       fetch(`http://api.weatherstack.com/current?access_key=${apiKey}&units=f&query=${validAddress.postalCode}`)
       .then(res => res.json())
       .then(json => {
            this.setState({
               temp: json.current.temperature,
               tempDetails: json.current.weather_descriptions[0]
           })
       })
   }
Enter fullscreen mode Exit fullscreen mode

-You can view the park's campsites by clicking on the page's sub-navigation “camping” link. When the camping component is rendered it makes a separate call to the National Parks Service API to pull in campsites for the national park and add them to the components state. If there aren’t any campgrounds, a muted screen will show to the user.
Camp site page when no campsites are available

-If there are campsites, the Google Maps API will plot them onto the map. When a campsite is clicked, the campsite information will display. This includes campsite description, hours, cost, reservation policy, and directions.
Camp site page when there are campsites available

//camping component snippet
export class ParkCamping extends Component {
   state = {
       campgrounds: [],
       selectedCampground: null
   }

   componentDidMount() {

       fetch(`https://developer.nps.gov/api/v1/campgrounds?parkCode=${this.props.parkcode}=&api_key=${apiKey}&limit=500`)
       .then(res => res.json())
       .then(json => {
           this.setState({
               campgrounds: json.data
           })
       })
   }

   handleCampgroundClick = (campground) => {
       this.setState({
           selectedCampground: campground
       })
   }

   handleCampgroundWindowClick = () => {
       this.setState({
           selectedCampground: null
       })
   }
   render() {
       const height = document.getElementById('park-details').offsetHeight

       return (
           <>
           <Grid item
           xs={12} sm={12} md={this.state.selectedCampground ? 6 : 9}
           className="details-map">
        //Google API map
               <CampingMap
               longitude={parseInt(this.props.longitude)}
               latitude={parseInt(this.props.latitude)}
               campgrounds={this.state.campgrounds}
               selectedCampground={this.state.selectedCampground}
               handleCampgroundClick={this.handleCampgroundClick}
               handleCampgroundWindowClick={this.handleCampgroundWindowClick}
                />
           </Grid>

           {this.state.selectedCampground && (
               <CampingInfo
               height={height}
               campground={this.state.selectedCampground}/>
           )}
           </>
       )
   }
}
Enter fullscreen mode Exit fullscreen mode

Saving a National Park

-You can “like” a national park directly from the national park card or within the overview page. When you “like” a park, it is added to state and posted to the JSON server so that the liked park persists throughout app changes and/or refreshes. It will then show up under your “liked parks”.
saving a national park

-If the heart is clicked again it will remove the park from state, remove it from the JSON server, and no longer show up in your “liked parks”
un-saving a national park

//app component
  state = {
   savedParks: []
 }

handleSaveParks = (newPark) => {
   const configData = {
     method: 'POST',
     headers: {
       'accept': 'application/json',
       'content-type': 'application/json'
     },
     body: JSON.stringify(newPark)
   }

   fetch('http://localhost:3000/parks', configData)

   this.setState(previousState => ({
     savedParks: [...previousState.savedParks, newPark]
   })
 )
 }

 handleUnsavePark = (removePark) => {
   const newSavedParks = this.state.savedParks.filter(park => park !== removePark)

   this.setState({
     savedParks: newSavedParks
   })

   fetch(`http://localhost:3000/parks/${removePark.id}`, {
     method: 'DELETE'
   })
 }

//snippet of park card with like button
<CardActions className="card-actions">
                   { parkIsSaved === undefined ?
                   <Tooltip title="Save Park" arrow>
                       <IconButton color="primary"
                       onClick={() => handleSaveParks(parkInfo)}
                       >
                           <FavoriteBorderIcon />
                       </IconButton>
                   </Tooltip>
                   :
                   <Tooltip title="Remove Park" arrow>
                   <IconButton color="primary"
                   onClick={() => handleUnsavePark(parkInfo)}
                   >
                       <FavoriteIcon />
                   </IconButton>
                   </Tooltip>
                   }

                   <Button
                   variant="text"
                   size="large"
                   color="primary"
                   endIcon={<TrendingFlatIcon />}
                   onClick={viewParkButtonClick}
                   >
                       View Park
                   </Button>
               </CardActions>
Enter fullscreen mode Exit fullscreen mode

Creating Navigation with a Single Page App

With a single page app the beauty of it is there is only one page that all elements populate on. However, it doesn’t allow for a user to navigate the site with URLs. This is where I used React Router to help build navigation within my React App.
Router structure

-I utilized The React Router’s built-in history method of push() to direct the user to the URL path they intended to visit. I used it in the primary navigation and the overview sub-navigation.

-I used the built-in location pathname property to show which page the user is on. If it matched the current path I added a class to the element to change the link’s styling.
current link styling

-I also used the built-in history method of goBack() on the park-details pages to allow the user to go back to the homepage or overview page easily if needed.
demo of using the go back button

Final Thoughts

React helped me better understand object oriented programming (OOP) by the way it is structured. Class components can be used as many times as needed and have properties they can pass down to their children components or carry individual attributes known as state. It is becoming more debatable if React is more OOP or functional now with hooks. Nevertheless, it helped me understand OOP better than I did before learning the framework.

I had a fun time learning React and Material UI to efficiently build out a single-page app. It gave me more time to build out features rather than focusing on styling and function interactions like I did for my first project. I focused a lot on React class components in this build but want to spend more time understanding React functional components and hooks in the future.

Top comments (0)