❔ React Redux SignalR
I'm trying to implement an auto-refreshing datagrid (Ant Design Table). Is there a good article on how to implement the React part?
public sealed class StockTickerHub : Hub
{
private readonly IStockTickerService _stockTickerService;
public StockTickerHub(IStockTickerService stockTickerService)
{
_stockTickerService = stockTickerService;
}
public ChannelReader<Stock> GetStockTickerStream(CancellationToken cancellationToken)
{
var channel = Channel.CreateUnbounded<Stock>();
_ = _stockTickerService.SubscribeAsync(channel.Writer, cancellationToken);
return channel.Reader;
}
}
public class Stock
{
public required string Symbol { get; init; }
public required double Price { get; init; }
}
public interface IStockTickerService
{
Task SubscribeAsync(ChannelWriter<Stock> writer, CancellationToken cancellationToken);
}
public sealed class StockTickerService : IStockTickerService
{
public async Task SubscribeAsync(ChannelWriter<Stock> writer, CancellationToken cancellationToken)
{
try
{
while (true)
{
var stock = new Stock
{
Symbol = "TESLA",
Price = Math.Round(Random.Shared.NextDouble() * 100, 2)
};
await writer.WriteAsync(stock, cancellationToken);
await Task.Delay(1000, cancellationToken);
}
}
finally
{
writer.Complete();
}
}
}
public sealed class StockTickerHub : Hub
{
private readonly IStockTickerService _stockTickerService;
public StockTickerHub(IStockTickerService stockTickerService)
{
_stockTickerService = stockTickerService;
}
public ChannelReader<Stock> GetStockTickerStream(CancellationToken cancellationToken)
{
var channel = Channel.CreateUnbounded<Stock>();
_ = _stockTickerService.SubscribeAsync(channel.Writer, cancellationToken);
return channel.Reader;
}
}
public class Stock
{
public required string Symbol { get; init; }
public required double Price { get; init; }
}
public interface IStockTickerService
{
Task SubscribeAsync(ChannelWriter<Stock> writer, CancellationToken cancellationToken);
}
public sealed class StockTickerService : IStockTickerService
{
public async Task SubscribeAsync(ChannelWriter<Stock> writer, CancellationToken cancellationToken)
{
try
{
while (true)
{
var stock = new Stock
{
Symbol = "TESLA",
Price = Math.Round(Random.Shared.NextDouble() * 100, 2)
};
await writer.WriteAsync(stock, cancellationToken);
await Task.Delay(1000, cancellationToken);
}
}
finally
{
writer.Complete();
}
}
}
2 Replies
configureStore.ts
stockSlice.ts
hooks.ts
How do I spawn the SignalR connection correctly and maintain it?
import { configureStore } from "@reduxjs/toolkit";
import stockReducer from "./stockSlice";
export const store = configureStore({
reducer: {
stock: stockReducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {stock: StockState}
export type AppDispatch = typeof store.dispatch;
import { configureStore } from "@reduxjs/toolkit";
import stockReducer from "./stockSlice";
export const store = configureStore({
reducer: {
stock: stockReducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {stock: StockState}
export type AppDispatch = typeof store.dispatch;
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export type Stock = Readonly<{
symbol: string;
price: number;
}>;
export type StockState = Readonly<{
stocks: Stock[];
}>;
const initialState: StockState = {
stocks: [],
};
const stockSlice = createSlice({
name: "stock",
initialState: initialState,
reducers: {
addStock(state, action: PayloadAction<Stock>) {
state.stocks.push(action.payload);
},
},
});
export const { addStock } = stockSlice.actions;
export default stockSlice.reducer;
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export type Stock = Readonly<{
symbol: string;
price: number;
}>;
export type StockState = Readonly<{
stocks: Stock[];
}>;
const initialState: StockState = {
stocks: [],
};
const stockSlice = createSlice({
name: "stock",
initialState: initialState,
reducers: {
addStock(state, action: PayloadAction<Stock>) {
state.stocks.push(action.payload);
},
},
});
export const { addStock } = stockSlice.actions;
export default stockSlice.reducer;
import type { RootState, AppDispatch } from './configureStore';
import { useDispatch, useSelector, type TypedUseSelectorHook } from 'react-redux';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
import type { RootState, AppDispatch } from './configureStore';
import { useDispatch, useSelector, type TypedUseSelectorHook } from 'react-redux';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
import {
HttpTransportType,
HubConnectionBuilder,
LogLevel,
} from "@microsoft/signalr";
import { Table } from "antd";
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { addStock, Stock } from "../store/stockSlice";
const connection = new HubConnectionBuilder()
.withUrl("https://localhost:5001/stockticker", {
skipNegotiation: true,
transport: HttpTransportType.WebSockets,
})
.configureLogging(LogLevel.Information)
.withAutomaticReconnect()
.build();
const Grid = () => {
const dispatch = useDispatch();
const [stocks, setStocks] = useState<Stock[]>([]);
useEffect(() => {
async function start() {
try {
await connection.start();
const reader = await connection.invoke("getStockTickerStream");
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
dispatch(addStock(value));
setStocks((prevStocks) => [...prevStocks, value]);
}
} catch (err) {
console.error(err);
}
}
start();
return () => {
// connection.stop();
};
}, [dispatch]);
return (
<Table dataSource={stocks}>
<Table.Column title="Symbol" dataIndex="symbol" key="symbol" />
<Table.Column title="Price" dataIndex="price" key="price" />
</Table>
);
};
export default Grid;
import {
HttpTransportType,
HubConnectionBuilder,
LogLevel,
} from "@microsoft/signalr";
import { Table } from "antd";
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { addStock, Stock } from "../store/stockSlice";
const connection = new HubConnectionBuilder()
.withUrl("https://localhost:5001/stockticker", {
skipNegotiation: true,
transport: HttpTransportType.WebSockets,
})
.configureLogging(LogLevel.Information)
.withAutomaticReconnect()
.build();
const Grid = () => {
const dispatch = useDispatch();
const [stocks, setStocks] = useState<Stock[]>([]);
useEffect(() => {
async function start() {
try {
await connection.start();
const reader = await connection.invoke("getStockTickerStream");
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
dispatch(addStock(value));
setStocks((prevStocks) => [...prevStocks, value]);
}
} catch (err) {
console.error(err);
}
}
start();
return () => {
// connection.stop();
};
}, [dispatch]);
return (
<Table dataSource={stocks}>
<Table.Column title="Symbol" dataIndex="symbol" key="symbol" />
<Table.Column title="Price" dataIndex="price" key="price" />
</Table>
);
};
export default Grid;
Grid.tsx
is the problematic code. It's completely wrong
https://github.com/fernetowac/settlechat/blob/86233d4ae762f54fbeccd87cdf62e325b43edb4b/SettleChat/ClientApp/src/store/SignalR.ts
https://github.com/fernetowac/settlechat/blob/86233d4ae762f54fbeccd87cdf62e325b43edb4b/SettleChat/ClientApp/src/components/SignalRContainer.tsx
this might be helpful but dunnoLooks like nothing has happened here. I will mark this as stale and this post will be archived until there is new activity.