The hitchhiker's guide to building a React + Flask web app (part3).

The hitchhiker's guide to building a React + Flask web app (part3).

introductions

So we did a lot of assumption for the backend in the last part, now we will build our frontend to interprete and represent our data. We're using React.js, React.js is a JavaScript library for building user interfaces. It is the view layer for web applications. React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.

What makes React different and special from other frameworks is it's virtual DOM. It has a copy of the actual DOM, so each changes you make in the React app is done to the virtual DOM, it then compares it to the real DOM and change only those properties in the real DOM that was changed in the virtual DOM, eliminating the problem of refreshing and re-rendering of the DOM every time a change is made to the client side.

We would use create-react-app which is a quick react project setup and we'll be using typescript alongside. So, in our root folder that is the Todo-app, you can just do

npx create-react-app client --template typescript

If you run into any problem, I'll advise instead of spending hours trying to solve, copy the code and paste in your browser, chances are other people have had similar problems. To avoid too much problem, upgrade your node version and create-react-app.

npm i n -g stable

This command above will update your node to the latest stable version.

After setting this up, you will discover that when we try to send a request to our server, even when start it up, it won't go through, we would treat this at the end of this section.

Prerequisites

Aside the prerequisites from the previous part, you also need

  • A basic knowledge of javascript vanilla
  • A basic knowledge of React.js
  • A basic knowledge of typescript is helpful but not compulsory
  • Create-react-app project setup

Imports

We should now navigate to the App.tsx file or APp.js file if you're using the JS template.

Some imports have been made for you already, you'll be adding to them.

import React, {useEffect, useState} from 'react';
import './App.css';

We'll be using useEffect hook and useState, so we needed to import it.

First

Then you can clear the content of the App function leaving only the outermost div, so we'll have

function App() {
  const [text , setText]=useState("")
  const [date, setDate]=useState("")
  const [todos, setTodos]=useState<Todos[]>([])

 return(
 <div className="App">

</div>
)
}

Yes, we have new additions up, we will need the variable text for the input of our todo item, date variable for input of our target date and todos is a list of the entire todo items.

Adding Elements and Logic

So, we'll be adding some extra element and logic to the div element which would act as our parent element.

function App() {
  const [text , setText]=useState("")
  const [date, setDate]=useState("")
  const [todos, setTodos]=useState<Todos[]>([])

 return(
 <div className="App">
   <header className="App-header">
        Todo App with reminders
      </header>
      <audio src="" id="audi" />
      <main className="App-main">
        <h3>Your Todo List</h3>
        <ul>
        {todos.map((i,k)=>(
          <li key={k}>{i.todo+"  |  "+i.target +"   "} {i.fulfilled? <React.Fragment>| Done Already</React.Fragment>: <button onClick={()=>fulfilling(k)}>Done!</button> }</li>
        ))}
        </ul>
        <form onSubmit={(e)=>save(e)}>
          <textarea value={text} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>)=>change(e.target.value , 1)} placeholder="enter your todo list" required/>
          <input type='date' value={date} onChange={(e: React.ChangeEvent<HTMLInputElement>)=>change(e.target.value,2)} placeholder="set a date target to finish" required/>
          <button type='submit' style={{backgroundColor: "yellow"}}>Save</button>
        </form>
      </main>
</div>
)
}

So, let' break this down, we have the header element for a simple title to our todo reminder app. We embedded an audio tag for playing a simple audio when needed to.

Then inside the main tag, we have an unordered list which inside we mapped the todos, remember we said it's a list of what we already inputted. It returns a list tag that contain information about our todo item, that is what is represented by i.todo, i.target is the target date, which we would convert to a nice format later on. We then see a ternary function that chooses to display "Done Already" or a button that you can click done. This is used to trigger the route change as defined in the previous part through the fulfilling function we would define later on.

Lastly we have a form, this form should have an onSubmit event listener, so that when we click the button or press enter it saves the todo in our database, we attach the function save to it. We may be wondering what the e is. It is a form event that we to do many things like control the behaviour of the form or get the data from it.

Inside our form we have a textarea where we would input our todo item, we gave it the value text and onChange, we attached a function to update the text value. We may wonder the meaning of some strange syntax like React.ChangeEvent, this is giving the textarea event a type, as typescript would assume an any type for it. Though I must say there are many time where you don't really need to set a type for the variables as typescript can infer the type from the values it holds.

