Stefan Gies
Stefan Gies

Reputation: 318

Redux useSelector with id field

I need your advice on filtering data with a selector. Suppose I have the following entities in my application. 1 organization has multiple devices which look the following in my state shape:

state {
   devices: {
       byId: [ 1 { name: device1 } ]
   }
   organizations: {
       byId: [ 
          1 { name: org1, devices: [1,2,3] } 
       ]
   }
}

Now I want to filter the devices inside the organization. This is something that I want to do with a selector. My selector looks like the following:

const selectDevice = (id: any) => (state: State) => state.devices.byId[id]


export const selectOrganizationDevices = (id: number) => (state: State) => {
    const organization = state.organizations.byId[id] || []
    return organization.devices.map(id => selectDevice(id)(state))
}

This should be working fine but my selector got called before I have dispatched the data from redux. I suppose that there's something wrong with my reducer or the component I've created.

My reducer looks like this:

return produce(state, draftState => {
        switch (action.type) {
            ...
            case OrganizationActionTypes.FETCH_DEVICES_SUCCESS: {
                draftState.byId = action.payload.entities.organizations
                draftState.allIds = Object.keys(action.payload.entities.organizations)
                draftState.loading = false
                break;
            }
            ...
            default: {
                return state
            }
        }
    })

My functional component looks like the following:

function Devices() {
    const dispatch = useDispatch();
    const devices = useSelector(selectOrganizationDevices(1))

    useEffect(() => {
        dispatch(fetchOrganizationDevices(1))
    }, [])

    const columns = []
    
    return (
        <Layout headerName={"Devices"}>
             <Table dataSource={devices} columns={columns}/>
        </Layout>
    )
}

The error I get now is organization.devices is undefined which says that the array of devices in the state is empty. It seems that useSelector is called before dispatch. How can I prevent redux of doing this? Or what should be changed in my code?

Upvotes: 1

Views: 2959

Answers (1)

markerikson
markerikson

Reputation: 67627

Yes, the useEffect hook runs after the first render. useSelector will run during the first render. So, your component code needs to safely handle the case where that data doesn't exist yet.

Also, don't put a hardcoded array/object literal in a selector like that, as it will be a new reference every time and force your component to re-render every time an action is dispatched. Either extract that to a constant outside of the selector, or use a memoization library like Reselect to create the selector.

Finally, you should be using our official Redux Toolkit package, which includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once. It also has a new createEntityAdapter API that helps you manage normalized state in the store.

Upvotes: 2

Related Questions