<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anthony Zhang</title>
    <description>The latest articles on DEV Community by Anthony Zhang (@anthonyzhang220).</description>
    <link>https://dev.to/anthonyzhang220</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F706440%2F22cc45d9-47f7-49fe-aecd-079e7cd06aa5.jpeg</url>
      <title>DEV Community: Anthony Zhang</title>
      <link>https://dev.to/anthonyzhang220</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anthonyzhang220"/>
    <language>en</language>
    <item>
      <title>I refactored my Discord clone app to see if using Redux helps with state management, and here is what I found…</title>
      <dc:creator>Anthony Zhang</dc:creator>
      <pubDate>Mon, 24 Jun 2024 21:12:15 +0000</pubDate>
      <link>https://dev.to/anthonyzhang220/i-refactored-my-discord-clone-app-to-see-if-using-redux-helps-with-state-management-and-here-is-what-i-found-2ofl</link>
      <guid>https://dev.to/anthonyzhang220/i-refactored-my-discord-clone-app-to-see-if-using-redux-helps-with-state-management-and-here-is-what-i-found-2ofl</guid>
      <description>&lt;h2&gt;
  
  
  The Story
&lt;/h2&gt;

&lt;p&gt;Before I chose the tech stack for my Discord clone app, I didn't expect this would be such a complex project so Redux was not part of the stack initially. The purpose of building this app, in the beginning, is I have been using Discord since 2017, and it is such a fun place to hang out with my friends, play games, chat, etc, so I want to replicate the experience. Later on, as I started to get a hang of modern web technologies, I became curious about what it takes to build Discord with the technologies I'm familiar with, for example, React.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;It took me about 2 to 3 months to finish building the app with all the basic discord features ready for end users to use, such as direct messaging, group messaging, voice calling, friend invites, server and channel invites, group audio and video chat, and more. However, the code I wrote made me want to stop myself from even beginning this project because it is really hard to maintain and the code is barely readable. My wish is to keep this project alive and add more features later on, so I decided to refactor it no matter what.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation before Refactoring
&lt;/h2&gt;

&lt;p&gt;It is very obvious to me that my main App.js file is too bulky. It contains almost all states, functions, hooks, and so on with about 1000 lines of code. Even though the initial design of dividing each section to separate React components is quite nice, to be honest, there is still too much props drilling, which results in chaotic state management. Just to give you an idea of what my App.js looks like before refactoring.&lt;/p&gt;




&lt;p&gt;Here is my  react component with all the props passed to it,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Route
    index
    element={
        &amp;lt;Fragment&amp;gt;
            &amp;lt;Channel
                currentServer={currentServer}
                setCurrentServer={setCurrentServer}
                currentUser={currentUser}
                currentChannel={currentChannel}
                setCurrentUser={setCurrentUser}
                signOut={signOut}
                handleAddChannel={handleAddChannel}
                handleCurrentChannel={handleCurrentChannel}
                channelModal={channelModal}
                setChannelModal={setChannelModal}
                handleChannelInfo={handleChannelInfo}
                newChannel={newChannel}
                voiceChat={voiceChat}
                setVoiceChat={setVoiceChat}
                currentVoiceChannel={currentVoiceChannel}
                setCurrentVoiceChannel={setCurrentVoiceChannel}
                handleLocalUserLeftAgora={handleLocalUserLeftAgora}
                muted={muted}
                defen={defen}
                handleDefen={handleDefen}
                handleVideoMuted={handleVideoMuted}
                handleVoiceMuted={handleVoiceMuted}
                voiceConnected={voiceConnected}
                isSharingEnabled={isSharingEnabled}
                isMutedVideo={isMutedVideo}
                screenShareToggle={screenShareToggle}
                stats={stats}
                connectionState={connectionState}
            /&amp;gt;
            {
                voiceChat ?
                    &amp;lt;VoiceChat
                        voiceChat={voiceChat}
                        currentVoiceChannel={currentVoiceChannel}
                        config={config}
                        currentUser={currentUser}
                        isMutedVideo={isMutedVideo}
                        remoteUsers={remoteUsers}
                        setRemoteUsers={setRemoteUsers}
                        currentAgoraUID={currentAgoraUID}
                    /&amp;gt;
                    :
                    &amp;lt;Chat
                        currentUser={currentUser}
                        currentServer={currentServer}
                        currentChannel={currentChannel}
                        handleAddMessage={handleAddMessage}
                        handleChatInfo={handleChatInfo}
                        currentMessage={currentMessage}
                    /&amp;gt;

            }
        &amp;lt;/Fragment&amp;gt;
    }
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here are the states and useEffect handling user authentications,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const navigate = useNavigate();
const GoogleProvider = new GoogleAuthProvider();
const FacebookProvider = new FacebookAuthProvider();
const TwitterProvider = new TwitterAuthProvider();
const GithubProvider = new GithubAuthProvider();

//show modal for new server/channel
const [channelModal, setChannelModal] = useState(false);
const [serverModal, setServerModal] = useState(false);

//add new server/channel
const [newChannel, setNewChannel] = useState("")
const [newServerInfo, setNewServerInfo] = useState({ name: "", serverPic: "" });
const [serverURL, setServerURL] = useState(null);
const [file, setFile] = useState(null);

//current Login USER/SERVER/CHANNEL
const [currentUser, setCurrentUser] = useState({ name: null, profileURL: null, uid: null, createdAt: null });
const [currentServer, setCurrentServer] = useState({ name: "", uid: null });
const [currentChannel, setCurrentChannel] = useState({ name: "", uid: null });
const [currentMessage, setCurrentMessage] = useState("");

const [imageDir, setImageDir] = useState("")
const [isLoading, setIsLoading] = useState(false);

const [friendList, setFriendList] = useState([])

//google sign in with redirect
const googleSignIn = () =&amp;gt; {
    signInWithRedirect(auth, GoogleProvider)
}

const facebookSignIn = () =&amp;gt; {
    signInWithRedirect(auth, FacebookProvider)
}