We also have an input that follows similar structure as the text area, but has a type of date, so it's a date input not a text input. Finally we have a button, that has a submit type, this gives me permission to submit the form.

The useEffect Hook

We are now going to write a function and use the useEffect hook to implement it at the time the component App renders.

useEffect(()=>{
    async function getResults(){
      let res = (await fetchRes('retrieve',{})).result
      res.sort((a: Todos,b: Todos)=>a.fulfilled==b.fulfilled?0: a.fulfilled? 1: -1).slice(0,25)
      setTodos(res)
    }
    getResults()
    const inv=setInterval(()=>{
      var ind = (new Date()).getHours()
      if (ind == 8 || ind == 12 || ind == 16 || ind== 21){
          remind()
      }
    },3600000)
    return ()=>clearInterval(inv)

  },[])

Inside the useEffect hook function, we have an async function to help us fetch data from the backend. At this point before I progress, I'll need to write the function fetchRes, I put it outside the component for clarity but you can always put it inside too.

async function fetchRes(path: string, data: any){
  const method=  {method:"POST",headers:{'Content-Type':'application/json'},body:JSON.stringify({...data}) }
  const returned = await fetch(`/${path}`,method)
  const res= await returned.json()
  return res
}

So now that we have defined, the fetchRes, we call the async function getResult, which fetches the data and sort it so that the unfulfilled ones go at the top and the fulfilled ones are below, also showing the top 25 for portability. Also after that we set an interval that check the present time and if the hours match the ones defined, it triggers the function remind, and this function runs every hour. We need to clear the interval to avoid memory leaks when we unmount the component.

Notice the empty list at the end of the hook, this is a list of variables that should trigger its recall, we could put any value there, just make sure you are not setting the state of that variable in the useEffect function, or an infinite loop will be triggered. Also it is not advisable to leave it empty that is without the list at the end, cause any change is state will trigger an infinite loop, even without that every little change trigger the useEffect function, and methods like fetching from a server which has slow response time could render the almost unusable.

The change function

So we look at the change function we attached to our input and textarea event listener.

 const change=(e: string, num: number)=>{
    if(num ==1){
      setText(e)
    }else{
      setDate(e)
    }
  }

The save function

The save function is also a callback to the submit event.

const save= async (e: React.FormEvent<HTMLFormElement>)=>{
    e.preventDefault()
    let time = new Date()
    let target = new Date(date)
    let tod =todos
    let format = time.toDateString()
    let formatTarget=target.toDateString()
    fetchRes('save',{todo:text, start:format, target:formatTarget})
    tod.push({todo:text, start:format, target:formatTarget})
    tod.sort((a: Todos,b: Todos)=>a.fulfilled==b.fulfilled?0: a.fulfilled? 1: -1).slice(0,25)
    setTodos(tod)
    setDate("")
    setText("")
  }

We should not that we need to call the preventDefault function on the form event, this would prevent it from refreshing the DOM automatically because if it does, all data inputted so far is lost. We could also see the getting time and formating to ate string. We then call the fetchRes function with save path and needed data inputted, we also update the todo with the data we have already, there is no need to wait for a response from the server. Lastly we clear the input and text area for new input.

The fulfilling function

So next we have the function attached to the click event of the done button.

const fulfilling= (k: number)=>{
    let tod =todos
    tod[k].fulfilled=true
    setTodos(tod)
    fetchRes('change',{idd:tod[k]["_id"]})
  }

We update the exact todo item fulfilled value and also send the trigger to the change route to do so too

The remind function

Lastly, we have the remind function which is triggered when the time matches the one we have hard coded.

