Local state, and global state: One container using another container's reducers, sagas, actions, etc

Viewed 66

I have an react app using redux where every container handles its own state.

However, I have some global states that I'm having a hard time deciding how to manage. The main one is everything having to do with authentication.

I want every container in my app have access to logged-in user's info, to the access and refresh tokens, etc. I also want certain authentication operations (say, refreshing a token, or logging out) to be available for dispatching throughout the app.

I ended up created a container that deals with everything Authentication (that has its own constants, actions, reducers, selectors and sagas). As such, whenever I want to dispatch an authentication operation (say, logout) I can do it from every page, just be importing the authentication reducer and actions.

My problem is that now, for every container on the app I have 2 sets of reducers, actions, etc: Those that belong directly to container that is currently active (say, user profile), and those that belong to the global app's authentication state (see below).

I feel like this goes against the principal of Separation of Concerns, but I have pulling hairs trying to figure out how to structure this.

Since authentication is such a basic element most web-apps, I am sure there's a "right way" of doing it, but my searches are not yielding any fruits.

Can anyone help? I'd love any tip or resource that may help.

enter image description here

1 Answers

I'm not sure what you mean when you say that you're violating separation of concerns. Having multiple reducers, actions, etc. being used in the same component is totally fine. It's a scope issue. Think of it like this, in javascript, every single function has access to the global scope and its own scope (this is simplified) and React Components are simply functions. If you have something that needs to be available globally (I don't like this word as it is not actually global - it's scoped to the top component), then set it up at the top of the tree.

One nice way to do this is to create your own custom hooks that you can import when and where you need that will expose what you need where you need it. The hooks can import what they need from the reducers / actions / etc. and only expose simple calls to the components. You'll still have multiple scoped stores connected to your components, but you won't have to include them over and over in each component. Just include the hook you want that has the functionality you need for that component, and you're done.

Here's a pseudo-example...

// mycustomhook.js
import action from './myactionfile.js'
import { useSelector } from 'react-redux' // not necessary to use this lib, just easy

export default useMyHook = () => {
  const myValue = useSelector(state => state.reducer.myValue)

  return {
     myValue,
     myFunction: action
  }
}

then in a component

// someComponent.js
import useMyHook from './mycustomhook.js'

export default props => {
  const {myValue, myFunction} = useMyHook()

  // rest of component here
}
Related