-
Hello guys, I am currently stuck at a very specific problem using Redux Toolkit. I have a slice that store objects from a database and then update them using WebSocket. I already have the assignation of objects when initializing and updating. For now, I am stuck when I am displaying the object into the component. This component can be instancied many many times, and for optimization, I can't check on each component instance if the variable has been edited. I added listenerMiddleware to try to listen when event is triggered but the event is triggered every time an object is modified. So, how can I trigger an event for every specific object to listen only on the concerned component? Effect on component to display useEffect(() => {
const unsubscribe = reduxDispatcher(
addListener({
actionCreator: updateObjects,
effect: (action, listenerApi) => {
console.log(action, listenerApi);
},
}),
);
return unsubscribe;
}, []); Object slice import { createEntityAdapter, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { ObjectElement } from '../../types/ObjectElement';
export interface ObjectState {
variables: ObjectElement[];
linkConnected: boolean;
connected: boolean;
}
const initialState: ObjectState = {
objects: [],
linkConnected: false, // Service connected
connected: false, // service active
};
const objectsAdapter = createEntityAdapter<ObjectElement>({
selectId: obj => obj.id,
sortComparer: (a, b) => a.path.localeCompare(b.path),
});
export const objectSlice = createSlice({
name: 'object',
initialState: objectsAdapter.getInitialState({
linkConnected: false,
connected: false,
}),
reducers: {
setObjects: (state, action: PayloadAction<ObjectElement[]>) => {
objectsAdapter.setAll(state, action.payload);
},
updateObjects: (state, action: PayloadAction<ObjectElement[]>) => {
const objs = variablesAdapter.getSelectors().selectAll(state);
const updatedObjects: ObjectElement[] = [];
for (let i = 0; i < action.payload.length; i++) {
const currentObjectIndex = objects.findIndex(
obj => obj.id === action.payload[i].id,
);
if (currentObjectIndex !== -1) {
updatedObjects.push({
...variables[currentObjectIndex],
value: action.payload[i].value,
updatedAt: action.payload[i].updatedDate,
});
}
}
objectsAdapter.upsertMany(state, updatedObjects);
},
updateLinkConnected: (state, action: PayloadAction<boolean>) => {
state.linkConnected = action.payload;
},
updateConnected: (state, action: PayloadAction<boolean>) => {
state.connected = action.payload;
},
},
});
export const {
setObjects,
updateObjects,
updateLinkConnected,
updateConnected,
} = objectSlice.actions;
export default objectSlice.reducer; Store file import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit';
import objectReducer from './slices/object.slice';
const listenerMiddleware = createListenerMiddleware();
const store = configureStore({
reducer: {
obj: objectReducer,
},
middleware: getDefaultMiddleware => getDefaultMiddleware().prepend(listenerMiddleware.middleware),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store; |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 4 replies
-
Can you show the component file so I can see how it's using this data for display? What does the effect need to do, specifically? When exactly does it need to run? Is the idea that this effect runs only when one specific object is updated (ie, the one object that this specific instance of the component is displaying / cares about)? |
Beta Was this translation helpful? Give feedback.
-
The final goal is to update component only when a value of the reducer changes and is required in the component to display. The required variables from reducer are determined here, after searching them on a string from the database. Here is the real currently developed component (simplified) : import { addListener } from '@reduxjs/toolkit';
import { KonvaEventObject } from 'konva/lib/Node';
import { useEffect, useMemo, useState } from 'react';
import { IntlShape } from 'react-intl';
import { Label, Tag, Text } from 'react-konva';
import { RGBAToString } from '../../../../../../helpers/stringToRGBA';
import { useAppDispatch } from '../../../../../../hooks/redux';
import XMLVariableObserverInstance from '../../../../../../Observers/XMLVariableObserver';
import { updateOpcuaVariables } from '../../../../../../redux/slices/opcua.slice';
import { LabelComponentInterface } from '../../../../../../types/Components/LabelComponentInterface';
import { XMLVar } from '../../../../../../types/XMLVar';
import {
DashboardMapLabelAction,
DASHBOARD_MAP_LABEL_ACTION_TYPES,
} from '../../MapLabelComponentReducer';
type Props = {
label: LabelComponentInterface;
intl: IntlShape;
draggable: boolean;
dispatch: (action: DashboardMapLabelAction) => void;
isDeleteMode: boolean;
};
export const LabelElement = (props: Props) => {
const reduxDispatcher = useAppDispatch();
const [splittedValues, setSplittedValues] = useState<string[]>(props.label.text.split('@'));
const [splittedValuesToDisplay, setSplittedValuesToDisplay] = useState<string[]>([]);
const [position, setPosition] = useState({
x: props.label.geoJSON[0],
y: props.label.geoJSON[1],
});
useEffect(() => {
if (!props.draggable) {
setPosition({
x: props.label.geoJSON[0],
y: props.label.geoJSON[1],
});
}
}, [props.draggable, props.label.geoJSON]);
useEffect(() => {
const variablesPath: string[] = [];
const splittedValuesToDisplay = [...splittedValues];
for (let i = 0; i < splittedValues.length; i++) {
if (i % 2 === 0 && splittedValuesToDisplay[i] !== '')
splittedValuesToDisplay[i] = props.intl.formatMessage({
id: splittedValuesToDisplay[i],
});
}
setSplittedValuesToDisplay(splittedValuesToDisplay);
for (let i = 0; i < splittedValues.length; i++) {
variablesPath.push(splittedValues[i]);
XMLVariableObserverInstance.attach(handleObserverEvent, splittedValues[i]);
}
return function cleanUp() {
for (let v in variablesPath) {
XMLVariableObserverInstance.detach(handleObserverEvent, variablesPath[v]);
}
};
}, [props.label.text]);
useEffect(() => {
const unsubscribe = reduxDispatcher(
addListener({
actionCreator: updateOpcuaVariables,
effect: (action, listenerApi) => {
console.log(action, listenerApi);
},
}),
);
return unsubscribe;
}, []);
const fontStyle = useMemo(() => {
if (props.label.options.isItalic && props.label.options.isBold) {
return 'italic bold';
} else if (props.label.options.isItalic) {
return 'italic';
} else if (props.label.options.isBold) {
return 'bold';
}
}, [props.label.options.isItalic, props.label.options.isBold]);
return (
<Label
key={`label-${props.label.id}`}
x={position.x}
y={position.y}
draggable={props.draggable}
clearBeforeDraw
>
<Tag fill={RGBAToString(props.label.options.backgroundColor)} />
<Text
text={splittedValuesToDisplay.join('')}
textDecoration={props.label.options.isUnderline ? 'underline' : ''}
fontSize={props.label.options.fontSize}
fontStyle={fontStyle}
fill={RGBAToString(props.label.options.color)}
padding={5}
/>
</Label>
);
}; |
Beta Was this translation helpful? Give feedback.
-
Solved... I hadn't seen the documentation part of useSelector, speciffically the part of selecting element by his ID. I tried a lot of things and I think I completely lost myself for nothing... |
Beta Was this translation helpful? Give feedback.
Solved...
I hadn't seen the documentation part of useSelector, speciffically the part of selecting element by his ID. I tried a lot of things and I think I completely lost myself for nothing...
Anyway, thank you so much guys for opening my eyes to something I had in front of me.