Redux – Your Next Web Dev Tool

Photo by: Marvin Meyer/Unsplash – https://unsplash.com/photos/SYTO3xs06fU

So, what’s the deal?

If you’ve ever used React as a frontend framework in any full-stack project, you’ll already be familiar with the concept of components and state (no, not geography). Among many other things, React allows developers to create components that can then be rendered out for display in a web page. Components are pieces of code that can be reused throughout your application. A good way to think of these is like a function or a method from your programming language of choice. However, instead of sorting an array or performing binary search, a component in React will return HTML used to represent things like a navigation bar, a login form, a button, etc. The beauty of React components is that once defined, a developer can then use them throughout their application without needlessly re-writing code. Furthermore, components can take props as an input. Again, think of props as parameters defined in a function or method definition. As an example, a button component could have button text that can be defined via the component’s props. So, whenever the developer wants to use the button component they could pass in “login” or “signup” depending on the context of the button.

From this, we move on to state. Importantly, components have state. This means that as a user interacts with the different components rendered on a web page, they will be changing pieces of information about the component they’re interacting with. A developer can then use the new state of a component to update other pieces of state or trigger events within the application. Let’s say a web page has a toggle switch that can be used to put the web application in light mode or dark mode. The toggle switch would be the component and the state of the toggle switch might be something like “toggled” or “not toggled”. When a user interacts with the toggle switch component we can use the toggle switch’s state to change the state of the entire application to light mode or dark mode. In other words, the “toggled” state would trigger dark mode and the “not toggled” state would trigger light mode.

Easy enough right? Well, not necessarily. In React, state can only be passed down the DOM tree. So, in the example above let’s say that the following tree represents my React app. It’s a simple application with three pages that a user can visit – the “app” node would be the root node for the application:

In the above tree, the “app” node would maintain the state for light mode and dark mode – let’s refer to this state as the “theme”. This “theme” state value would then need to be passed down to each of the “page” nodes so that their color scheme could change if the web application “theme” changes. But, the “theme” can only change when the toggle switch is interacted with and the toggle switch component would live within page 1, page 2, and page 3. So, how do we change the “theme” value in the “app” node from our “page” nodes? To do this, the “app” node must also pass down a function that will update the “theme” state value when the toggle switch is interacted with. When the toggle switch in any of the “page” nodes is toggled, the theme for the entire application can be updated using the function to update the “theme” state value. The new “theme” state will then be able to be passed down to each “page” node in the application. Remember, since state must be passed down there isn’t really a better way of handling this situation with React by itself.

Enter Redux

In all honesty, the above situation is pretty manageable. But, consider what happens as our application expands and we have multiple components on multiple pages all with their own state. This can very quickly become messy and difficult to manage as state and associated functions to update state need to be passed up and down the DOM tree to allow all of our components and pages to be working with the same state values.

The solution? Redux! Redux essentially stores all state values in a centralized location and removes the need to pass state all over our application. Using Redux, our above application schematic would now be:

With Redux, you can see that the components and pages in our application no longer store any state value. Instead, the state values are all stored within Redux and any page or component can look into the Redux store to get the current value of a specific piece of state. Furthermore, since Redux is a centralized location for our state values, there isn’t any concern that components are working with incorrect or stale state values.

Redux works by processing dispatched actions which are then resolved to specific values of state using reducers. The reducers are combined in a Redux store which is then used to pass all application state down from the root node of the application. With React, we would start by creating a Redux store. This would involve combining all of our reducers, defining any initial state (maybe something stored in local or session storage), and finally creating our store using the createStore redux function:

import {
    legacy_createStore as createStore, 
    combineReducers, 
    applyMiddleware 
} from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import { themeReducer } from './reducers/themeReducers';

const reducer = combineReducers({ theme: themeReducer });

const initialState = {};

const store = createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunk)));

export default store;

Next, we can wrap our application’s root node in a Provider tag from react-redux and give it the store we created above as its prop value. Again, this allows all components to have access to the state values stored in our Redux store:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

Next, we can define our reducers. In our example, we’re just using a theme reducer, but in a real-world application there will be reducers for each piece of state. Generally, a reducer is just a function that takes two parameters: an initial state value and an action. The action will be passed in when it is dispatched from one of our components or pages in React. Actions are generally objects with two keys: a type and a payload. The type key defines the type of action and is used by the reducer to resolve the specific action that was dispatched. The payload key is the state value that will be updated when the action is resolved. A reducer itself is just a simple switch statement used to resolve the action based on the action type. In the following reducer, if the action type is a theme change request, then the updated value of state will be taken out of the action payload. In any other case, the state parameter value provided will be returned:

import { THEME_CHANGE_REQUEST } from '../constants/themeConstants';

export const themeReducer = (state = { type: 'light' }, action) => {
    switch (action.type) {
        case THEME_CHANGE_REQUEST:
            return { type: action.payload };
        default:
            return state;
    }
}

To dispatch actions, we use the useDispatch hook from react-redux. When a change is detected in the toggle switch, the following function could be executed:

const handleCheckedEvent = () => {
        setChecked(!isChecked);
        isChecked 
            ? dispatch({ type: THEME_CHANGE_REQUEST, payload: 'light' }) 
            : dispatch({ type: THEME_CHANGE_REQUEST, payload: 'dark' });
    }

Here we can see that if the toggle switch is checked, then we dispatch a theme change request action with the payload being “light”. Conversely, if the toggle switch is not checked, then we dispatch a theme change request action with the payload being “dark”. This action will then be resolved by the above reducer and the payload value (“light” or “dark”) will be used to update the application theme.

With this in place, we can then use the useSelector hook from react-redux to pull out any piece of state from our Redux store:

const App = () => {
  const theme = useSelector(state => state.theme);

  return (
    <div id={theme.type}>
      <Router>
        <Routes>
          <Route path='/' element={<Navigate to='/login-signup'/>}/>
          <Route path='/login-signup' element={<LoginSignup/>}/>
        </Routes>
      </Router>
    </div>
  );
}

In our App component we get the value of our “theme” state with the useSelector hook and use that to set an id value on the outermost div. The value of this id can then be used to trigger light mode or dark mode css styles for the entire application.

Interested in learning more?

To get started learning more about Redux check out the “Getting Started” and “Tutorial” sections on their website: https://redux.js.org/introduction/getting-started and https://redux.js.org/tutorials/essentials/part-1-overview-concepts

Brad Traversy of Traversy Media is another great resource for all things related to web development. He has a knack for making complex topics very understandable and he has an excellent tutorial for Redux on his YouTube channel: https://www.youtube.com/watch?v=93p3LxR9xfM

Sources and References

Brad Traversy and Udemy. MERN eCommerce From Scratch. Retrieved from Udemy: https://www.udemy.com/course/mern-ecommerce/

Dan Abramov and the Redux documentation authors. Redux Essentials, Part 1: Redux Overview and Concepts. Retrieved from Redux: https://redux.js.org/tutorials/essentials/part-1-overview-concepts

My Summer In The Rainforest

Photo by: Kyle Cleveland / Unsplash – https://unsplash.com/photos/VDPFEyIrAn0

And So It Begins

This Summer, I had the opportunity to work as a Software Development Engineering intern with Amazon Web Services. I spent the Summer in Seattle, WA working as part of the Kendra team within the Machine Learning platform of AWS. This internship marked my first official experience working in the software industry and, despite some nerves, I was more than ready to get started. From my perspective, this opportunity was something I had been working for consistently since I began my Computer Science studies with Oregon State in the Winter quarter of 2021. My hard work was finally paying off.

On June 15th, the start of my journey began as I hopped on a plane in Cleveland, OH. After spending 5+ hours in the air and going through 3 different time zones I finally arrived at the Seattle-Tacoma International Airport. I eagerly departed the airplane and found my luggage at baggage claim. With all my belongings accounted for, I ordered an Uber and made my way to the Airbnb I would be staying at for the Summer.

With my internship not starting until June 20th, I had some time to get oriented to Seattle and my new way of life for the Summer. In the days leading up to my internship, I spent time learning the bus system, finding my route to the office, exploring the city, and getting my Airbnb to feel like more of a home.

Below are a few of the pictures I took while I was exploring Seattle.

My Project And Internship Experience

My first few days of work were spent doing general onboarding activities – setting up my laptop, getting correct access to various internal systems/services, setting up my email, meeting the team, etc. I had my first 1-on-1 with my mentor the Wednesday of my first week. During this 1-on-1, I was introduced to my internship project – I would be developing a real-time analytics dashboard that the Kendra management team could use to better understand how customers were using the Kendra service. I was told that I would be responsible for taking the project from concept all the way to production. This meant I would need to design the project infrastructure, create the project design document, implement the project infrastructure and code logic, and create associated unit tests. To say I was feeling overwhelmed is an understatement! With the internship duration being only 12 weeks, I instantly felt way over my head – both cloud application development and AWS were completely new concepts to me. Nonetheless, I was determined not to fail. I quickly began researching and doing everything I could to understand all about AWS and what services were offered.

A view of the Space Needle from my desk

