DEV Community

Kiana
Kiana

Posted on • Edited on

First react app creation.

When I started brainstorming what to build for my first React project I wanted something that would keep me motivated through the errors I knew I would run into. I decided on a simple quiz app themed after The Office since it's my favorite show.
This quiz would display quotes that a user has to select who they believe said it, At the end the user is redirected to a page that displays their score. I felt this would keep me motivated and excited because it's a quiz I wanted to be able to take myself!

The main building requirements I had to meet were,
1.Fetching Rails API backend to persist data.
2.using Redux and redux-thunk middleware to modify state.
3.Using React-router.

I started building this project by creating my backend. I followed a walk through of creating a rails API where the data would be pulled from. I wrote out all the data myself rather than finding an open-source API to build a front end to pair with because I was building a very specific project that needed direct quotes and specific characters as answers.
This was certainly the most tedious part and of course as a developer I don't want to be responsible for creating and hardcoding all my data but I felt for the project I was doing I wanted to really focus on creating what I wanted and not relaying on finding an open-source api to fit. Maybe later down the road when I have a firmer understanding Id love to challenge myself to extract nested data or manipulate data to fit my ideas.

From there I started building the frontend with 'create-react-app', another walk through that made the set up of this project very easy. I started with mapping out the lifecycle of the components and where/how they would be displayed and updated.
At the top layer, in my App component React router made a painless set up of wrapping my components so they could be easily navigated to. React-Router allows changing the browser URL while staying in sync with the UI as the user(or developer) navigates. React router really made debugging and setting up each page a breeze.
Since it uses component structure to call components, I could display the appropriate information without the page refreshing while I was in the middle of working on a component.

Home would be a presentational component to introduce the user to the quiz and the option to link a user to other pages in the app.
The Quiz component would be a container component that would have all the logic for the app along with rendering JSX.
The Result page would simply update the state of 'score' and display it while also posting to the API. Finally, Highscores would be a leaderboard to compare different users scores.

App.js
return (
  <div className="App">
     <Router>
      <Switch >
        <Route exact path="/">
          <Home />
        </Route>
         <Route exact path="/quiz"> 
             <quiz/>
        </Route>
        <Route exact path="/results">
          <Results />
        </Route>
         <Route exact path="/highscore">
          <Highscore/>
        </Route>
      </Switch>
      </Router>
    </div> 
  );
Enter fullscreen mode Exit fullscreen mode

I had to create a store using thunk-middleware. I created a separate store folder with action, reducer and selector files.
The action folder is where I fetched data from my backend, the reducer provided access to that data for the store and in my selector file I used the react hook of useState which allowed me to extract data from the Redux store state and pass it to my Quiz component.

Setting up my action to fetch the API data certainly took a bit of preplanning for how I wanted to work with the data. I decided on a promise.all because I needed two sources of data from my API, getting the questions as well as the highscores that were stored. I could have also separated out fetching these but seeing as promise.all returns an array I could easily map over the data, render it to json, store it in state in my reducer to be passed through a selector and call on the index throughout my app, Keeping it DRY.

action.js

export function fetchQuestions(){
  console.log("c")
  return function(dispatch){
  Promise.all([
    fetch("http://127.0.0.1:3000/questions"),
    fetch("http://127.0.0.1:3000/scores",
  { method:"GET",
headers:{ "Content-Type":"application/json",
          "Accept":"application/json"
}
})
]).then(function (responses) {
    return Promise.all(responses.map(function (response) {
    return response.json();
    }));
}).then(data => {
   dispatch({ type: fetchSuccess, payload: data })
    console.log(data);
}).catch(function (error) {
    console.log(error);
})}};
Enter fullscreen mode Exit fullscreen mode

Because this was the first time I worked with a promise.all I had some console.logs just to follow what was being executed.

reducer.js

const quizReducer = (state = {}, action) => {
    switch (action.type){
        case fetchRequest:
             return state
        case fetchSuccess:
        return {...state, data:action.payload}
        default:
        return state    
    }    
}
Enter fullscreen mode Exit fullscreen mode
selector.js
import { useSelector } from "react-redux";
export const getData = (state) => {
    return state.data
}
export default function useData(){
    const data = useSelector(getData) 
    return{data}}
