Finishing my project ( n°5 )

Hello again

Today I went mega coding session and finished my first react project! I decided that I would make the app display data from a bunch of API's. To do this i created this component called Widget that takes in a params object in the props. This object has all the necessary information to fetch data from a specific API and then display it correctly :

{
        url: "https://example.com/api/",
        propertyName: "data",
        subPropertyName: "data"
        title: "Amazing data",
        borderColor: "tailwindcss-color",
        type: "reload/no reload"
}

So lets brake down this object:

  • url

    This option is followed by the URL of the API endpoint
  • propertyName

    This is used to access the right part of data returned from the request and does this thanks to object manipulation methods:
    setContent( data[propertyName] )
    
  • subPropertyName

    this is an optional argument, and is used if the desired data is nested further in the response object.
  • title

    This is the title of the widget, displayed above the data
  • borderColor

    you can set this property to the indended border color of the widget (uses tailwind classes.
  • type

    This property can have 2 options:
    1. if type is set to "reload" then a button will appear next to the data letting you send a new request to the API to fetch different data.
    2. if typeis set to "no reload" , there will not be a reload button. this option is used for API's that will always send the same data.

Now let's break down my widget component:

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

First of all I import React and various hooks, then a custom button i made. Next, I initialize the component and use object de-structuring to extract variables from the params prop.

export default function Widget(props) {

    const { url, propertyName, title, borderColor, type } = props.params;

After this, I create 2 states, the content state to store the widgets content, and the needsReload state, which will later be used to send new requests.

    const [content, setContent] = useState(null);
    const [needsReload, setNeedsReload] = useState(false);

The next part creates a subProperty variable if the later exists in the params object. I used error handling for this but their might be a better solution.

    var subPropertyName;
    try {
        subPropertyName = props.params.subPropertyName;
    } catch(err) {
        subPropertyName = false
    }

A useEffect hook is then used to fecth the data from the API. The content state is then set to the meaningful part of the data. This is where the subProperty option comes in handy in case the data is nested deeper inside the response.

    useEffect(() => {
        fetch(url)
            .then(response => response.json())
            .then(data => {
                if (subPropertyName) {
                    setContent(data[propertyName][subPropertyName])
                } else {
                    setContent(data[propertyName])
                }
            })
            .catch(err => setContent('there was an error'))
    }, [needsReload])

needsReload is passed into the effect to trigger it again if it is updated, creating an easy way to send new requests if the variable is changed. Fetching the API itself uses .then() to handle the promise and .catch() to handle errors. Next I use a template literal to for dynamic styling of the widget, i can pass in the color from the params object.

    const contentClass = `widget-content border-${borderColor}`

Finally, I return the widget with a title and the data, and conditional rendering is used to include (or not) the reload button.

    if (type == "reload") {
        return (
            <div className="wiget-container">
                <div 
                    className={contentClass}
                >
                    <div>
                        <h1 className="font-bold text-2xl">{title}</h1>
                        <p className="font-semibold">{content}</p>
                    </div>
                </div>
                <NextButton setNeedsReload={setNeedsReload} needsReload={needsReload} borderColor={borderColor} />
            </div>
        )
    } else {
        return (
            <div className="wiget-container">
                <div 
                    className={contentClass}
                >
                    <div>
                        <h1 className="font-bold text-2xl">{title}</h1>
                        <p className="font-semibold">{content}</p>
                    </div>
                </div>
            </div>
        )
    }
}

Here is the full component:

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

export default function Widget(props) {

    const { url, propertyName, title, borderColor, type } = props.params;

    const [content, setContent] = useState(null);
    const [needsReload, setNeedsReload] = useState(false);

    var subPropertyName;
    try {
        subPropertyName = props.params.subPropertyName;
    } catch(err) {
        subPropertyName = false
    }

    useEffect(() => {
        fetch(url)
            .then(response => response.json())
            .then(data => {
                if (subPropertyName) {
                    setContent(data[propertyName][subPropertyName])
                } else {
                    setContent(data[propertyName])
                }
            })
            .catch(err => setContent('there was an error'))
    }, [needsReload])

    //template literal for dynamic styling
    const contentClass = `widget-content border-${borderColor}`

    if (type == "reload") {
        return (
            <div className="wiget-container">
                <div 
                    className={contentClass}
                >
                    <div>
                        <h1 className="font-bold text-2xl">{title}</h1>
                        <p className="font-semibold">{content}</p>
                    </div>
                </div>
                <NextButton setNeedsReload={setNeedsReload} needsReload={needsReload} borderColor={borderColor} />
            </div>
        )
    } else {
        return (
            <div className="wiget-container">
                <div 
                    className={contentClass}
                >
                    <div>
                        <h1 className="font-bold text-2xl">{title}</h1>
                        <p className="font-semibold">{content}</p>
                    </div>
                </div>
            </div>
        )
    }
}

Thanks to this component i can map a bunch of API params to display data from as many API's as nescesarry!

export default function Home() {

    const APIlist = [
        {
            url: "https://v2.jokeapi.dev/joke/Any?safe-mode&type=single",
            propertyName: "joke",
            title: "Joke:",
            borderColor: "th-blue-cyan",
            type: "reload"
        },
        {
            url: "https://www.boredapi.com/api/activity",
            propertyName: "activity",
            title: "Are you bored? Try this:",
            borderColor: "th-pink-true",
            type: "reload"
        },
        {
            url: "https://api.ipify.org/?format=json",
            propertyName: "ip",
            title: "your IP:",
            borderColor: "th-yellow-gold",
            type: "no reload"
        },
        {
            url: "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
            propertyName: "bitcoin",
            subPropertyName:"usd",
            title: "btc price ($usd):",
            borderColor: "th-red-chili",
            type: "no reload"
        },
        {
            url: "https://v2.jokeapi.dev/joke/programming?safe-mode&type=single",
            propertyName: "joke",
            title: "Programming joke:",
            borderColor: "th-blue-cyan",
            type: "reload"
        },
    ]

    return (
        APIlist.map(api => {
            return <Widget key={api.title} params={api} />
        })
    )
}

Here are what those widgets look like:

Capture.PNG

So this will conclude my learning react Blog series, I have really enjoyed using react and think it's a great way to work with JavaScript in frontend development!