state not updated redux - reactjs

so I made a notification system with react and redux and added a queue system for preventing to display to much notifications at the same time. It works how it should, but there is only one problem. If add 5 notifications the fifth one should go inside the queue array it does but the components do not relaize that the queue array has now a length of one. If i put again 5 notifications i have 4 on the active array and then 2 notifications inside the queue array, from here it knows that queue array length is now 2. Why is that? Is there a problem with the re-rendering?
My Code:
const notify = useSelector((state) => state.notify);
const handleClick = () => {
let notification = {
iconType: 0,
title: 'notification.title',
message: 'notification.message',
color: '03A65A',
width: 0,
};
showNotifications(notification);
};
const timeoutNotification = async (notification: any) => {
await dispatch(AddActiveItem(notification));
setTimeout(async () => {
await dispatch(RemoveActiveItem(notification.id));
if (notify.queue.length && notify.active.length < 4) {
let newQueue = notify.queue;
let pushItem = newQueue.shift();
await dispatch(SetNewQueue(newQueue));
timeoutNotification(pushItem);
}
}, 120 * notification.message.length);
};
const showNotifications = async (notification: any) => {
notification = {
id: notify.id,
iconType: notification.iconType,
title: notification.title,
message: notification.message,
color: notification.color,
width: notification.width,
};
let newId = notify.id + 1;
dispatch(SetId(newId));
if (notify.active.length > 3) {
await dispatch(AddQueueItem(notification));
} else {
timeoutNotification(notification);
}
};

Remember that Redux's dispatch is an async function, so the changes made to the Redux state are not visible immediately. If you're sure the state should/will update, simply await the changes..

Related

ReactJS: STOMP subscription to multiple topics

