Normalization

Here's another thought exercise in terms of handling data in your app.

Let's say we have an author with bo and we wish to display the details of that author and underneath a list of recent book entries.

A typical way of doing this (the direct data handling approach) which you often see in tutorials, is as follows:

export function AuthorPage() {

    const data = {
        author: {
            id: 'author:1',
            name: 'J.K. Rowling',
            books: [
                {
                    id: 'book:2',
                    name: 'The Running Grave',
                    year: 2003,
                    upvotes: 1
                },
                {
                    id: 'book:1',
                    name: 'Harry Potter and the Philosopher\'s Stone',
                    year: 1997,
                    upvotes: 99
                }
            ]
        }
    }

    return (
        <div>
            <div className="flex items-center space-x-6">
                <img />
                <h1>
                    {data.author.name}
                </h1>
            </div>
            <h2>
                Recent publications
            </h2>
            <ul>
                {data.author.books.map(book => {

                    return (
                        <div key={book.id} className="flex items-center space-x-6">
                            <div className="flex-grow">{book.name}</div>
                            <div>{book.upvotes}</div>
                        </div>
                    )
                })}
            </ul>
        </div>
    )

}

Downsides of this approach

  • Tight Coupling of Data: The book data is directly nested within the author object, making it difficult to update a book independently without refetching the entire author object.
  • Redundancy in Data Management: If books are used in different parts of the application, each instance requires separate fetching and management, leading to redundancy and inefficiency.
  • Increased Complexity: Managing hierarchical data in this way can lead to complex code, as each level of the hierarchy needs to be individually managed and updated.
  • Difficulty in State Management: With deeply nested data, keeping the state consistent and up-to-date across different components can be challenging.
  • Scalability Issues: As the application grows, managing nested data becomes increasingly cumbersome, affecting the scalability and maintainability of the code.
  • Limited Reusability: Components are less reusable because they are tightly coupled with the specific data structure.

This is where the Redux approach comes in.

Redux approach

The store's initial state:

{
    "Author": {
        "author:1": {
            "id": "author:1",
            "name": "J.K. Rowling",
            "books": [
                "book:1",
                "book:2"
            ]
        }
    },
    "Book": {
        "book:2": {
            "id": "book:2",
            "name": "The Running Grave",
            "year": 2003,
            "upvotes": 1
        },
        "book:1": {
            "id": "book:1",
            "name": "Harry Potter and the Philosopher's Stone",
            "year": 1997,
            "upvotes": 99
        }
    }
}
import { useSelector } from 'react-redux'

function BookListItem(props) {

    // this is also tightly coupled.
    // the redux community has plenty of examples
    // on how to decouple the mapping of state to these props
    const { book } = useSelector(state => {

        return {
            book: state.Book[props.bookId]
        }
    })

    return (
        <div key={book.id} className="flex items-center space-x-6">
            <div className="flex-grow">{book.name}</div>
            <div>{book.upvotes}</div>
        </div>
    )
}

export function AuthorPage() {

    const { author } = useSelector(state => {

        return {
            author: state.Author[props.authorId]
        }
    })

    return (
        <div>
            <div className="flex items-center space-x-6">
                <img />
                <h1>
                    {author.name}
                </h1>
            </div>
            <h2>
                Recent publications
            </h2>
            <ul>
                {author.books.map(bookId => {

                    return (
                        <BookListItem key={bookId} bookId={bookId} />
                    )
                })}
            </ul>
        </div>
    )
}

Upsides of this approach

  • Decoupling of Data: By using Redux, data for authors and books are stored separately, making it easier to manage and update them independently.
  • Centralized State Management: Redux provides a centralized store for state management, enhancing consistency and predictability across the application.
  • Improved Scalability: With a more organized and centralized state, the application becomes more scalable and easier to maintain as it grows.
  • Enhanced Reusability: Components become more reusable because they are no longer tightly coupled to specific data structures, and they interact with the Redux store instead.
  • Simplified Data Fetching and Updating: Updating a single item in the store automatically reflects across all components that use that data, simplifying state updates.
  • Increased Performance: Reduces unnecessary re-renders and data fetching, as components subscribe only to the relevant parts of the state, improving performance.
  • Easier Debugging and Testing: Redux's predictable state management and dev tools make debugging and testing more straightforward.
  • Better State Synchronization: Ensures consistent state across the application, avoiding issues related to unsynchronized states in different components.