I am a beginner react developer, I am using custom hook and context for the first time and still have some confusion about it. My goal with this approach was to write clean decoupled code that I can use to provide my components with signalR data without having to drilldown for them. I looked around on the internet and I found context and well after looking around I developed the code in order to provide data from signalR stream to react components. I have structured my code in this way:
- connection.js - class creates a singleton instance of signalR connection and provides function to communicate with signalR server. (Eg. ping, login, sendMessage etc)
import * as signalR from "@microsoft/signalr";
const URL = process.env.HUB_ADDRESS;
class Connector {
connection;
events;
ticks;
static instance;
constructor() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl(URL)
.withAutomaticReconnect()
.build();
this.connection.start()
.then(() => {
console.log("Connected to the hub ");
this.connection.on("Connected", (data) => {
//onMessageReceived("Connected");
});
this.connection.on("Pong", (data) => {
console.log("Ping reply => ", {
data
})
this.events(data);
});
}).catch(err => console.error("Error connecting to the hub:", err));
this.events = (onMessageReceived) => {
console.log("EVENT => ", onMessageReceived);
this.connection.on("Connected", (data) => {
onMessageReceived("Connected");
});
this.connection.on("Pong", (data) => {
console.log("Ping reply II => ", {
data
})
onMessageReceived(data);
});
};
this.ticks = (onMessageReceived) => {
this.connection.stream("SymbolQuotes")
.subscribe({
next: (quote) => {
onMessageReceived({
quote
})
},
complete: (data) => {
if (data && data.error) {
console.error("Stream completed with error:", data.error);
} else {
console.log("Stream completed successfully");
}
},
error: (err) => {
console.error("Error in stream:", err);
}
});
};
}
disconnect = () => {
this.connection.stop()
.then(() => {
console.log("Disconnected .... ");
}).catch(err => console.error("Error disconnecting:", err));
}
connect = () => {
console.log(this.connection.state);
this.connection.send("ConnectCtrader", "zobad").then(data => {
console.log("SENT")
})
.catch(err => console.error("Error sending connection request:", err));
}
newMessage = (messages) => {
console.log(this.connection.state)
if (messages === "Disconnect")
this.connection.send("Disconnect", "zobad")
.then(() => {
console.log("SENT");
})
.catch(err => console.error("Error sending disconnect request:", err));
else {
this.connection.send(messages).then(() => {
console.log("Message sent: Message>>> ", messages);
}).catch((err) => {
console.log("Message send failed: Message>>> ", messages);
console.log("ERROR: ", err)
});
}
}
getStatus = () => {
return this.connection.state;
};
cleanup = () => {
this.connection.off("Connected");
this.connection.off("Pong");
this.connection.off("SymbolQuotes");
};
}
export const getSignalRInstance = () => {
if (!Connector.instance || Connector.instance.getStatus() === "Disconnected") {
console.log("Creating a new connection instance")
Connector.instance = new Connector();
}
return Connector.instance;
}
- useSignalR.js - A custom hook and a context that provides the signalR instance to the components:
const SignalRContext = createContext();
export const useSignalR = () => {
const INDEX_NOT_FOUND = -1;
const [tickData, setTickData] = useState([]);
const [ticker, setTicker] = useState({});
const [connectionStatus, setConnectionStatus] = useState("Disconnected");
const {connector} = useContext(SignalRContext);
const {tick} = connector;
const updatePrice = (price)=>{
const { symbolId, symbolName, bid, ask, digits } = price;
setTickData((prevTickData) => {
const existingIndex = prevTickData.findIndex((item) => item.symbolId === symbolId);
if (existingIndex !== INDEX_NOT_FOUND && prevTickData.length > 0) {
const updatedPriceTick = [...prevTickData];
updatedPriceTick[existingIndex] = { symbolId, symbolName, bid, ask, digits };
return updatedPriceTick;
} else {
return [...prevTickData, { symbolId, symbolName, bid, ask, digits }];
}
});
};
const connect = useCallback(() => {
connector.connect();
connector.ticks((tick) => {
const quote = tick.quote;
setTickData((prevTickData) => {
const { symbolId, symbolName, bid, ask, digits } = quote;
const existingIndex = prevTickData.findIndex((item) => item.symbolId === symbolId);
if (existingIndex !== -1 && prevTickData.length > 0) {
const updatedPriceTick = [...prevTickData];
updatedPriceTick[existingIndex] = { symbolId, symbolName, bid, ask, digits };
return updatedPriceTick;
} else {
return [...prevTickData, { symbolId, symbolName, bid, ask, digits }];
}
});
});
},[connector]);
const disconnect = useCallback(() => {
connector.disconnect();
setConnectionStatus("Disconnected");
},[connector]);
const sendPing = useCallback(() => {
console.info("Sending ping");
connector.newMessage("Ping");
},[connector]);
const sendCustomMessage = useCallback((message) => {
connector.newMessage(message);
},[connector]);
return { connectionStatus, tickData, connect, disconnect, sendPing, sendCustomMessage };
};
export const useSignalRContext = () => useContext(SignalRContext);
export const SignalRProvider = ({ children }) => {
const connector = getSignalRInstance();
return (
<SignalRContext.Provider value={{ connector}}>
{children}
</SignalRContext.Provider>
);
};
Now I want to use this updated tickData(from useSignalR) in one of my component. How should I go about doing this? I have tried creating my component and tried to use tickData like so:
const MarketWatch = () =>{
const hook = useSignalR();
const {tickData, sendPing} = hook;
console.log("MarketWatch ticks ", { tickData });
return (
<>
</>
)
};
export default MarketWatch;
I am not getting the updated tickData in my component, however I have tested that in the custom hook the tickData seems to updating.
Can someone point me out if there is something I am doing wrong? Any mistake my beginner brain cannot figure out? Is there a better simpler approach of achieving my goal?
Thank you for any help in advance.