Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass a value from React Context into a Redux-Saga function?

How can I pass something from the React context into my Redux Saga function?

I have a context value that I could retrieve in the react components with something like this

const { connectionObject } = useMyContext();

This would allow me to get the connectionObject instance from the context.

However, I'm also using redux saga in some other parts of my application. One of these redux sagas will need to use the personObject from the context.

Since Redux-Sagas are technically just regular generator functions themselves, I can't use useMyContext() in it to retrieve the connectionObject. For example, this wouldn't work

export function* saveData() {
   const { connectionObject } = useMyContext();  // This will not work
   yield performSaveSomething(connectionObject);
}

The Redux Saga are triggered from me dispatching a redux action from within a react component. A possible workaround could of course be getting the connectionObject from the context in the react component and then dispatch the action with the connectionObject as part of the payload which then passes it to the saga. But this feels totally anti-pattern and isn't elegant especially when the payload is supposed to be updated to the state but in this case is used only to be passed over to the saga. Here's an example to illustrate the awkwardness:

// In the React component
export const myReactComponent = (props) => {
   const { connectionObject } = useMyContext();
   dispatch(saveDataAction({ 
      data: 'some data here to be put in the redux store',
      connection: connectionObject 
   }));
}

// In the reducer
function reducer (state, action) {
   switch (action.type) {
      case SAVE_DATA_ACTION_TYPE:
         // action.payload has 2 attributes in this case: data and connection
         // But I am throwing away the connection one here in this reducer because that is only meant for the saga. Feels weird here.
         return {
            ...state,
            data: action.payload.data.  // Instead of using the whole payload, I've to use only the "data" attribute of the payload because only this mattered to the store. I can't use the whole payload because it has the connectionObject in there. Doesn't look elegant.
         }
   }
}

// And finally in the saga function
export function* saveData({ payload: { connection } }) {
   // Good thing here is I could finally access the connectionObject which came from the react context
   // However, it's also doesn't seem right that I'm passing the connectionObject through the payload which isn't a piece of data but as a dependency
   yield performSaveSomething(connection);
}
   

Is there a way where I can elegantly and easily pass a value retrieved from the React context and somehow pass it into my Redux-Saga function so that it can use it?

like image 853
Carven Avatar asked Sep 20 '25 23:09

Carven


1 Answers

My first inclination is to do what you proposed: include it in the payload of the action. The component knows the value, the saga needs the value, therefore it can pass the value in. This is likely the simplest solution.

Other than that... it kinda depends on the nature of the context value. Let's start with the simplest case and move to harder ones. Simplest case: There's only one copy of the context, and its value never changes. If it never changes, then it doesn't actually need to be in context. You can simply move the connectionObject into a file, export it, and then import it everywhere it's needed.


But you're using context, so it's probably a value that does change. So that brings us to the next case: There's only one copy, it's value does change, but only components care when it changes (the saga will just look it up when needed, and does not need to be notified). For this, i would probably have the context provider duplicate its state out to a global variable, which the saga checks.

let globalValue = {};
export const getGlobalValue = () => globalValue;

export const MyContextProvider = ({ children }) => {
  const [stateValue, setStateValue] = useState({});
  useEffect(() => {
    globalValue = stateValue;
  }, [stateValue]);

  return (
    <MyContext.Provider value={stateValue}>
      {children}
    </MyContext.Provider>
  )
}

// In the saga:
import { getGlobalValue } from './path/to/context/provider/file'

export function* saveData() {
   const connectionObject = getGlobalValue();
   yield performSaveSomething(connectionObject);
}

Next up in difficulty is where there's only one copy, the value changes, and both the components and the sagas need to be able to listen to changes in its value. For example, maybe the saga starts, checks the value, and if it sees that the value is false it needs to sleep until the value turns true.

To support this, you're going to need to have some sort of event emitter which the saga can subscribe to. There's many existing libraries for this kind of code, our you could roll your own, something like:

let listeners = [];
export const subscribe = (callback) => {
  listeners.push(callback);
  const unsubscribe = () => {
    let i = listeners.indexOf(callback);
    listeners.splice(i, 1);
  }
  return unsubscribe;
}

export const MyContextProvider = ({ children }) => {
  const [stateValue, setStateValue] = useState({});
  useEffect(() => {
    for (const listener of [...listeners]) {
      callback(stateValue);
    }
  }, [stateValue]);
  // ...
}

And finally, the case which is the hardest: there are multiple context providers. If that's the case, then there are multiple possible values, and no way for the saga to know which one to use. No way except to tell it of course, which you would do through the action payload, which then brings us back to the original idea.

I guess it would be possible to not pass the entire object in, but just an id, and then use one of the techniques above to look up the object. But i don't see that that's any better than passing the object itself.

like image 182
Nicholas Tower Avatar answered Sep 22 '25 12:09

Nicholas Tower