const twitterSignIn = () =&amp;gt; {
    signInWithRedirect(auth, TwitterProvider)
}
const githubSignIn = () =&amp;gt; {
    signInWithRedirect(auth, GithubProvider)
}

//auth/login state change
useEffect(() =&amp;gt; {
    const loginState = onAuthStateChanged(auth, (user) =&amp;gt; {
        console.log(user)
        if (user) {
            const userRef = doc(db, "users", user.uid);

            getDoc(userRef).then((doc) =&amp;gt; {
                const has = doc.exists();
                if (has) {
                    setCurrentUser({ name: doc.data().displayName, profileURL: doc.data().profileURL, uid: doc.data().userId, createdAt: doc.data().createdAt.seconds, status: doc.data().status })
                } else {
                    setDoc(userRef, {
                        displayName: user.displayName,
                        email: user.email ? user.email : "",
                        profileURL: user.photoURL,
                        userId: user.uid,
                        createdAt: Timestamp.fromDate(new Date()),
                        status: "online",
                        friends: [],
                    }).then((doc) =&amp;gt; {
                        setCurrentUser({ name: doc.data().displayName, profileURL: doc.data().profileURL, uid: doc.data().userId, createdAt: doc.data().createdAt.seconds, status: doc.data().status })
                    })
                }
            })

            //if user not in the storage, add to the local storage
            if (!localStorage.getItem(`${user.uid}`)) {
                localStorage.setItem(`${user.uid}`, JSON.stringify({ defaultServer: "", defaultServerName: "", userDefault: [] }));
            } else {
                const storage = JSON.parse(localStorage.getItem(`${user.uid}`))
                setCurrentServer({ name: storage.defaultServerName, uid: storage.defaultServer })
                setCurrentChannel({ name: storage.userDefault.lengh == 0 ? "" : storage.userDefault.find(x =&amp;gt; x.currentServer == storage.defaultServer).currentChannelName, uid: storage.userDefault.find(x =&amp;gt; x.currentServer == storage.defaultServer).currentChannel })
            }
            navigate('/channels')
        } else {
            updateDoc(doc(db, "users", currentUser.uid), {
                status: "offline",
            })
            setCurrentUser({ name: null, profileURL: null, uid: null, status: null })
            navigate('/')

        }

    })

    return () =&amp;gt; {
        loginState();
    }

}, [auth])