Enter fullscreen mode Exit fullscreen mode

Back in my app file I used react hooks to pass this new data to my app.
of course more Console.logs to follow all my data around...

App.js
const dispatch = useDispatch();
const data = useSelector(getData)
useEffect (() => {
  console.log("a")
    dispatch(fetchQuestions());
  console.log("b")
},
 [dispatch]);
console.log(data)
Enter fullscreen mode Exit fullscreen mode

Now I had an easy array of data to work with that I could pass as a prop throughout my app and call in the specific index.
All of this made possible and easy by middleware! Of course for all of this to work within my index file I had to wrap my app component in Provider, define the store and give my app access to it.

index.js
const store = createStore(quizReducer,applyMiddleware(thunk))
ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
        <App />
    </Provider>
  </React.StrictMode>,
document.getElementById('root')
)
Enter fullscreen mode Exit fullscreen mode

I also updated my App.js to be able to pass the data through as props to each route.

return (

  <div className="App">
     <Router>
      <Switch >
        <Route exact path="/">
          <Home />
        </Route>
         <Route exact path="/quiz"> 
           <Quiz quotes={data}/> 
        </Route>
        <Route 
        path='/Result'
        render={(props) => (
        <Result {...props}/>)}
          />
         <Route exact path="/highscore">
          <Highscore highscore={data}/>
        </Route>
      </Switch>
      </Router>
    </div> 
  );

}

Enter fullscreen mode Exit fullscreen mode

After my App file was set up with the routes to each component, and my index file was wrapping App with
the Provider component making the Redux store available to these components, I started to build out the Quiz class which would be all of the main logic for the app.

class Quiz extends Component {
    state = {
        questionBank: [],
        score: 0,
        responses: 0
    };