During the research phase, I relied heavily on my mentor, my onboarding buddy, and the other members of the Kendra development team. AWS has a huge offering of cloud services, and the team played a huge role in helping keep my research efforts focused in the right direction as I worked on designing the project infrastructure. One of the tricky aspects of the project was that the Kendra service is available in 7 different AWS regions, and each region has exclusive customer data. This meant that the service I was building would need to process raw customer data from each region and then consolidate all the processed data together in one region so that a dashboard with analytics could be built to show all the combined data together.

After 2 weeks of heavy research, I finally had a service infrastructure design that I felt confident in! My service would start by using Amazon CloudWatch to trigger an AWS Lambda function which would be responsible for processing raw customer data. The event trigger would be based on a cron job that was setup to fire every 2 hours. Once triggered, the AWS Lambda function would begin by pulling in relevant raw data from DynamoDB, CloudWatch logs, and several other internal AWS services for processing. Once the processing was complete, the data would be written out in its final form as a JSON document into an Amazon S3 bucket partitioned by region, by year, by month, and by day. From here, the partitioned data would need to be consolidated into a single table. Now, you may be asking why not just consolidate the data upfront? Well, since each region will be processing its respective raw customer data every 2 hours, there is the possibility that one region will overwrite data already written into the S3 bucket by another region. Thus, the decision was made to keep the initial processed data upload partitioned by region to prevent cross-region overwriting. To consolidate the processed data from each region, I decided to use AWS Glue and AWS Athena. Glue crawlers were used to create tables out of the JSON files and Athena was used to merge all the data into a final table. Finally, a connection could be made between AWS Athena and AWS QuickSight allowing the consolidated processed data to be displayed using easy drag-and-drop analytics graphics offered by the QuickSight service.

After getting approval on my design, I went to work with implementation. I spent the next 10 weeks working on building the infrastructure, implementing the code logic, going through code reviews, and developing unit tests. The infrastructure itself was written using TypeScript and the code logic that went into the Lambda functions was written in Java. Both languages were new to me, but I found that the foundation built during my course work at OSU more than prepared me to quickly hit the ground running with both. For unit testing, I had the opportunity to work with JUnit and Mockito. Mocking and stubbing of objects was a brand-new concept to me, however I attribute both as a huge factor in helping me improve my coding skills. I learned that to successfully mock and stub objects, your code package needs to “correctly” inject different dependencies and for dependency injection I used Dagger. This led me to learn more about dependency injection and really shaped the way I look at efficiently developing a code package to allow for unit testing and better readability.

In the end, I was able to successfully get my project completed and released to production for internal use among the Kendra team. I also experienced a huge growth in my skills as a software developer – I was pushed hard to continuously grow and learn new concepts, and this ultimately helped take me to the next level in my journey to become a software developer. Excitingly, I also received a full-time return offer to rejoin the Kendra team after graduation!

Key Takeaways From My Internship

  1. Keep Calm and Continue Moving Forward

There were many, many moments during my internship that I felt overwhelmed, unsure, or confused. I found that the worst thing I could do in those moments was to panic. In that regard, I found that the best path forward was to remain calm and continue moving forward in search of understanding or a solution. Besides, where’s the fun in anything without a challenge?

  1. Don’t Struggle in Silence

I’ve found that the tech industry is full of highly intelligent, motivated, and hard working people. Sadly, I’ve also found that it is very easy for people in tech (myself included!) to end up with imposter syndrome. In my case, this can lead to me putting myself on an island and not reaching out for help in fear that I’ll be “found out”. I worry that not knowing something will shine a light on my inadequacies. During my internship, I learned that this was quite the opposite! I found that it was extraordinarily beneficial to reach out to a mentor or another team member when I was stuck or unsure. A problem that may have taken me a day to come to a solution on could be reduced to an hour with the help of someone more experienced. Not only that, but the opportunity to build a relationship with your teammates and learn from them is invaluable!

  1. Look for the Learning Opportunity

Perspective and attitude is everything. During my internship I consistently came up against challenges. Whenever you face a challenge you can choose to be frustrated by it or you can choose to see it as an opportunity for growth. I learned that if I can look at every challenge as an opportunity to grow and learn then I don’t have to be frustrated by them at all. In fact, with this mindset, I found myself seeking out more challenges that would push me to keep being better.

  1. Take a Walk

I can’t begin to tell you the number of problems I’ve solved by just getting up and walking away – my internship project was no different. Whether it’s going for a walk, a run, getting a coffee, or spending some time staring out the window, often I’ve found a seemingly “unsolvable” problem was only “unsolvable” because I was stuck looking at it in a very specific way. Getting up and doing something else gives your brain the opportunity to reset itself and when you come back you’ll often see the solution was right in front of you the whole time!