Typical usage pattern not clear
Hi!
I really like your work, but I'm having trouble understanding the typical usage pattern - specifically "how should this work?" based on the README and examples.
Typically, I expect a custom context provider to work something like this (based on your library, in pseudo-code):
const SignalRContext = createSignalRContext();
export const MainComponent = () => {
return (
<SignalRContext.Provider
url={'https://localhost:5000/hub'}
onOpen={con => console.log('Connection Id: ' + con.connectionId)}
onReconnect={con => console.log('Reconnected Id: ' + con.connectionId)}
>
<ACustomComponent>
<NestedComponent />
</ACustomComponent>
</SignalRContext.Provider>
);
};
My assumption is that I should create a single provider for a large portion of my project, maintaining one connection throughout the user session. This would allow all components that need server communication to use your hooks/methods. In my case, SignalR will be used for:
- Global updates (connection dropped, user logged out, etc.) shown as popups independent of the current screen
- Updates for specific components (e.g., "text changed in a text field")
Then, inside ACustomComponent and/or NestedComponent (separate files for each component), I should be able to access the context's methods, specifically invoke and useSignalREffect.
However, from what I observe (and I may be wrong since I'm not deeply experienced with React), to access these functionalities, I have to create a new SignalRContext inside the aforementioned components. This causes the connection to be null, and the functionality doesn't work.
I've tried working around this by replacing:
const SignalRContext = createSignalRContext();
with:
export const SignalRContext = createSignalRContext();
and using this export to access invoke and useSignalREffect.
However, this seems incorrect as it contradicts the typical React context pattern usage which should allow me to just import "context" functionalities from your implementation and use them directly.
Am I doing/understanding something wrong way, does your implementation need adjustment or maybe there is no way around it?
Hi, createSignalRContext is designed for creating few signalr connection in and app. Let me explain by an example, assume you need tow connection and use them together in a component, so you just need use hook of each connection. this approach is because of it.
So, in theory, according to react guides about hooks - i should be able to encompass my components in nested providers, like:
const SignalRContext = createSignalRContext();
const SignalRSecondContext = createSignalRContext();
export const MainComponent = () => {
return (
<SignalRContext.Provider
url={'https://localhost:5000/hub'}
onOpen={con => console.log('Connection Id: ' + con.connectionId)}
onReconnect={con => console.log('Reconnected Id: ' + con.connectionId)}
>
<SignalRSecondContext.Provider
url={'https://localhost:10000/hub2'}
>
<ACustomComponent>
<NestedComponent />
</ACustomComponent>
</SignalRSecondContext.Provider>
</SignalRContext.Provider>
);
};
And be able to resolve the hook inside nested component for each one separately, yes?
Possibly using some sort of a key, given that the constructor for provider would require said key, like createSignalRContext('con1')?
This would require invoke and useSignalREffect to also require said identifier as mandatory parameter, as well as use of context hook inside of your wrapper for storing all of the created connections plus logic to handle unmount.
Is this something that you could consider in the future or should I stay with my current approach stick with export const SignalRContext = createSignalRContext(); for each provider created?
This seems a bit dangerous, in theory you could import this anywhere, not just inside components encompassed by your provider(?).
Also, thank you for such a quick reply!
This seems a bit dangerous, in theory you could import this anywhere, not just inside components encompassed by your provider(?).
This issue is about react Context Provider, I don't think we should be wary about it.
Is this something that you could consider in the future or should I stay with my current approach stick with export
const SignalRContext = createSignalRContext();for each provider created?
I have no resistance to change, but I don't see any reason to change it now. Stay with it, i will fix any issue if you encounter it.
Possibly using some sort of a key, given that the constructor for provider would require said key, like
createSignalRContext('con1')?
Creating an instance makes it easier to manage stuffs related to that instance. For example, we could make a different provider with specific parameters.
I agree with the OP. The current implementation seems like a signalR hub connection hidden the React component tree by means of a Provider, but the Context/Provider pattern isn't fully implemented because to access the context you have to import it's instance.
React Context is designed to easily access the nearest provider if you traverse up the component tree with useContext(Context). Note that Context is not an instance.
For example when you have a <ThemeProvider value="light">, and a <ThemeProvider value="dark"> somewhere in a child component.
With context, if you have 2 immediately nested Providers, you cannot access the parent provider from a anymore. This is by design.
In case of SignalR with 2 hub providers, there is no order or priority of those /hub and /hub2 connections, so the context pattern is a bad fit for the signalR use case, unless you have one provider that manages multiple hub connections.
Then it would look like something like this:
<SignalRContext.Provider
hubs={{ hub: 'https://localhost:5000/hub', hub2: 'https://localhost:5000/hub' }
onOpen={con => console.log('Connection Id: ' + con.connectionId)}
onReconnect={con => console.log('Reconnected Id: ' + con.connectionId)}
>
<MyComponent />
</SignalRContext.Provider>
// MyComponent
import { useSignalREffect } from 'react-signalr/signalr';
useSignalREffect('hub2', 'onMessage', (message) => {
console.log('Received message from hub2', message);
}, [])