    getQuestions = () => {
        quiz().then(question => {
            this.setState({
                questionBank: question
            });

        });
    };
    computeAnswer =
    (answer, correctAnswer) => {
        if (answer === correctAnswer) {
      this.setState({
     score: this.state.score + 1
});
        }
      this.setState({
            responses: this.state.responses < 5 ? this.state.responses + 1 : 5
        })
    }
    componentDidMount(){
        this.getQuestions();
    }
    render() {
        return (
        <div className="container">
        <div className="title">IT IS THE OFFICE QUIZ.</div>
        <div className="intro"> WHO SAID IT?</div>
        {this.state.questionBank.length > 0 && 
        this.state.responses < 5 &&
        this.state.questionBank.map(
            ({question, answers, correct, questionId}) => (
        <QuestionBox question={question} options={answers} key={questionId} 
        selected={answer => this.computeAnswer(answer,correct)}
        /> ) 
        )}
        {this.state.responses === 5 ? (<Result score={this.state.score}/>) : null}
        </div> 
    )
}
}
ReactDom.render(<Quiz />, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

within this class I called my QuestionBox component and passed through the question, answers and selected props which would then allow the the answer to be selected and determine if it was correct!

const QuestionBox = ({question, options, selected}) => {
  const [answer, setAnswer] = useState(options);
  return (
    <div className="questionBox">
      <div className="question">"{question}"</div>
      {answer.map((text, index) => (
        <button
          key={index}
          className="answerBtn"
          onClick={() => {
              setAnswer([text]);
              selected(text);
          }}
        >
          {text}
        </button>
      ))}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Once I had this working how I wanted by pushing the selected answer into its own array, I built out some CSS and logic for the home page and result page since this was primarily what was being displayed.

home.js

const Home = (props) => {

    return (
        <div className="container">
            <div className="welcome">
            <div className="title">
              <h1>IT IS THE OFFICE QUIZ.</h1>
            </div>
             <div className="intro">
               <h2> Think you know who said it?</h2>
                <h4>Test your 'The Office' quotes knowledge</h4>
             </div>
            <div className="home">
               <h3> Its harder than you think... </h3>
                <small>(that's what she said)</small>
               </div> 
                <button className="start">
                    <Link to="/quiz">Lets Play!</Link>
                    </button>
                    <button className="homescoreBtn"
                    onClick={props.highscore}>
                  <Link to="/highscore">Highscores</Link>
        </button>
        </div>
        </div>
    )
    }
Enter fullscreen mode Exit fullscreen mode

The results page showed the users score from setState and also rendered a PopUp component for a user to submit their name. I decided on a pop up as a challenge for myself, Pop up windows are every where in every day life and I wanted to take an opportunity to learn the basics!

Results.js 
const [showPopup, setShowPopup] = useState(false);
  useEffect(() => {
    setTimeout(() => setShowPopup(true), 2000);
}, []);

    return( 
         <div className="container">
             { showPopup ? <PopUp highscore={`${props.location.state}`} /> : null }

              <div className="intro">IT IS THE OFFICE QUIZ.</div>
             <div className="home">
               <h3> That wasn't so hard... </h3>
                <small>(that's what she said)</small>
               </div> 
  <div>


      </div>

        <div className="score-board">
        <div className="score">{`You answered ${props.location.state}/ 5 correct!`}</div>
        <div className="gif">
        <iframe className="gif" src="https://giphy.com/embed/8VrtCswiLDNnO" width="580" height="450" frameBorder="0" class="giphy-embed" allowFullScreen></iframe><p><a href="https://giphy.com/gifs/the-office-nbc-8VrtCswiLDNnO"></a></p>
        </div>
        <div className="tryagain">“Fool me once, strike one. Fool me twice, strike three.” -Michael Scott</div>
        <button className="playBtn"
         onClick={props.playAgain}>
             <Link to="/quiz">Play again?</Link>
        </button>
        <button className="scoreBtn">
        <Link to="/">Home</Link>
         </button>

    </div>
    </div>
    )
    }



Popup.js
const PopUp = (props) => {

  const [open, setOpen] = useState(false);

  const initialFormData = {name: " ", highscore: props.highscore}
  const [formData, updateFormData] = useState(initialFormData);

  const handleChange = (e) => {
    updateFormData({
      ...formData,
      [e.target.name]: e.target.value.trim()
    });
  };



    const handleSubmit = () => {
    console.log(formData);
    fetch( "http://localhost:3000/scores", 
                    {
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(formData),
                    method: "post",
                     })
                     .then (console.log);

}

    return  (
        <nav className={open ? "open" : null}>
          <div className="popup">
          <div className="popupform"> identity theft is not a joke.</div> 
          <form className="formBtn" >
              <label> Name:
          <input type="text" name="name" id="name" onChange={handleChange} />
             </label>
            </form>
                      <button className="formsubmit" value="submit"
        onClick={() => {
        setOpen(!open);
        handleSubmit();
        }}
      >
          Submit
        </button>


    </div>
   </nav>
    );
}
Enter fullscreen mode Exit fullscreen mode

Creating the pop up component was also an easy way I could get the username data and score data together and post to the database.

Finally to make sure all of these components had access to each other and the data I had to make sure all the files had the proper imports defined

App.js

import React from "react";
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'
import "./assets/style.css";
import Quiz from "./components/quiz";
import Home from "./components/home";
import Highscore from "./components/highscore";
import Result from "./components/Result";
import { useEffect} from "react";
import {fetchQuestions} from './store/actions/action';
import {useDispatch, useSelector} from "react-redux"; 
import {getData} from "./store/selectors/selector";

index.js

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import quizReducer from './store/reducers/reducer'
import "./assets/style.css";
import App from "./App";

reducer.js

import { fetchSuccess, fetchRequest, fetchQuestions} from "../actions/action"
import useData from "../selectors/selector"
Enter fullscreen mode Exit fullscreen mode

And so on throughout the rest of the components!
The biggest challenge I faced during this project was passing data through props properly! It took some react doc reading to fully understand breaking down my project into thin vertical slices and working one problem at a time vs. wanting to just find a single solution to fix all my problems! taking the time to uncomment and console.log small things and to work at a slower pace to really grasp the flow of what parts of the data needed to be where and what state needed to be changed and updated is something that can only be learned from actually going through step by step on a project. I do love the ease and flexibility the React library provides.

Top comments (0)