My React code creates a WebSocket connection to our company's ActiveMQ 5.15.5 server, and then subscribes to the following two topics: salary and decoding. The problem is that the code is only able to subscribe to one of the topics. It cannot subscribe to both.
const client = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj6.stomp');
const headers = { id: 'username' };
client.debug = null;
client.connect('user', 'pass', () => {
client.subscribe(
'/topic/salary', //BREAKPOINT was set here
message => {
const body = JSON.parse(message.body);
if (body && body.pcId) {
salaries[body.pcId] = body;
setState({ salaries});
}
},
headers,
);
client.subscribe(
'/topic/decoding', //BREAKPOINT was set here
message => {
const newBody = JSON.parse(message.body);
if (newBody && newBody.PcID) {
consoleMessage[newBody.PcID] = newBody;
setState({ consoleMessage });
}
},
headers,
);
});
So in the code above I put a break-point at client.subscribe('/topic/decoding... and client.subscribe('/topic/salary.... I saw that it only subscribes to /topic/decoding but not /topic/salary.
Does anyone know how I can fix this issue so that it subscribes to both topics?
From Stomp documentation:
Since a single connection can have multiple open subscriptions with a server, an id header MUST be included in the frame to uniquely identify the subscription. The id header allows the client and server to relate subsequent MESSAGE or UNSUBSCRIBE frames to the original subscription.
Within the same connection, different subscriptions MUST use different subscription identifiers.
Stomp API definition:
subscribe(destination, callback, headers = {})
So for my understanding, You can't have the same username id for both of your subscriptions
Try creating 2 clients, e.g.:
const salaryClient = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj6.stomp');
const salaryHeaders = { id: 'salary' };
salaryClient.debug = null;
salaryClient.connect('user', 'pass', () => {
salaryClient.subscribe(
'/topic/salary',
message => {
const body = JSON.parse(message.body);
if (body && body.pcId) {
salaries[body.pcId] = body;
setState({ salaries});
}
},
salaryHeaders,
);
});
const decodingClient = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj7.stomp');
const decodingHeaders = { id: 'decoding' };
decodingClient.debug = null;
decodingClient.connect('user', 'pass', () => {
decodingClient.subscribe(
'/topic/decoding',
message => {
const newBody = JSON.parse(message.body);
if (newBody && newBody.PcID) {
consoleMessage[newBody.PcID] = newBody;
setState({ consoleMessage });
}
},
decodingHeaders,
);
});

Show status of `For` loop to user

I have an action which will take quite some time to complete. It will be looping through an array with over 250k entries. With each entry it will be doing some logic. I want to inform the user of the actions progress, which would be:
let progress = (i / array.length) * 100
How can I add this to redux store or components state so that I can communicate the current progress in the UI?
I tried dispatching an action (as you can see below) which updates the stores progress with the current iterator but the reducer only executes when the for loop is created.
export const generateKeyMap = (mapFrom, mapTo) => {
return async (dispatch) => {
await dispatch(startGeneration()); <-- This only runs when I use a setTimeout for the following action.
setTimeout(() => {
dispatch(buildKeyMap(mapFrom, mapTo));
}, 100);
};
};
export const buildKeyMap = (mapFrom, mapTo) => {
return async (dispatch) => {
let keyMap = {};
for (let i = 0; i < mapFrom.length; i++) {
await dispatch(reportProgress(i)); // <-- This does not update in store until for loop is done.
let randomIndex = randomIntFromInterval(0, mapTo.length - 1);
let key = mapFrom[i].toString();
let value = mapTo[randomIndex].toString();
Object.assign(keyMap, { [key]: value });
mapTo.splice(randomIndex, 1);
}
await dispatch(stopGeneration());
return { type: actionTypes.DELIVERKEYMAP, payload: keyMap };
}
};

React Redux await until first action is complete

I have an action that will take quite some time to complete. I want to inform the users that the action is currently taking place. To do this I am dispatching an action that updates a reducers state with a boolean.
The issue I am running into is that the larger action starts before the the state is updated. As a result, when a users clicks the generate button, they then experience the system lagging appose to UI informing them to wait.
How can I ensure the first action startGeneration() is completed before buildKeyMap() starts? Same questioned for reportProgress(). I have redux-thunk installed.
export const generateKeyMap = (mapFrom, mapTo) => {
return async (dispatch) => {
dispatch(startGeneration()), // <-- this updates reducers state with a boolean, generatingMap: true
dispatch(buildKeyMap(mapFrom, mapTo)) // <-- this takes a long time to finish
};
};
export const buildKeyMap = (mapFrom, mapTo) => {
return async (dispatch) => {
let keyMap = {};
for (let i = 0; i < mapFrom.length; i++) {
dispatch(reportProgress(i)); // <-- would like this to finish before moving on in the loop
let randomIndex = randomIntFromInterval(0, mapTo.length - 1);
let key = mapFrom[i].toString();
let value = mapTo[randomIndex].toString();
Object.assign(keyMap, { [key]: value });
mapTo.splice(randomIndex, 1);
}
dispatch(stopGeneration()); // <--- Would like this to finish before delivering the map
return { type: actionTypes.DELIVERKEYMAP, payload: keyMap };
};
};
export const startGeneration = () => {
return { type: actionTypes.STARTKEYMAPGENERATION };
};

React State is not updated with socket.io

When page loaded first time, I need to get all information, that is why I am calling a fetch request and set results into State [singleCall function doing that work]
Along with that, I am connecting websocket using socket.io and listening to two events (odds_insert_one_two, odds_update_one_two), When I got notify event, I have to
check with previous state and modify some content and update the state without calling again fetch request. But that previous state is still [] (Initial).
How to get that updated state?
Snipptes
const [leagues, setLeagues] = useState([]);
const singleCall = (page = 1, params=null) => {
let path = `${apiPath.getLeaguesMatches}`;
Helper.getData({path, page, params, session}).then(response => {
if(response) {
setLeagues(response.results);
} else {
toast("Something went wrong, please try again");
}
}).catch(err => {
console.log(err);
})
};
const updateData = (record) => {
for(const data of record) {
var {matchId, pivotType, rateOver, rateUnder, rateEqual} = data;
const old_leagues = [...leagues]; // [] becuase of initial state value, that is not updated
const obj_index = old_leagues.findIndex(x => x.match_id == matchId);
if(obj_index > -1) {
old_leagues[obj_index] = { ...old_leagues[obj_index], pivotType, rateOver: rateOver, rateUnder:rateUnder, rateEqual:rateEqual};
setLeagues(old_leagues);
}
}
}
useEffect(() => {
singleCall();
var socket = io.connect('http://localhost:3001', {transports: ['websocket']});
socket.on('connect', () => {
console.log('socket connected:', socket.connected);
});
socket.on('odds_insert_one_two', function (record) {
updateData(record);
});
socket.on('odds_update_one_two', function (record) {
updateData(record);
});
socket.emit('get_odds_one_two');
socket.on('disconnect', function () {
console.log('socket disconnected, reconnecting...');
socket.emit('get_odds_one_two');
});
return () => {
console.log('websocket unmounting!!!!!');
socket.off();
socket.disconnect();
};
}, []);
The useEffect hook is created with an empty dependency array so that it only gets called once, at the initialization stage. Therefore, if league state is updated, its value will never be visible in the updateData() func.
What you can do is assign the league value to a ref, and create a new hook, which will be updated each time.
const leaguesRef = React.useRef(leagues);
React.useEffect(() => {
leaguesRef.current = leagues;
});
Update leagues to leaguesRef.current instead.
const updateData = (record) => {
for(const data of record) {
var {matchId, pivotType, rateOver, rateUnder, rateEqual} = data;
const old_leagues = [...leaguesRef.current]; // [] becuase of initial state value, that is not updated
const obj_index = old_leagues.findIndex(x => x.match_id == matchId);
if(obj_index > -1) {
old_leagues[obj_index] = { ...old_leagues[obj_index], pivotType, rateOver:
rateOver, rateUnder:rateUnder, rateEqual:rateEqual};
setLeagues(old_leagues);
}
}
}

update store on mobx react hook asynchronously

I am trying to fetch the data asynchronously from firestore to my local state using react hooks and mobx and got no clue how to update the store after getting data from firestore. I have used https://github.com/IjzerenHein/firestorter/blob/master/docs/API.md#Collection+query
My store
const store = useObservable({
forms: [],
async initForms(user_id) {
console.log(user_id);
my_forms.query = ref =>
ref
.where('userId', '==', user_id)
.limit(20);
let obj = {};
const arr = [];
my_forms.docs.forEach((doc, i) => {
obj = {
key: i + 1,
title: doc.data.title,
id: doc.id,
tags: doc.data.tags,
category: doc.data.category,
locked: doc.data.locked
};
arr.push(obj);
});
//i can see records from firestore
console.log('arr',arr);
return arr;
},
})
How i tried to update store
useEffect(() => autorun(() => {
store.forms=store.initForms(user_id);
}),[]);
store.forms && (
store.forms.length > 0 ? (
<FuseAnimateGroup
enter={{
animation: "transition.slideUpBigIn"
}}
className="flex flex-wrap py-24"
>
{store.forms.map((f) => {
Well i fixed it by using classic mobx store and using them as the context
These are the steps i did
Create the classic mobx store like we used to do in normal react project
export class MyFormsStore {
forms: []
async initForms(user_id) {
console.log(user_id);
my_forms.query = ref =>
ref
.where('userId', '==', user_id)
.limit(20);
await my_forms.fetch();
let obj = {};
const arr = [];
my_forms.docs.forEach((doc, i) => {
obj = {
key: i + 1,
title: doc.data.title,
id: doc.id,
tags: doc.data.tags,
category: doc.data.category,
locked: doc.data.locked
};
arr.push(obj);
});
this.forms = arr;
}
}
decorate(MyFormsStore, {
forms: observable
})
Export it as the context
export default createContext(new MyFormsStore());
Play with store as the functional component using useContext hook
const Forms = observer((props) => {
const store = useContext(MyFormsStore)
.
.
.
Fetch the data from firestore using useEffect hook
useEffect(() => {
store.initForms(user_id);
}, []);
Check if the store has been initialised or not , it yes render it
{
store.forms !== undefined && (
store.forms.length > 0 ? (
//render
I am still looking how can I memoize expensive functions so that I can avoid calling them on every render using useMemo . Feedbacks will be appreciated
You don't need the useEffect to update data that is tracked by Mobx.
The simplest way is to use runInAction from MobX.
So when your async function is finally resolved, you then use runInAction to update the store. And that will eventually trigger the rendering of react components that are using that particular data.
something like this:
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
// after await, modifying state again, needs an actions:
runInAction(() => {
this.state = "done"
this.githubProjects = filteredProjects
})
} catch (error) {
runInAction(() => {
this.state = "error"
})
}
}
You can read more in official documentation:
Writing asynchronous actions

Resources