mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
perf(LiveGroupFilter, ChannelsTable): optimize state updates and re-rendering logic for improved performance
This commit is contained in:
parent
b470ae68db
commit
7cbbf2a270
2 changed files with 29 additions and 36 deletions
|
|
@ -95,8 +95,8 @@ const LiveGroupFilter = ({
|
|||
? groupStates.find((g) => g.channel_group === configuringGroupId)
|
||||
: null;
|
||||
const applyGroupChange = (nextGroupState) => {
|
||||
setGroupStates(
|
||||
groupStates.map((state) =>
|
||||
setGroupStates((prev) =>
|
||||
prev.map((state) =>
|
||||
state.channel_group === nextGroupState.channel_group
|
||||
? nextGroupState
|
||||
: state
|
||||
|
|
@ -497,8 +497,8 @@ const LiveGroupFilter = ({
|
|||
}, [playlist, channelGroups]);
|
||||
|
||||
const toggleGroupEnabled = (id) => {
|
||||
setGroupStates(
|
||||
groupStates.map((state) => ({
|
||||
setGroupStates((prev) =>
|
||||
prev.map((state) => ({
|
||||
...state,
|
||||
enabled: state.channel_group == id ? !state.enabled : state.enabled,
|
||||
}))
|
||||
|
|
@ -506,8 +506,8 @@ const LiveGroupFilter = ({
|
|||
};
|
||||
|
||||
const toggleAutoSync = (id) => {
|
||||
setGroupStates(
|
||||
groupStates.map((state) => {
|
||||
setGroupStates((prev) =>
|
||||
prev.map((state) => {
|
||||
if (state.channel_group != id) return state;
|
||||
const turningOn = !state.auto_channel_sync;
|
||||
const next = { ...state, auto_channel_sync: turningOn };
|
||||
|
|
@ -520,7 +520,7 @@ const LiveGroupFilter = ({
|
|||
if (currentStart && currentStart > 1) return next;
|
||||
|
||||
let proposedStart = 1;
|
||||
for (const other of groupStates) {
|
||||
for (const other of prev) {
|
||||
if (other.channel_group == id) continue;
|
||||
if (!other.enabled || !other.auto_channel_sync) continue;
|
||||
const otherMode =
|
||||
|
|
@ -550,8 +550,8 @@ const LiveGroupFilter = ({
|
|||
// Handle logo selection from LogoForm
|
||||
const handleLogoSuccess = ({ logo }) => {
|
||||
if (logo && logo.id && currentEditingGroupId !== null) {
|
||||
setGroupStates(
|
||||
groupStates.map((state) => {
|
||||
setGroupStates((prev) =>
|
||||
prev.map((state) => {
|
||||
if (state.channel_group === currentEditingGroupId) {
|
||||
return {
|
||||
...state,
|
||||
|
|
@ -564,7 +564,7 @@ const LiveGroupFilter = ({
|
|||
return state;
|
||||
})
|
||||
);
|
||||
ensureLogosLoaded(); // Refresh logos
|
||||
ensureLogosLoaded();
|
||||
}
|
||||
setLogoModalOpen(false);
|
||||
setCurrentEditingGroupId(null);
|
||||
|
|
@ -582,8 +582,8 @@ const LiveGroupFilter = ({
|
|||
};
|
||||
|
||||
const selectAll = () => {
|
||||
setGroupStates(
|
||||
groupStates.map((state) => ({
|
||||
setGroupStates((prev) =>
|
||||
prev.map((state) => ({
|
||||
...state,
|
||||
enabled: isVisible(state) ? true : state.enabled,
|
||||
}))
|
||||
|
|
@ -591,8 +591,8 @@ const LiveGroupFilter = ({
|
|||
};
|
||||
|
||||
const deselectAll = () => {
|
||||
setGroupStates(
|
||||
groupStates.map((state) => ({
|
||||
setGroupStates((prev) =>
|
||||
prev.map((state) => ({
|
||||
...state,
|
||||
enabled: isVisible(state) ? false : state.enabled,
|
||||
}))
|
||||
|
|
@ -1803,8 +1803,8 @@ const LiveGroupFilter = ({
|
|||
'fixed'
|
||||
}
|
||||
onChange={(value) => {
|
||||
setGroupStates(
|
||||
groupStates.map((state) => {
|
||||
setGroupStates((prev) =>
|
||||
prev.map((state) => {
|
||||
if (
|
||||
state.channel_group === group.channel_group
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -244,11 +244,10 @@ const ChannelRowActions = React.memo(
|
|||
</Box>
|
||||
);
|
||||
},
|
||||
// Custom comparator: only re-render when the actual channel changes.
|
||||
// The row object is a new TanStack Table reference on each render, but
|
||||
// row.original.id is stable. Callbacks read fresh data at call time.
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.row.original.id === nextProps.row.original.id
|
||||
// Custom comparator: skip re-render when the channel's data object hasn't
|
||||
// changed. row.original is stable when the underlying channel hasn't been
|
||||
// updated; it becomes a new reference when the store replaces that channel.
|
||||
(prevProps, nextProps) => prevProps.row.original === nextProps.row.original
|
||||
);
|
||||
|
||||
const ChannelsTable = ({ onReady }) => {
|
||||
|
|
@ -575,36 +574,30 @@ const ChannelsTable = ({ onReady }) => {
|
|||
}));
|
||||
};
|
||||
|
||||
const editChannel = async (ch = null, opts = {}) => {
|
||||
// If forceAdd is set, always open a blank form
|
||||
const editChannel = useCallback(async (ch = null, opts = {}) => {
|
||||
if (opts.forceAdd) {
|
||||
setChannel(null);
|
||||
setChannelModalOpen(true);
|
||||
return;
|
||||
}
|
||||
// Use table's selected state instead of store state to avoid stale selections
|
||||
const currentSelection = table ? table.selectedTableIds : [];
|
||||
console.log('editChannel called with:', {
|
||||
ch,
|
||||
currentSelection,
|
||||
tableExists: !!table,
|
||||
});
|
||||
const currentSelection =
|
||||
useChannelsTableStore.getState().selectedChannelIds;
|
||||
console.log('editChannel called with:', { ch, currentSelection });
|
||||
|
||||
if (currentSelection.length > 1) {
|
||||
setChannelBatchModalOpen(true);
|
||||
} else {
|
||||
// If no channel object is passed but we have a selection, get the selected channel
|
||||
let channelToEdit = ch;
|
||||
if (!channelToEdit && currentSelection.length === 1) {
|
||||
const selectedId = currentSelection[0];
|
||||
|
||||
// Use table data since that's what's currently displayed
|
||||
channelToEdit = data.find((d) => d.id === selectedId);
|
||||
channelToEdit = useChannelsTableStore
|
||||
.getState()
|
||||
.channels.find((d) => d.id === selectedId);
|
||||
}
|
||||
setChannel(channelToEdit);
|
||||
setChannelModalOpen(true);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const deleteChannel = async (id) => {
|
||||
console.log(`Deleting channel with ID: ${id}`);
|
||||
|
|
@ -1057,7 +1050,7 @@ const ChannelsTable = ({ onReady }) => {
|
|||
// from the store, so we don't need to recreate columns when logos load.
|
||||
// Note: tvgsLoaded is intentionally excluded - EditableEPGCell handles loading state internally
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[selectedProfileId, channelGroups, theme, tvgsById, epgs]
|
||||
[selectedProfileId, channelGroups, theme, tvgsById, epgs, editChannel]
|
||||
);
|
||||
|
||||
const renderHeaderCell = (header) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue