Passing data between react context

Viewed 333

I'm having troubles passing data from one context to the other in React. I have some job data that is received from a SignalR connection and I need to pass it to a specific job context, but I'm not sure how to do this.

I have the following code:

SignalRContext

export interface SignalRContextProps {
  connect: () => void;
}

export const SignalRContext = createContext<SignalRContextProps>(null!);

export const useSignalRContext = (): SignalRContextProps => {
  const {onProgressReceived} = useContext(JobsContext);

  const connect = () => {
    //Removed a lot of connection setup code for readability
    const connection = new HubConnectionBuilder().build();
    connection.on('JobReportProgress', onProgressReceived);
    connection.start();
  };

  return {
    connect,
  };
};

SignalRContextProvider

type Props = {
  children: ReactElement | ReactElement[];
}

export const SignalRContextProvider = (props: Props) => {
  const {children} = props;
  const signalRContext = useSignalRContext();

  return (
    <SignalRContext.Provider value={signalRContext}>
      {children}
    </SignalRContext.Provider>
  );
};

JobsContext

export const JobsContext = createContext<JobsContextProps>(null!);

export const useJobsContext = (): JobsContextProps => {
  const [jobs, setJobs] = useState<Job[]>([]);

  const load = async (): Promise<void> => {
    const jobs = await getAllJobs();
    setJobs(jobs);
  };

  const onProgressReceived = (progress: JobProgress) => {
    console.log(jobs);
    const currentJob = jobs.find((job) => job.id === progress.id);
    console.log(currentJob); //currentJob will always be empty because jobs array is NULL on receiving progress.
    }
  };

  return {
    load, onProgressReceived, jobs
  };
};

JobsContextProvider

interface Props {
  children: ReactElement | ReactElement[];
}

export const JobsContextProvider = (props: Props): ReactElement => {
  const {children} = props;
  const jobsContext = useJobsContext();

  return (
    <JobsContext.Provider value={jobsContext}>
      {children}
    </JobsContext.Provider>
  );
};

index

ReactDOM.render(
  <JobsContextProvider>
    <SignalRContextProvider>
      <App />
    </SignalRContextProvider>
  </JobsContextProvider>,
  document.getElementById('root'),
);

Flow

  1. In my app.tsx I start the SignalR connection by calling signalRContext.connect() created by const signalRContext = useContext(SignalRContext);
  2. I go to my job page where my 6 jobs are loaded from the backend via my context
const {load} = useContext(JobsContext);
await load();
  1. I trigger a job and I see that the SignalR context is calling onProgressReceived on the jobContext. But for some reason the jobs array is empty so I can't update the correct job. Is seems that a new context is created instead of reusing the existing context.

Anyone has an idea how I can make my SignalRContext pass data to my JobContext? Or maybe there is a better system that using context for this?

UPDATE 1:

I have the feeling that there is something strange going on with the HubConnection instance. When I register my 'onProgressReceived' function as a callback function on the 'JobReportProgress' event then it doesn't work. But when I first save the progress with setState and trigger the 'onProgressReceived' function with useEffect it seems to be working. Small example:

const {jobs, onProgressReceived} = useContext(JobsContext);
const [progress, setProgress] = useState<JobProgress | null>(null);

//Change the connection.on line with the following
connection.on('JobReportProgress', onReceived);

//And then we trigger the function on change
const onReceived = (progress: JobProgress) => {
  setProgress(progress);
};

useEffect(() => {
  if (progress !== null) {
    onProgressReceived(progress);
  }
}, [progress]);

This seems to be working, but not sure why I first need to save my progress with useState.

1 Answers

I had very same issue a while ago. I even asked question about it Here
There are ways to connect nested context to each other, but then code gets messy.

  1. At first I used event listeners and was dispatching events when some data received, but didn't worked well.

  2. Then I tried passing callbacks in context value. Pass callback from JobsContextProvider ex: onSignalData and call this callback when data is updated in SignalRContextProvider and save in JobsContextProvider.

  3. Combined these 2 contexts into one and it worked well, but didn't liked it as well. Code got messy.

  4. Then I give up using contexts in this type of data and used redux with Redux Toolkit and RTK Query. It works very vell, code is well organized and this is the best solution I have found so far.

Let me know in comments which solution works best for you and I can write exact pseudocode for that solution.

Related