Search code examples
javascriptreactjsreduxstore

Redux Store Help: Students and Schools


I'm working on a large-scale react/redux web app, and recently ran into a problem while implementing a new feature. I'm concerned that I'm not following redux best practices. I figured I'd simplify the situation and post it here in order to see how others are working in redux.

Here is the situation:

I have a tab bar that has two items: "Students" and "Schools"

When the "Students" tab is active, a list of all students is displayed, along with a button at the bottom of the page to display more students.

When the "Schools" tab is active, a similarly paginated list of schools is displayed.

When you click on a student on the "Students" page, you are directed to a page that shows more information about the student.

When you click on a school in the "Schools" page, you are directed to a page that shows more information about the school, AS WELL AS a paginated list of all students that attend the school.

API endpoints for this are quite simple:

GET /students - gets all students
GET /students/1 - gets student with id "1"
GET /schools - gets all schools
GET /schools/1 - gets school with id "1"
GET /students?schoolId=1 - gets all students attending school with id "1"

The problem I'm running into is that I cannot go to the "Students" tab, paginate through, then click on the "Schools" tab, click into one, paginate through some students, and return to the "Students" tab without seeing the students from the "Schools" tab showing up in the general "Students" tab list.

I can think of three ways to handle this in redux:

Solution 1:

{
    students: {
        all: {
            "123": {
                id: "123",
                name: "Bob",
                schoolId: 1,
                fetchStatus: ""
            }
        },
        fetchStatus: ""
    },
    schools: {
        all: {
            "321": {
                id: "321",
                name: "Redux University",
                fetchStatus: "" 
            }
        },
        fetchStatus: ""
    },
    studentsPage: {
        pageNumber: 1,
        all: [ "123" ]
    },
    schoolsPage: {
        pageNumber: 1,
        all: [ "321" ]
    },
    schoolPage: {
        studentsList: {
            pageNumber: 1,
            all: [ "321" ]
        }
    }
}

This approach is the largest, but "flattest" and contains the most information in redux versus in state. It's a bit hard to reason about, but it also does not duplicate data in many different places.

Solution 2:

{
    students: {
        all: {
            "123": {
                id: "123",
                name: "Bob",
                schoolId: 1,
                fetchStatus: ""
            }
        },
        fetchStatus: ""
    },
    schools: {
        all: {
            "321": {
                id: "321",
                name: "Redux University",
                fetchStatus: "" 
            }
        },
        fetchStatus: ""
    }
}

This approach removes the page-related entries, and keeps them purely in the state of the react components that render the lists themselves.

Solution 3:

{
    students: {
        pageNumber: 1,
        all: {
            "123": {
                id: "123",
                name: "Bob",
                schoolId: 1,
                fetchStatus: ""
            }
        },
        fetchStatus: ""
    },
    schools: {
        all: {
            "321": {
                id: "321",
                name: "Redux University",
                fetchStatus: "",
                studentsPageNumber: 1,
                studentsFetchStatus: "",
                students: [
                    "123": {
                        id: "123",
                        name: "Bob",
                        schoolId: 1
                    }
                ]
            }
        },
        fetchStatus: ""
    }
}

This approach nests students inside of schools, as well as outside. It's easiest to handle, but also duplicates data and makes it harder to keep student info in sync.

Solution 4:

{
    students: {
        all: {
            "123": {
                id: "123",
                name: "Bob",
                schoolId: 1,
                fetchStatus: ""
            }
        },
        fetchStatus: ""
    },
    schools: {
        all: {
            "321": {
                id: "321",
                name: "Redux University",
                fetchStatus: "" 
            }
        },
        fetchStatus: ""
    }
}

This approach is the same of solution 2, but instead of keeping the list info in react state, it's kept in the store and every time pages are navigated the students entries are cleared.

Maybe I'm thinking about redux wrong, but I'm having trouble figuring this out. Anyone have any ideas?

Thanks so much!


Solution

  • I would generally recommend approach #1, which matches the structure I described in the Normalizing State Shape page in the Redux docs. While you can organize your state tree any way you want, the general encouraged practice is to normalize the state and treat it like a mini client-side database. This is usually the best way to handle cached data.

    From there, you can use lists of IDs to represent the subset of data needed for a given screen. There's an excellent post called Advanced Redux Entity Normalization that goes into more detail on this approach.

    If writing the logic to save, update, and retrieve those items in the store is difficult, you may want to consider using one of the many entity/collection management addon libraries available. I personally am a fan of the Redux-ORM library, and show how to use it in my "Practical Redux" tutorial series. That said, feel free to use whichever approach you find easiest to work with.