const remind=()=>{
    let tod = todos
    tod = tod.filter(i=> !i.fulfilled)
    if(tod.length >0){
      alert(`Hi!, you have ${24-(new Date()).getHours()} and ${tod.length} tasks remaining! Ginger Up!`)
      let audi= document.getElementById("audi") as HTMLAudioElement

      if (audi !== null){
        audi.src="https://www.zapsplat.com/wp-content/uploads/2015/sound-effects-41945/zapsplat_emergency_nuclear_power_station_meltdown_alarm_42849.mp3?_=7"
        audi.play()
      }
    }

First, it check if there is a unfulfilled todo item, if there is it send an alert with a message telling how many hours of the day remaining and number of tasks we have left. Also it captures the audio tag and attaches a source link to it then plays it to give a quick alarm sound alert.

Finally

So we'll mostly end up having this in our App.tsx

import React, {useEffect, useState} from 'react';
import './App.css';

type Todos={
  todo:string,
  start:string, 
  target:string,
  fulfilled?:boolean,
  _id?: string
}

function App() {
  const [text , setText]=useState("")
  const [date, setDate]=useState("")
  const [todos, setTodos]=useState<Todos[]>([])

  useEffect(()=>{
    async function getResults(){
      let res = (await fetchRes('retrieve',{})).result
      res.sort((a: Todos,b: Todos)=>a.fulfilled==b.fulfilled?0: a.fulfilled? 1: -1).slice(0,25)
      setTodos(res)
    }
    getResults()
    const inv=setInterval(()=>{
      var ind = (new Date()).getHours()
      if (ind == 8 || ind == 12 || ind == 16 || ind== 21){
          remind()
      }
    },3600000)
    return ()=>clearInterval(inv)

  },[])

  const change=(e: string, num: number)=>{
    if(num ==1){
      setText(e)
    }else{
      setDate(e)
    }
  }
  const save= async (e: React.FormEvent<HTMLFormElement>)=>{
    e.preventDefault()
    let time = new Date()
    let target = new Date(date)
    let tod =todos
    let format = time.toDateString()
    let formatTarget=target.toDateString()
    fetchRes('save',{todo:text, start:format, target:formatTarget})
    tod.push({todo:text, start:format, target:formatTarget})
    tod.sort((a: Todos,b: Todos)=>a.fulfilled==b.fulfilled?0: a.fulfilled? 1: -1).slice(0,25)
    setTodos(tod)
    setDate("")
    setText("")
  }
  const fulfilling= (k: number)=>{
    let tod =todos
    tod[k].fulfilled=true
    setTodos(tod)
    fetchRes('change',{idd:tod[k]["_id"]})
  }
  const remind=()=>{
    let tod = todos
    tod = tod.filter(i=> !i.fulfilled)
    if(tod.length >0){
      alert(`Hi!, you have ${24-(new Date()).getHours()} and ${tod.length} tasks remaining! Ginger Up!`)
      let audi= document.getElementById("audi") as HTMLAudioElement

      if (audi !== null){
        audi.src="https://www.zapsplat.com/wp-content/uploads/2015/sound-effects-41945/zapsplat_emergency_nuclear_power_station_meltdown_alarm_42849.mp3?_=7"
        audi.play()
      }
    }
  }

  return (
    <div className="App">
      <header className="App-header">
        Todo App with reminders
      </header>
      <audio src="" id="audi" />
      <main className="App-main">
        <h3>Your Todo List</h3>
        <ul>
        {todos.map((i,k)=>(
          <li key={k}>{i.todo+"  |  "+i.target +"   "} {i.fulfilled? <React.Fragment>| Done Already</React.Fragment>: <button onClick={()=>fulfilling(k)}>Done!</button> }</li>
        ))}
        </ul>
        <form onSubmit={(e)=>save(e)}>
          <textarea value={text} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>)=>change(e.target.value , 1)} placeholder="enter your todo list" required/>
          <input type='date' value={date} onChange={(e: React.ChangeEvent<HTMLInputElement>)=>change(e.target.value,2)} placeholder="set a date target to finish" required/>
          <button type='submit' style={{backgroundColor: "yellow"}}>Save</button>
        </form>
      </main>
    </div>
  );
}

async function fetchRes(path: string, data: any){
  const method=  {method:"POST",headers:{'Content-Type':'application/json'},body:JSON.stringify({...data}) }
  const returned = await fetch(`/${path}`,method)
  const res= await returned.json()
  return res
}

export default App;

Also like I mentioned, we need to configure our react app to check our server in our backend. We go to our package.json and paste

"proxy": "http://localhost:5000/",

Flask uses a default port of 5000, but if your uses another port, you'll have to change the 5000 to your port number.

Finally, we open a terminal in our root project folder and do this

cd server && python server.py && cd .. && cd client && npm run start

This command will navigate to the server folder and start the server.py, navigate back to the client folder and start the react app server.

Extra Helpings

This is just bare minimum, looking at our app, it'll look ugly, you can play around with the css to get something like this 2020-10-04 00_24_24-Window.png There is still a lot we could do with our app, so we had better get back to our editor!