//auth sign out function
const signOut = () =&amp;gt; {
    auth.signOut().then(() =&amp;gt; {

        const userRef = doc(db, "users", currentUser.uid)
        updateDoc(userRef, {
            status: "offline"
        })
        setCurrentUser({ name: null, profileURL: null, uid: null, createdAt: null })
    }).then(() =&amp;gt; {
        navigate("/", { replace: true })
    })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here are some of the states and functions handling voice group chat using Agora SDK,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [voiceChat, setVoiceChat] = useState(false);
const [currentVoiceChannel, setCurrentVoiceChannel] = useState({ name: null, uid: null })

const [config, setConfig] = useState(AgoraConfig)

const [isSharingEnabled, setIsSharingEnabled] = useState(false)
const [isMutedVideo, setIsMutedVideo] = useState(true)
const [agoraEngine, setAgoraEngine] = useState(AgoraClient);
const screenShareRef = useRef(null)
const [voiceConnected, setVoiceConnected] = useState(false);
const [remoteUsers, setRemoteUsers] = useState([]);
const [localTracks, setLocalTracks] = useState(null)
const [currentAgoraUID, setCurrentAgoraUID] = useState(null)
const [screenTrack, setScreenTrack] = useState(null);

const FetchToken = async () =&amp;gt; {
    return new Promise(function (resolve) {
        if (config.channel) {
            axios.get(config.serverUrl + '/rtc/' + config.channel + '/1/uid/' + "0" + '/?expiry=' + config.ExpireTime)
                .then(
                    response =&amp;gt; {
                        resolve(response.data.rtcToken);
                    })
                .catch(error =&amp;gt; {
                    console.log(error);
                });
        }
    });
}

useEffect(() =&amp;gt; {
    setConfig({ ...config, channel: currentVoiceChannel.uid })
}, [currentVoiceChannel.uid])

const [connectionState, setConnectionState] = useState({ state: null, reason: null })

useEffect(() =&amp;gt; {
    agoraEngine.on("token-privilege-will-expire", async function () {
        const token = await FetchToken();
        setConfig({ ...config, token: token })
        await agoraEngine.renewToken(config.token);
    });
    //enabled volume indicator
    agoraEngine.enableAudioVolumeIndicator();

    agoraEngine.on("volume-indicator", (volumes) =&amp;gt; {
        handleVolume(volumes)
    })
    agoraEngine.on("user-published", (user, mediaType) =&amp;gt; {
        console.log(user.uid + "published");
        handleUserSubscribe(user, mediaType)
        handleUserPublishedToAgora(user, mediaType)
    });
    agoraEngine.on("user-joined", (user) =&amp;gt; {
        handleRemoteUserJoinedAgora(user)
    })
    agoraEngine.on("user-left", (user) =&amp;gt; {
        console.log(user.uid + "has left the channel");
        handleRemoteUserLeftAgora(user)
    })
    agoraEngine.on("user-unpublished", (user, mediaType) =&amp;gt; {
        console.log(user.uid + "unpublished");
        handleUserUnpublishedFromAgora(user, mediaType)
    });

    agoraEngine.on("connection-state-change", (currentState, prevState, reason) =&amp;gt; {
        setConnectionState({ state: currentState, reason: reason })
    })

    return () =&amp;gt; {
        removeLiveUserFromFirebase(currentAgoraUID)

        for (const localTrack of localTracks) {
            localTrack.stop();
            localTrack.close();
        }
        agoraEngine.off("user-published", handleRemoteUserJoinedAgora)
        agoraEngine.off("user-left", handleRemoteUserLeftAgora)
        agoraEngine.unpublish(localTracks).then(() =&amp;gt; agoraEngine.leave())
    }

}, []);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And this is only part of the code for my main App.js file. The reason I was putting all of them in here is because a lot of the state needs to be shared across child components, so I have to put it at the top of the DOM tree to be passed. (&lt;a href="https://github.com/AnthonyZhang220/discord_clone/blob/63e3c21f106d3863661f768a56926acb4ec11a40/src/App.js" rel="noopener noreferrer"&gt;If you want to take a look at my old App.js file, here it is for you.&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  Refactoring
&lt;/h2&gt;

&lt;p&gt;The refactoring consists of a few steps. First, I need to divide them into different slices with different features, such as authentications, voice chat(Agora states), channel, server, message input, user, etc. Second, to further shrink the size of my App.js file, I need to group functions with similar purposes and put them into separate folders, for example, a util folder for all the utility functions, a handler/service folder for all the handlers such as user input handlers, voice chat room control handlers, server handlers, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;Here is the shrunken-down version of my App.js file after refactoring. It serves as the project entry point and only deals with routing making the code clean.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function App() {
    const dispatch = useDispatch()
    const { user } = useSelector((state) =&amp;gt; state.auth)
    const { isVoiceChatPageOpen } = useSelector(state =&amp;gt; state.voiceChat)
    const { currVoiceChannel } = useSelector(state =&amp;gt; state.channel)

    const navigate = useNavigate();

    useEffect(() =&amp;gt; {
        const unsubscribe = onAuthStateChanged(auth, async (user) =&amp;gt; {
            if (user) {
                const userRef = doc(db, "users", user.uid);
                const userDoc = await getDoc(userRef);

                if (userDoc.exists()) {
                    const userData = userDoc.data();
                    console.log("userData", userData)
                    dispatch(setUser({ ...userData }))
                } else {
                    const userDoc = await setDoc(userRef, {
                        displayName: user.displayName,
                        email: user.email ? user.email : "",
                        avatar: user.photoURL,
                        id: user.uid,
                        createdAt: Timestamp.fromDate(new Date()),
                        status: "online",
                        friends: [],
                        bannerColor: await getBannerColor(user.photoURL)
                    })
                    if (userDoc) {
                        dispatch(setUser(userDoc))
                    }
                }
                getSelectStore()
                dispatch(setIsLoggedIn(true))
                navigate("/channels")
            } else {
                dispatch(setUser(null))
                dispatch(setIsLoggedIn(false))
                navigate("/")
            }
        })

        return unsubscribe;
    }, [auth])

    return (
        &amp;lt;ThemeContextProvider&amp;gt;
            &amp;lt;CssBaseline /&amp;gt;
            &amp;lt;Error /&amp;gt;
            &amp;lt;Routes&amp;gt;
                &amp;lt;Route path="/" element={&amp;lt;LoginPage /&amp;gt;} /&amp;gt;
                &amp;lt;Route path="/reset" element={&amp;lt;ResetPasswordPage /&amp;gt;} /&amp;gt;
                &amp;lt;Route path="/register" element={&amp;lt;RegisterPage /&amp;gt;} /&amp;gt;
                &amp;lt;Route element={
                    &amp;lt;Box className="app-mount"&amp;gt;
                        &amp;lt;Box className="app-container" &amp;gt;
                            &amp;lt;Outlet /&amp;gt;
                        &amp;lt;/Box&amp;gt;
                    &amp;lt;/Box&amp;gt;
                } &amp;gt;
                    &amp;lt;Route path="/channels"
                        element={
                            &amp;lt;Fragment&amp;gt;
                                &amp;lt;ServerList /&amp;gt;
                                &amp;lt;Outlet /&amp;gt;
                            &amp;lt;/Fragment&amp;gt;
                        }&amp;gt;
                        &amp;lt;Route index
                            element={
                                &amp;lt;Fragment&amp;gt;
                                    &amp;lt;Channel /&amp;gt;
                                    {
                                        isVoiceChatPageOpen ?
                                            &amp;lt;VoiceChat currVoiceChannel={currVoiceChannel} /&amp;gt;
                                            :
                                            &amp;lt;Chat /&amp;gt;
                                    }
                                &amp;lt;/Fragment&amp;gt;
                            }
                        /&amp;gt;
                        &amp;lt;Route
                            path='/channels/@me'
                            element={
                                &amp;lt;Fragment&amp;gt;
                                    &amp;lt;DirectMessageMenu /&amp;gt;
                                    &amp;lt;DirectMessageBody /&amp;gt;
                                &amp;lt;/Fragment&amp;gt;
                            } /&amp;gt;
                    &amp;lt;/Route&amp;gt;
                &amp;lt;/Route&amp;gt;
                &amp;lt;Route path="*" element={&amp;lt;PageNotFound /&amp;gt;} /&amp;gt;
            &amp;lt;/Routes&amp;gt;
        &amp;lt;/ThemeContextProvider&amp;gt;
    )
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here is the Redux state graph. Almost all states are managed inside Redux.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiueyjllmztjfp5t1n22j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiueyjllmztjfp5t1n22j.png" alt="Redux Graph 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegr322zugic0whfni9sq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegr322zugic0whfni9sq.png" alt="Redux Graph 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Redux help with state management?&lt;/strong&gt; For the obvious reason, yes it does. It makes my code way easier to read and maintain. Also with Redux, I can access the global state wherever I want. For example, both channel and voice chat components need to access the mic mute/unmute and camera on/off state, I can easily grab them in each child component.&lt;/p&gt;




&lt;h2&gt;
  
  
  More Questions Ahead
&lt;/h2&gt;

&lt;p&gt;As I was refactoring the code base, a question was raised in my head. Every time we fetch data from APIs, we tend to sync and store them inside Redux and reflect the data in our UI. This gives me the feeling that Redux serves almost the same role as the backend database, but works as a constantly changing database for the frontend part. It's like creating another database in the frontend part only for displaying data in the UI, which I think might be redundant because we already have a database, and we are communicating with it by APIs. Why do we need an extra layer? Why can't we just simply display the data as we received them from the backend?&lt;/p&gt;

&lt;p&gt;After some reading and research, it turns out we have created many solutions to my question. In the MVC model, we have a model, view, and controller system, where views are directed by controllers to represent the models. Many frontend technologies use this MVC model like Ruby on Rails and Django. Recently, HTMX has gained more popularity among backend developers because it is server-centric. When a new piece of data is received, it is immediately updated on the website, while keeping it interactive. &lt;a href="https://www.youtube.com/watch?v=3GObi93tjZI" rel="noopener noreferrer"&gt;Here is a link to the video from DjangoCon 2022 where a HTMX demo is being presented by David Guillot.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are certainly more solutions. But Redux for sure plays an essential role in the React ecosystem. It is extremely useful when we are building large and complex web applications where we need state management across the project. But on the downside, it creates more boilerplate code, in my Discord clone as well. This is like creating another abstraction on top of React, which is an abstraction itself, but we kind of stuck to using it. I would also think that Redux is a more front-end approach to how we handle data, especially in SPA. As modern web technologies evolve and develop, we will see more ideas and solutions. With the latest React v18 and 19 with RSC, we will start to see more things done in the server component on the server side. This to me feels like a trend, and I find it very interesting that the front end is trying to move closer to the back end, and the back end is trying to move closer to the front end with new techs like HTMX. I'm excited to see what is coming for modern web development in the near future.&lt;/p&gt;




&lt;p&gt;If you would like to check out my whole Discord clone project, here is the &lt;a href="https://github.com/AnthonyZhang220/discord_clone" rel="noopener noreferrer"&gt;GitHub link&lt;/a&gt; to my repository. If you like my project, please don't hesitate to leave a star.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7sl5fgnco29rgm55585r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7sl5fgnco29rgm55585r.png" alt="Discord Clone Screenshot"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1xx6min296jdq7o7oij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1xx6min296jdq7o7oij.png" alt="Happy Coding!"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discord</category>
      <category>redux</category>
      <category>react</category>
      <category>learning</category>
    </item>
    <item>
      <title>My take on how to improve the performance and accessibility of our website with React? (2) Accessibility</title>
      <dc:creator>Anthony Zhang</dc:creator>
      <pubDate>Sat, 09 Dec 2023 04:03:40 +0000</pubDate>
      <link>https://dev.to/anthonyzhang220/my-take-on-how-to-improve-the-performance-and-accessibility-of-our-website-with-react-2-accessibility-1p71</link>
      <guid>https://dev.to/anthonyzhang220/my-take-on-how-to-improve-the-performance-and-accessibility-of-our-website-with-react-2-accessibility-1p71</guid>
      <description>&lt;p&gt;The topic of creating a more accessible internet environment has been raised a lot more in recent years, especially in web development. Making web pages available to read for disabled people is almost a required skill for front-end engineers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  How could we improve the accessibility of our website?
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Use semantic HTML tags and add AIRA attributes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By using HTML tags, the type of content that is highlighted would be read out by the screen readers. These are built-in features that are interactive elements with keyboard support. For example, &lt;a&gt;, ,,  are the most used ones.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disabled people could use Tab and Shift + Tabnavigate through each element.&lt;/p&gt;

&lt;p&gt;Basically, each element tag would have one or more of the following 4 properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;type.&lt;/strong&gt; Describe its type such as a button, or an input area.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;name.&lt;/strong&gt; Describe its name or title. Mostly, it is the computed label. If there is no title or placeholder attribute, we could use &lt;strong&gt;AIRA attributes&lt;/strong&gt; such as aria-label and aria-labelledby.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;value.&lt;/strong&gt; Usually associate with . What the user has typed in the text field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;**state. **For example,  can be expanded or collapsed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Color and contrast accessibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Unlike sight-impaired people, some people may see things with low vision but have difficulty perceiving colors. Color and contrast accessibility are another important part we need to consider adding to our website.&lt;/p&gt;

&lt;p&gt;The WebAIM guidelines give the following standards for developers to follow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Level A&lt;/strong&gt; is a basic requirement for some users with disabilities to be able to access and use web content.&lt;br&gt;
 &lt;strong&gt;Level AA&lt;/strong&gt; indicates overall accessibility and removal of significant barriers to accessing content.&lt;br&gt;
 &lt;strong&gt;Level AAA&lt;/strong&gt; provides improvements and enhancements to web accessibility for some users with disabilities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Typically, Level AA is the minimum recommendation with the following criteria:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The visual presentation of &lt;a href="https://www.w3.org/TR/WCAG21/#dfn-text"&gt;text&lt;/a&gt; and &lt;a href="https://www.w3.org/TR/WCAG21/#dfn-images-of-text"&gt;images of text&lt;/a&gt; has a &lt;a href="https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio"&gt;contrast ratio&lt;/a&gt; of at least 4.5:1, except for the following:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Large Text&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.w3.org/TR/WCAG21/#dfn-large-scale"&gt;*Large-scale&lt;/a&gt; text and images of large-scale text have a contrast ratio of at least 3:1;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Incidental&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Text or images of text that are part of an inactive &lt;a href="https://www.w3.org/TR/WCAG21/#dfn-user-interface-components"&gt;user interface component&lt;/a&gt;, that are &lt;a href="https://www.w3.org/TR/WCAG21/#dfn-pure-decoration"&gt;pure decoration&lt;/a&gt;, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Logotypes&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Text that is part of a logo or brand name has no contrast requirement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These color and contrast ratio can be confusing since these are usually taking into account by the UI and UX engineers. But as front-end developers, playing a role in the web development process, we should at least know that what they are and how they work.&lt;/p&gt;

&lt;p&gt;After putting these standards into practice, I tested my personal portfolio website with Google Lighthouse, and here is my result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i4XJb903--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2ASWaHYA3nZrdlBjtgWcOB2Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i4XJb903--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2ASWaHYA3nZrdlBjtgWcOB2Q.png" alt="result" width="525" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hNcuv_dr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AuFJCZSy7jDZ9M9xz7785lQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hNcuv_dr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AuFJCZSy7jDZ9M9xz7785lQ.png" alt="passed audit details" width="530" height="775"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wwX3ivfR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2A32o3a8a4fiiUXCBVNTtvMw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wwX3ivfR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2A32o3a8a4fiiUXCBVNTtvMw.png" alt="background and contrast ratio" width="529" height="651"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we look through the report, I did pretty well on the naming and labeling part, but I still lack the experience in getting the correct contrast ratio correct. However, Google Lighthouse can guide me to improve the accessibility each time, and each time my accessibility score went up.&lt;/p&gt;

&lt;p&gt;With these accessibility features, more people could access the endless web world. The developer community differs vastly from other working communities as it is friendly and inclusive, and it is one of the prior reasons why I chose front-end engineer as my career path. I hope that after reading this article, more developers will understand the importance of making our website more accessible to the disabled community.&lt;/p&gt;

&lt;p&gt;Let’s connect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anthonyzhang.netlify.app/"&gt;Portfolio&lt;/a&gt; | &lt;a href="https://github.com/AnthonyZhang220"&gt;Github&lt;/a&gt; | &lt;a href="https://medium.com/@anthonyzhang220"&gt;Medium&lt;/a&gt;| &lt;a href="https://www.linkedin.com/in/anthony-xiangyu-zhang/"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UjgZJYOg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o41qdqsxj76jkmqe17dc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UjgZJYOg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o41qdqsxj76jkmqe17dc.png" alt="Happy Coding" width="700" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/" class="ltag_cta ltag_cta--branded"&gt;Introduction to Web Accessibility&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webaim.org/standards/wcag/" class="ltag_cta ltag_cta--branded"&gt;Web Content Accessibility Guidelines&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>react</category>
      <category>webdev</category>
      <category>html</category>
    </item>
    <item>
      <title>My take on how to improve the performance and accessibility of our website with React? (1) Performance</title>
      <dc:creator>Anthony Zhang</dc:creator>
      <pubDate>Sat, 09 Dec 2023 03:57:32 +0000</pubDate>
      <link>https://dev.to/anthonyzhang220/my-take-on-how-to-improve-the-performance-and-accessibility-of-our-website-with-react-1-performance-3l2c</link>
      <guid>https://dev.to/anthonyzhang220/my-take-on-how-to-improve-the-performance-and-accessibility-of-our-website-with-react-1-performance-3l2c</guid>
      <description>&lt;p&gt;Usually, SEO ranking plays a huge role in making our website stand out. As you may have known, page loading speed is one of several factors that affect your SEO ranking, and improvement in website performance results in ranking up in SEO. Therefore, this leads to the topic I will be discussing in this article,&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  How could we improve the performance of our website?
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Reduce bundle size, unused packages, and third-party libraries
&lt;/h2&gt;

&lt;p&gt;When we first started coding as a Frontend Developer, Create React App(or CRA for abbreviation) was our best friend. It bootstraps our single-page application in React so that we don’t have to worry about the required configuration. However, there are tradeoffs in using CRA. The drawbacks are: that its bundle size is bulky, and with all the unused packages preinstalled it can slow our website in terms of performance.&lt;/p&gt;

&lt;p&gt;One solution is that we don’t use CRA in the first place and configure Webpack and Babel depending on the situation. However, for those who already used CRA for their projects, it can be hard and time-consuming to eject our existing project.&lt;/p&gt;

&lt;p&gt;Therefore, one workaround I found to reduce the bundle size is with the use of a library named &lt;a href="https://www.npmjs.com/package/depcheck"&gt;**depcheck&lt;/a&gt;**. We can install the package by&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**npm install depcheck**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and run command,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**depcheck**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we will see all the unused dependencies and missing dependencies in a list. which later we can uninstall accordingly. Here is an example of the list I got for my portfolio website.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PS D:\Github Projects\portfolio-website&amp;gt; depcheck
**Unused dependencies**
* [@fortawesome/fontawesome-free](http://twitter.com/fortawesome/fontawesome-free)
* [@fortawesome/free-regular-svg-icons](http://twitter.com/fortawesome/free-regular-svg-icons)
* [@material](http://twitter.com/material)-ui/icons
* [@mui/styles](http://twitter.com/mui/styles)
* [@testing](http://twitter.com/testing)-library/jest-dom
* [@testing](http://twitter.com/testing)-library/react
* [@testing](http://twitter.com/testing)-library/user-event
* body-parser
* concurrently
* mongoose
* node
* react-swipeable-views
* request
* sass
* web-vitals
* webpack
**Missing dependencies**
* eslint-config-react-app: .\package.json
* [@material](http://twitter.com/material)-ui/styles: .\src\components\Project\ProjectDetails.jsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;My take: Third-party UI libraries are usually bulk in size, which could dramatically lower the performance of our website. For example, my website uses icons from fort awesome, UI components from Material UI, and an animation library from GSAP. From the Lighthouse Treemap below, we can see a lot of unused resources from them(on the right half).&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kEA3Wn5c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/3836/1%2AI6Rv6zUN5DRt0fDdB3xH1A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kEA3Wn5c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/3836/1%2AI6Rv6zUN5DRt0fDdB3xH1A.png" alt="Tree map from Google Lighthouse" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Therefore, think considerably before you implement them in your projects.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Use lazy loading and code-splitting
&lt;/h2&gt;

&lt;p&gt;There are a lot of different ways to lazy load. And we can lazy load different elements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Component lazy loading in React&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For components, luckily in React, there are build-in methods that we can use to lazy load our components. It allows us to dynamically import components.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const MusicPlayer = **React.lazy**(() =&amp;gt; import("./MusicPlayer"));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here is an example taken from my website, where I am dynamically importing my Music Player component. The reason to use lazy loading is, that this component is at the very bottom of my page near the footer, so users do not need to see it during the first painting.&lt;/p&gt;

&lt;p&gt;The lazy component should later be rendered inside a Suspense component that looks like this,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**&amp;lt;Suspense** 
     **fallback**={
        &amp;lt;Box&amp;gt;
             &amp;lt;Widget&amp;gt;Loading...&amp;lt;/Widget&amp;gt;
        &amp;lt;/Box&amp;gt;}
**&amp;gt;**
     &amp;lt;MusicPlayer /&amp;gt;
**&amp;lt;/Suspense&amp;gt;**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;My take: The habit of breaking our website into smaller reusable components now makes more sense because in a way we can only load the essential components during the first painting, so users won’t have to wait for everything to be loaded. Though the differences for smaller websites are in milliseconds, for a scalable website that later becomes enormous, these differences could add up to seconds I could imagine.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metadata preloading (audio/video)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set &lt;strong&gt;preload&lt;/strong&gt; attribute to &lt;strong&gt;none&lt;/strong&gt; restricts the browser from loading the audio or video file when the page is loaded, thus saving a lot of network resources.&lt;/p&gt;

&lt;p&gt;Here is an example taken from my website,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;audio id='audio' **preload='none'** ref={audioRef}&amp;gt;&amp;lt;/audio&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;My take: The audio/video files are usually greater than 3MB (before compression), which takes longer time to load if we wait for it. Therefore, loading the metadata after the user takes their first action could greatly improve page load speed.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lazy loading with images&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set &lt;strong&gt;loading&lt;/strong&gt; attribute to &lt;strong&gt;lazy&lt;/strong&gt; would delay the loading until everything else is loaded. This is extremely helpful if we have a lot of images to display on our website.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;img src="image.jpg" alt="..." **loading="lazy"**&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;My take: Although theoretically we can lazy load every image on our website, we don’t want to apply these settings to images that are within the first viewport that users can see.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results by Google Lighthouse shows performance improvement
&lt;/h2&gt;

&lt;p&gt;I will show the improvement of my website. Simply doing these tricks I mentioned in the article, makes me realize that even a small effort in doing so could lead to a &lt;strong&gt;34%&lt;/strong&gt; performance leap. (Your result may vary.)&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TSSZeF7l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AwYrVKAbUhOAiOTJo8O2KAw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TSSZeF7l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AwYrVKAbUhOAiOTJo8O2KAw.png" alt="before" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kky-isTG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2A-9tPiLzdNBnac0IrDoREkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kky-isTG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2A-9tPiLzdNBnac0IrDoREkg.png" alt="after" width="800" height="737"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next article, I will be discussing how we could improve the accessibility of our website, which is also a measure factor of SEO ranking. Make sure to keep your eyes on my next medium update.&lt;/p&gt;

&lt;p&gt;Let’s connect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anthonyzhang.netlify.app/"&gt;Portfolio&lt;/a&gt; | &lt;a href="https://github.com/AnthonyZhang220"&gt;Github&lt;/a&gt; | &lt;a href="https://medium.com/@anthonyzhang220"&gt;Medium&lt;/a&gt;| &lt;a href="https://www.linkedin.com/in/anthony-xiangyu-zhang/"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gWWBVma1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/800/1%2A0RrV3HsjyziQGvJ_GpBYbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gWWBVma1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/800/1%2A0RrV3HsjyziQGvJ_GpBYbw.png" alt="Happy Coding" width="700" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://reactjs.org/docs/code-splitting.html" class="ltag_cta ltag_cta--branded"&gt;Code-Splitting - React&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading" class="ltag_cta ltag_cta--branded"&gt;Lazy loading - Web Performance | MDN&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>seo</category>
      <category>beginners</category>
      <category>react</category>
      <category>performance</category>
    </item>
    <item>
      <title>Building a Music Player using React hook useState() with my own implementation</title>
      <dc:creator>Anthony Zhang</dc:creator>
      <pubDate>Sat, 09 Dec 2023 03:46:00 +0000</pubDate>
      <link>https://dev.to/anthonyzhang220/building-a-music-player-using-react-hook-usestate-with-my-own-implementation-2ba5</link>
      <guid>https://dev.to/anthonyzhang220/building-a-music-player-using-react-hook-usestate-with-my-own-implementation-2ba5</guid>
      <description>&lt;p&gt;Having been practicing the piano for almost 10 years, I came across this idea to add a music player playing my favorite music on my portfolio website as the development process was halfway through. I feel that it’s just a nice and unique feature to have because it can also showcase my personality through the music I listen to.&lt;/p&gt;

&lt;p&gt;The building process is a struggle. I got a painful and slow start with this idea. I did not know what technology I would be using to implement the basic functionalities of a music player, such as play/pause, previous/next song, volume control, backward/forward, etc. Fortunately, there are methods that I found build-in with JavaScript that can do some of these things. However, features like go to the next song can not be found within its function library(Correct me if I’m wrong), so I decided to write my own functions with the help of hook useState().&lt;/p&gt;

&lt;p&gt;We need some sort of music player layout with good aesthetics to start with so that it is appealing to people’s eyes. Here, I take the music player layout from Material UI, where they showcase an example for their Slider component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kxHdesBZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AiAR2_drftcRXmrbbB-QRFw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kxHdesBZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AiAR2_drftcRXmrbbB-QRFw.png" alt="Layout taken from Material UI Website" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a better user experience, with just a little twist I added a previous and next song button and a mute volume icon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qVDY9x2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AK0a-RvP0ndgn9ykIDKAC3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qVDY9x2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AK0a-RvP0ndgn9ykIDKAC3g.png" alt="My music player layout" width="415" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With our layout ready, we can start thinking about the logic behind functionalities and start coding.&lt;/p&gt;

&lt;p&gt;First, we need to initialize a few state. A few elements we need to consider are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Duration, playback position and time left indication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Playing? Or, paused?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;song unique ID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Volume.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From above, we have the following state initialized:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// song length in sec
const [duration, setDuration] = useState(0);

// playback position
const [position, setPosition] = useState(0);

// playing or paused
const [paused, setPaused] = useState(true);

const randomizer = Math.floor(Math.random() * (musicList.length - 1) + 1)
//initial random song
const [songId, setSongId] = useState(randomizer);

//0.2 means 20% volume
const [volume, setVolume] = useState(0.2);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;One thing to point out is that we need the song to be paused initially because it can be obtrusive if the song played by itself. This is better practice because you usually want your video or audio to be muted unless the play interaction is started by the users. Think about a college student or your colleagues opened your website during a class or a meeting but forget to mute their laptop, it will be very awkward for them.&lt;/p&gt;

&lt;p&gt;To start putting audio element on the webpage, we need an  tag, and its  will depend on the songId that we choose from my manually imported music list. With useRef(), we can easily access the DOM. (audioRef.current)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gZYQEbKI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/4564/1%2ABAyLKbRmTlF3Xh-ZCx08vw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gZYQEbKI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/4564/1%2ABAyLKbRmTlF3Xh-ZCx08vw.png" alt="&amp;lt;audio&amp;gt; tag" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a peek of the structure of my music list data in a JavaScript file. You can add more songs later on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here comes the main part, where we are going to implement features for each of our buttons and bars.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Playing/Paused.&lt;/strong&gt; Fairly simple. JavaScript includes &lt;strong&gt;play()&lt;/strong&gt; and &lt;strong&gt;pause()&lt;/strong&gt; functions. All we need to do is to change their state when the button is clicked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;2. Rewind/Forward. Playback bar.&lt;/strong&gt; I found 5 secs to be reasonable for rewind and forward. &lt;strong&gt;currentTime&lt;/strong&gt; gives the current playing timestamp for the song. It is one of the properties of audioRef.current.&lt;/p&gt;

&lt;p&gt;If the song is at the beginning 5 seconds, it should not go below 0 seconds. For the same reason, if a song is at the end 5 seconds, it should not go beyond song’s duration. Otherwise this music player will be bugged out.&lt;/p&gt;

&lt;p&gt;Keep in mind. We not only have to change the &lt;strong&gt;currentTime&lt;/strong&gt; of our song, but also have to show its position on the playback bar. The code below keeps them in sync. Every time a user drags the playback bar, it will update its position and assign the value to the &lt;strong&gt;currentTime&lt;/strong&gt; property.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Previous/Next Song.&lt;/strong&gt; This is more complicated because there are more things to be considered.&lt;/p&gt;

&lt;p&gt;If this is the last song in our music list, it should skip to the first song. Otherwise, it will proceed as a normal song skip. And every time we skip a song, we need to load the new data and play so that the user do not need to click the play button again. For better experience, we could add an error handler if something went wrong with the loading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Volume Change.&lt;/strong&gt; Here I use localStorage to let the browser remember what user’s previous volume is.&lt;/p&gt;

&lt;p&gt;To show the mute volume icon when volume reaches 0, I simply use a ternary operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Display current playback time/Time left.&lt;/strong&gt; Current playback time is the same as current playback position. Time left will be song duration minus current playback time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n850edxs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/3220/1%2AXy2c-4NwZemN8eCKNo5Wzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n850edxs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/3220/1%2AXy2c-4NwZemN8eCKNo5Wzw.png" alt="format from seconds to min:sec" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KaotyIIu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2612/1%2As_Z9cej8gdkJWdYmZFE5pw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KaotyIIu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2612/1%2As_Z9cej8gdkJWdYmZFE5pw.png" alt="time left" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mMHW2V30--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2580/1%2ArnEFkIKVLdTFZvewsmru-w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mMHW2V30--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2580/1%2ArnEFkIKVLdTFZvewsmru-w.png" alt="time display" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Due to the fact that the &lt;strong&gt;duration&lt;/strong&gt; property of audioRef.current is in seconds, we need to format it into &lt;strong&gt;min:sec&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We can only get the song duration after song data is being loaded so here we can use the &lt;strong&gt;onLoadedMetadata&lt;/strong&gt; event handler.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The loadedmetadata event is fired when the metadata has been loaded.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D7FcahN9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/800/1%2ABAyLKbRmTlF3Xh-ZCx08vw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D7FcahN9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/800/1%2ABAyLKbRmTlF3Xh-ZCx08vw.png" alt="onLoadedMetadata" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Time update on each tick.&lt;/strong&gt; Keep track of playback in real time, and if song reaches to the end, it will trigger the skip next song function. Here we use another event handler &lt;strong&gt;onTimeUpdate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D7FcahN9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/800/1%2ABAyLKbRmTlF3Xh-ZCx08vw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D7FcahN9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/800/1%2ABAyLKbRmTlF3Xh-ZCx08vw.png" alt="onTimeUpdate" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The timeupdate event is fired when the time indicated by the currentTime attribute has been updated.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have finished writing all the functions with the buttons and bars. The last thing we need to do is to put these functions in their corresponding &lt;strong&gt;onClick&lt;/strong&gt; event handler as well as incorporating them with our layout. (For bars, you would need to use &lt;strong&gt;onChange&lt;/strong&gt; event handler).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MMPBm_jn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2A4hcyyapiHT6LdwnWuWTeOg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MMPBm_jn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2A4hcyyapiHT6LdwnWuWTeOg.gif" alt="Done" width="369" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I have a bug free music player on my portfolio website created by myself. I know for sure this is not the best practice to build it this way, so please leave a comment if you know anything that can improve upon on my implementation.&lt;/p&gt;

&lt;p&gt;Here is my entire code for the music player component in JSX: &lt;a href="https://github.com/AnthonyZhang220/portfolio-website/blob/main/src/components/Footer/MusicPlayer.jsx"&gt;Code&lt;/a&gt;. If you like my work, please don’t forget to star my repo.&lt;/p&gt;

&lt;p&gt;Let’s connect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AnthonyZhang220"&gt;Github&lt;/a&gt; | &lt;a href="https://medium.com/@anthonyzhang220"&gt;Medium&lt;/a&gt;|&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zKMEUx4j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/800/0%2AEoZMySdyc1lZA2QS.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zKMEUx4j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/800/0%2AEoZMySdyc1lZA2QS.png" alt="Happy Coding" width="700" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Best_practices" class="ltag_cta ltag_cta--branded"&gt;Web Audio API best practices - Web APIs | MDN&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>mui</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Form Validation with Material UI Text Field component and React</title>
      <dc:creator>Anthony Zhang</dc:creator>
      <pubDate>Sat, 09 Dec 2023 03:31:34 +0000</pubDate>
      <link>https://dev.to/anthonyzhang220/form-validation-with-material-ui-text-field-component-and-react-23dl</link>
      <guid>https://dev.to/anthonyzhang220/form-validation-with-material-ui-text-field-component-and-react-23dl</guid>
      <description>&lt;p&gt;In this article, I will talk about form validation, and how you could use Text Field component in Material UI with React to create a great user experience in form validation.&lt;/p&gt;

&lt;p&gt;First of all, what is form validation? Form validation is one of the fundamental features in web development. Client-side validation helps ensure that the data, users submitted to the server side, is in the correct format. It stops the issues from reaching to the server thus saving times back and forth. However, form validation can sometimes be tricky because there are a lot of things to consider during the process.&lt;/p&gt;

&lt;p&gt;Below are a few functionalities I found necessary and important to include in the validation process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Format Validation&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Format validation is mainly about the use of regular expression. I will not go deep into the concept, but it basically helps restrict the input pattern to match the correct character format in strings. A few common use cases are formats with phone number, email address, mail address and password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Required&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;required&lt;/code&gt;, we are able to tell the end users what information we are looking for and remind them with some forms of feedbacks, such as an error message or a popup. This will prevent any submission with missing information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Feedback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is probably the most important part out of these three points. As front end developers, we could write a perfect logic for form validation, but without clear feedback, users could be confused with what to expect and thus results in drop in efficiency. This reminds me of an idea that Steve Jobs mentioned in the WWDC’97,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You’ve got to start with the customer experience and work backward to the technology.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think this is what we should keep in mind as we build web applications.&lt;/p&gt;

&lt;p&gt;To start our example, first we need to have our layout ready and import all the required components from Material UI. Here, I use component="form" for the  component, so it serves as a component equivalent to a integrated with the element.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
If we need to set this text field required, we can simply add required={true} as one of our props for  , and as we click on the submit button, a warning pops up indicating “Please fill out this field.”,

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--feebmdwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AEaKAt9oRC78MemX3mJUNrw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--feebmdwI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AEaKAt9oRC78MemX3mJUNrw.png" alt="Divider" width="568" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For form validation, we are going to first define the input rules like this,&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const reg = new RegExp("[a-z]");
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are other regex for different purposes, I will include one here for &lt;strong&gt;email&lt;/strong&gt; address. (You can easily find other regex rules on the internet)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(?:[a-z0–9!#$%&amp;amp;’&lt;em&gt;+/=?^_`{|}~-]+(?:.[a-z0–9!#$%&amp;amp;’&lt;/em&gt;+/=?^_`{|}~-]+)&lt;em&gt;|”(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])&lt;/em&gt;”)@(?:(?:&lt;a href="https://dev.to?:[a-z0%E2%80%939-]*[a-z0%E2%80%939]"&gt;a-z0–9&lt;/a&gt;?.)+&lt;a href="https://dev.to?:[a-z0%E2%80%939-]*[a-z0%E2%80%939]"&gt;a-z0–9&lt;/a&gt;?|[(?:(?:(2(5[0–5]|[0–4][0–9])|1[0–9][0–9]|[1–9]?[0–9])).){3}(?:(2(5[0–5]|[0–4][0–9])|1[0–9][0–9]|[1–9]?[0–9])|[a-z0–9-]*[a-z0–9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])+)])&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and then we can use test() to test whether the input matches the character combinations in strings, and return true/false. With custom hook in react useState() and form event handler onChange , we can write,&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [value, setValue] = useState("");
**const [valid, setValid] = useState(false);**

const handleValidation = (e) =&amp;gt; {
    //set value to user input
    setValue(e.target.value);

    //define regex     
    const reg = new RegExp("[a-z]");

    //test whether input is valid
    **setValid(reg.test(e.target.value));**
};
...
&amp;lt;TextField
    name="Name"
    label="Name"
    variant="outlined"
    value={value}
    **onChange={(e) =&amp;gt; handleValidation(e)}**
    required={true}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How do we let user know that their input are invalid?  has another useful prop error , where if we pass in false, it will change the color of the textfield border to red, indicating an invalid input. Here we can simply pass in valid like this,&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;TextField
    name="Name"
    label="Name"
    variant="outlined"
    value={value}
    onChange={(e) =&amp;gt; handleValidation(e)}
    **error={!valid}**
    required={true}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we put in “123” as our input,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pdX9fPH6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2ALXMwDbfrUN40oVdY9NWOkQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pdX9fPH6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2ALXMwDbfrUN40oVdY9NWOkQ.png" alt="Divider" width="519" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voilà! There we have it. A simple form validation with Text Field Material UI and React.&lt;/p&gt;

&lt;p&gt;Here is all the code in Code Sandbox: &lt;a href="https://codesandbox.io/s/magical-joana-ctr9hu?file=/src/App.js"&gt;Demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s connect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AnthonyZhang220"&gt;Github&lt;/a&gt; | &lt;a href="https://medium.com/@anthonyzhang220"&gt;Medium&lt;/a&gt; |&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bZQgQJGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AiGeyI4DqMBXD5Kwf0FO_IA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bZQgQJGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AiGeyI4DqMBXD5Kwf0FO_IA.png" alt="Divider" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/a/201378/6162027" class="ltag_cta ltag_cta--branded"&gt;How can I validate an email address using a regular expression?&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation" class="ltag_cta ltag_cta--branded"&gt;Client-side form validation - Learn web development | MDN&lt;/a&gt;
&lt;/p&gt;



</description>
      <category>webdev</category>
      <category>react</category>
      <category>mui</category>
      <category>form</category>
    </item>
  </channel>
</rss>
