Print out Function (Debug Redux State and Actions without Redux Dev Tools) - javascript

I am doing some research into how Slack uses Redux. I am running custom Javascript on the page using the Chrome extension CJS.
I log the action and state changes. When the action is a function I can't log the function correctly.
Here is an excerpt from the console log:
...
[AP] store dispatch called: function d(e,n){return t(e,n,r)}
[AP] teamStore dispatch called: {"type":"[21] Navigate to a route","payload":{"routeName":"ROUTE_ENTITY","params":{"teamId":"TS6QSK7PA","entityId":"DU52E70NB"}},"error":false}
...
The code where I print the function is:
console.log("[AP] store dispatch called: " + (JSON.stringify(action) || action.toString()));
Here is full code code:
const teamStates = [];
const states = [];
let base;
let teamStore;
let store;
function subscribeToStores() {
const reactRoot = document.getElementsByClassName('p-client_container')[0];
try {
base = reactRoot._reactRootContainer._internalRoot.current
} catch (e) {
console.log('[AP] Could not find React Root');
}
if (!base) {
setTimeout(() => {
subscribeToStores();
}, 1);
} else {
console.log('[AP] Found React Root');
while (!store) {
try {
store = base.pendingProps.store;
} catch (e) {}
base = base.child
}
console.log('[AP] Found store');
console.log(JSON.stringify(store.getState()));
states.push(store.getState());
while (!teamStore) {
try {
teamStore = base.pendingProps.teamStore;
} catch (e) {}
base = base.child
}
console.log('[AP] Found teamStore');
console.log(JSON.stringify(teamStore.getState()));
teamStates.push(teamStore.getState());
var unsubscribe1 = teamStore.subscribe(() => {
teamStates.push(teamStore.getState());
console.log("[AP] teamStates length:" + teamStates.length);
console.log(JSON.stringify(teamStore.getState()));
})
var rawDispatchTeamStore = teamStore.dispatch;
teamStore.dispatch = (action) => {
console.log("[AP] teamStore dispatch called: " + (JSON.stringify(action) || action.toString()));
rawDispatchTeamStore(action);
}
var unsubscribe2 = store.subscribe(() => {
states.push(store.getState());
console.log("[AP] states length:" + states.length);
console.log(JSON.stringify(store.getState()));
})
var rawDispatchStore = store.dispatch;
store.dispatch = (action) => {
console.log("[AP] store dispatch called: " + (JSON.stringify(action) || action.toString()));
rawDispatchStore(action);
}
}
}
subscribeToStores();

I'm not sure how better you could log that function. Since the code is most likely minified, you won't be able to get its original name, except maybe through sourcemaps. What more information did you want?
That said, it's pretty strange to dispatch a function to begin with. I would be more curious how the reducer for store is handling functions since, normally, actions are supposed to be objects with a type property.

Related

React app using empty state inside onMessage from WEbsocket

I've created a application for my company to manage diffrent things.
I first started realizing it with Class-Components, now switched to Functional components.
The issue that i'm facing is, that in one function inside Websocket onMessage uses old/empty state.
This is how the application currently is built.
const DocumentControl = ({ createWebsocket, logOut }) => {
const [elementList, setElementList] = useState([]);
const webSocket = useRef(null);
useEffect(() => {
webSocket.current = createWebsocket();
webSocket.current.onmessage = (messageEvent) => {
console.log(elementList);
if (devComm) {
console.log(JSON.parse(messageEvent.data));
}
processMessage(JSON.parse(messageEvent.data));
};
window.addEventListener("beforeunload", () => {
logOut(true);
});
}, []);
useEffect(() => {
loadElements();
}, [menu]);
const processMessage = (message) => {
var newArr, index;
switch (message.type) {
case "document":
var elementItem = message.data;
newArr = [...elementList]; // here elementList is always []
...
break;
default:
if (devComm) {
console.log("---Unknown WSMessage!---");
}
}
};
}
There is obviously more logic but this may be sufficient to describe my problem.
Basically i'm loading from the Server my ElementList if the Menu changes.
Whenever an Element on the Server-Side is updated, I send a message through Websockets to all clients, so all are up-to-date.
---My Issue---
Loading the Elements from Server and using setElementList works fine. But whenever I receive a message through websockets, the state elementList is always empty.
I made a button which displays the current elementList and there the List is correctly.
For example: I have 4 Elements in my list. Because of Message in Websockets i want to add to my current list the new Element. But the elementList is [] inside processMessage, so I add the new element to an [] and after setElementList all other elements are gone (because of empty array previously)
My first thought was, that (because I get the Websocket element from parent) maybe it uses diffrent instance of elementList, which is initialized as empty [].
But it would not make sense, because setElementList still affects the "original" elementList.
I also tried using elementList with useRef(elementList) and accessing it with ref.current but still didn't change the outcome.
here is how the createWebsocket is implemented:
const createWebsocket = () => {
if (!webSocket.current) {
var uri = baseURI.replace("http://", "ws://");
console.log("Create new Websocket");
console.log("Websocket: " + uri + "websocket/");
let socket = new WebSocket(uri + "websocket/" + user.Token);
socket.onopen = () => {};
socket.onclose = (closeEvent) => {
console.log("closed socket:");
if (closeEvent.code !== 3001) {
logOut(false);
if (closeEvent !== null && closeEvent.reason.length > 0) {
alert(closeEvent.reason);
}
}
};
socket.onerror = () => {
logOut(true, false);
alert("Something went wrong");
};
webSocket.current = socket;
}
return webSocket.current;
};
Any Ideas why elementList is diffrent inside ProcessMessage then in other functions of the Component?
--------- UPDATE -------
I could temporary fix it, by using this workaround:
const elementsRef = useRef([]);
useEffect(() => {
elementsRef.current = elementList;
}, [elementList]);
and then accessing elementsRef.current
But there must be an more elegant soltuion to this

Async function overwrites array state when it should update only one item

I have a file upload component. The behavior is simple: I send one upload request to the back-end per file and as the upload progress increase, I have a bar that should increase with it.
I have a state that holds every selected file and their respective progress, as such:
interface IFiles {
file: File;
currentProgress: number;
}
const [selectedFiles, setSelectedFiles] = useState<IFiles[]>([]);
And when the user clicks the upload button, this function will be triggered and call uploadFile for each file in my array state.
const sendFilesHandler = async () => {
selectedFiles.map(async (file) => {
const fileType = file.file.type.split('/')[0];
const formData = new FormData();
formData.append(fileType, file.file);
formData.append('filename', file.file.name);
await uploadFile(formData, updateFileUploadProgress);
});
};
Here is what the uploadFile function looks like.
const uploadFile = async (body: FormData, setPercentage: (filename: string, progress: number) => void) => {
try {
const options = {
onUploadProgress: (progressEvent: ProgressEvent) => {
const { loaded, total } = progressEvent;
const percent = Math.floor((loaded * 100) / total);
const fileName = body.get('filename')!.toString();
if (percent <= 100) {
setPercentage(fileName, percent)
}
}
};
await axios.post(
"https://nestjs-upload.herokuapp.com/",
body,
options
);
} catch (error) {
console.log(error);
}
};
As you can see, when uploadProgress is triggered it should inform call setPercentage function, which is:
const updateFileUploadProgress = (fileName: string, progress: number) => {
console.log('Entrada', selectedFiles);
const currentObjectIndex = selectedFiles.findIndex((x) => fileName === x.file.name);
const newState = [...selectedFiles];
newState[currentObjectIndex] = {
...newState[currentObjectIndex],
currentProgress: progress,
};
setSelectedFiles(newState);
console.log('SaĆ­da', newState);
};
And this function should only update the object of my state array where the filenames match. However, it is overriding the whole thing. The behavior is as follows:
So it seems that everything is fine as long as I am updating the same object. But in the moment onUploadProgress is triggered to another object, selectedFiles states becomes its initial state again. What am I missing to make this work properly?
I am not sure what is the exact reason behind this behaviour but I came up with the below unexpectedly simple solution after spending 3 hours straight on it.
const updateFileUploadProgress = (fileName: string, progress: number) => {
setSelectedFiles(prevState => {
const currentObjectIndex = prevState.findIndex((x) => fileName === x.file.name);
const newState = [...prevState];
newState[currentObjectIndex] = {
...newState[currentObjectIndex],
currentProgress: progress,
};
return newState;
})
};
I was able to mimic your problem locally, and solved it with the above approach. I think it was because the function (for some reason) still referenced the initial state even after rerenders.

How to use AzureLogger in Azure Communication Services

The example code snippet # https://learn.microsoft.com/en-us/azure/communication-services/concepts/troubleshooting-info?tabs=csharp%2Cjavascript%2Cdotnet is incomplete. I'm not familiar enough with JavaScript modules to get this working. I've added the module to package.json ("#azure/logger": "1.0.1"). When I execute my code example, I receive the following exception:
Uncaught TypeError: this._azureLogger.info is not a function
documentation example:
import { AzureLogger } from '#azure/logger';
AzureLogger.verbose = (...args) => { console.info(...args); }
AzureLogger.info = (...args) => { console.info(...args); }
AzureLogger.warning = (...args) => { console.info(...args); }
AzureLogger.error = (...args) => { console.info(...args); }
callClient = new CallClient({logger: AzureLogger});
my failed attempt:
import { AzureLogger } from '#azure/logger';
const logger = require('#azure/logger');
logger.setLogLevel('verbose');
const callClientOptions: CallClientOptions = { logger };
Can anyone share a working example of this code?
Scott, thank you for raising this up. The product group is working on updating the document you linked for a long term fix. In regards to a short term fix to get you unblocked, can you please try the below sample?
We apologize for the inconvenience that this caused and look forward to your verification of the below sample.
import { createClientLogger, setLogLevel } from '#azure/logger';
const logger = createClientLogger('ACS');
setLogLevel('verbose');
logger.verbose.log = (...args) => { console.log(...args); };
logger.info.log = (...args) => { console.info(...args) ; };
logger.warning.log = (...args) => { console.warn(...args); };
logger.error.log = (...args) => { console.error(...args); };
const options = { logger: logger };
this.callClient = new CallClient(options);

Node and React are not in sync

I was able to achieve the following -> When a user clicks on a particular date in component A the data gets sent to the Node (Sails API) where all the necessary calculations are done, and before component B is rendered the correct data is ready to be shown.
The problem is when a user returns back from component B to component A and chooses a different date, he/ she gets the exact same result (old value) because even though the new value is sent to the backend API, Node isn't doing the recalculations with the new value.
I'm only able to achieve the correct result after I manually refresh the page, or make changes to the server so it forces the recalculation.
I think I need to mention that I'm passing data using Redux, so maybe the issue occurs on that part.
I would consider some type of auto refresh, animated loading, anything.
Yup, so stuck :/
Is it even possible to make them in total sync?
UPDATE --> Here is the code:
BACKEND
getDetails: (req, res) => {
authentication.authenticate().then((auth) => {
const sheets = google.sheets('v4');
sheets.spreadsheets.values.get({
auth: auth,
spreadsheetId: config.spreadsheetSettings.spreadsheetId, // id of spreadsheet
range: config.spreadsheetSettings.employeeSheetId, // name of employee spreadsheet and range- get all cells
}, (err, response) => {
if (err) {
res.serverError(err);
return;
}
const rows = response.values; // response-all cells
const updatedData = employeeService.mapEmployeeSheetToJson(rows);
// FETCHING THE VALUE FROM REST API
let myArr = [];
(function() {
axios.get(`http://localhost:1337/api/`)
.then(res => {
let kajmak = res.data.slice(-1)[0]
let test = kajmak[Object.keys(kajmak)[0]]
myArr.push(test)
}).catch(err => console.error(err));
})();
// MAPING OVER THE ARRY AND DOING THE LOGIC
setTimeout(() => {
myArr.map(xo => {
const result = [];
updatedData.forEach(emp => {// 2013 2012 2014
if (xo > parseInt(moment(emp.startdate).format('YYYYMM'), 10) &&
(xo < parseInt(moment(emp.enddate).format('YYYYMM'), 10))) {
result.push(emp);
}
});
// IF THEY STARTED WORKING BEFORE THE SELECTED DATE AND STILL WORKING
updatedData.forEach(emp => { // 2013 > 2012 & 2013 -
if (xo > parseInt(moment(emp.startdate).format('YYYYMM'), 10) &&
((parseInt(moment(emp.enddate).format('YYYYMM'), 10) == undefined ))) {
result.push(emp);
}
});
// IF THEY STARTED WORKIG BEFORE THE SELECTED DATE,
// BUT STOPPED WORKING BEFORE THE SELECTED DATE
updatedData.forEach(emp => { // 2013 < 2014 || 2013 > 2017
if (xo < parseInt(moment(emp.startdate).format('YYYYMM'), 10) &&
(xo > parseInt(moment(emp.startdate).format('YYYYMM'), 10))) {
result.pop(emp);
}
});
// Getting the names to use for unique sheet req
let finalResult = [];
result.map(x => {
finalResult.push((x.name + ' ' + x.surname))
})
if (rows.length === 0) {
res.err('No data found.');
} else {
res.ok(finalResult);
}
})
}, 1000);
});
}
FRONTEND
getEmployeeSalaryData = () => {
// GETTING THE CLICKED VALUE FROM THE PREVIOUS COMPONENT
const { year } = this.props.history.location.state.item;
const { month } = this.props.history.location.state.item;
const selectedMonth = moment().month(month).format("MM");
const finalSelect = parseInt(year + selectedMonth, 10);
const { employees } = this.props;
// I'M RECIEVING THIS AS PROPS USING REDUX AND THIS IS THE ACTUAL 'FINAL' DATA USED FOR FURTHER CALCS AND RENDERING
const { details } = this.props;
// HERE I'M SENDING THE 'CLICKED' VALUE FROM THE PREVIOUS COMPONENT TO THE BACKEND API
axios.post(`http://localhost:1337/api/`, { 'test' : finalSelect })
.then(res => {
console.log('Data send')
// console.log(res.data);
}).catch(err => console.error(err));
// Making the req
details.map(x => {
EmployeeApi.getEmployee(x)
.then(y => {
//Making sure everything is in the right order
let test = Object.assign(y.data);
let ii = x;
setTimeout(
this.setState(prevState => ({
...prevState.currentEmployee,
fullNames: [...prevState.currentEmployee.fullNames, ii]
})), 100);
let onlyRelevantDate = [];
test.map(item => {
if (finalSelect == parseInt(item.year + moment().month(item.month).format("MM"), 10)) {
onlyRelevantDate.push(item)
}})
this.setState(prevState => ({
currentEmployee: {
...prevState.currentEmployee,
salaryInfo: [...prevState.currentEmployee.salaryInfo, onlyRelevantDate],
fullNames: [...prevState.currentEmployee.fullNames, ii]
}}))
})
});
}
componentWillReceiveProps(nextProps) {
this.getEmployeeSalaryData(nextProps);
}
componentWillMount() {
this.getEmployeeSalaryData(this.props);
}
In component A you should dispatch an action that is a function taking a dispatch function.
//some click handler for when user makes a selection
// the function should be in action creator file but you get the jist
const handleSomeClick = someValue =>
//when you dispatch an action that is a function in redux with thunk then
// the thunk middleware will not call next (no reducers will be called)
// thunk will pass a parameter to this function that is the dispatch
// function so from your function you can dispatch actual object action(s)
dispatch(
dispatch=>
setTimeout(
dispatch({type:"changedValue",data:someValue}),//dispatching the action
someValue*1000//assuming someValue is a number
)
)
Here is an example that has component A set someValue depending on what button is clicked and will highlight that button it'll also set someValue of B asynchronously. This is done in the function changeLater that dispatches an action that is a function so thunk will execute it with the dispatch.
This function will dispatch an action after a timeout. If you click the numbers 5 and then 1 (quickly) you'll see that the highlighted button of A and value after async of B do not match (highlighted of A is 1 and value after async of B is showing 5).
This is because the order of which the user clicks and starts the async process is not the same as the order the async process resolves. You could solve this by only dispatching an action when it's the last resolved promise.
This example shows how it's done by using a promise created by later and only resolve it if it's the last by using a partially applied version of onlyLastRequestedPromise called lastNumberClicked
you can use RxJS to solve this

Testing with Jest - reset/clear variables set by tested function and catching console logs

I'm learning writing unit test with Jest.
I use typescript, but it shouldn't be a problem here. Feel free to provide examples with pure JavaScript.
Until now I have function:
const space = String.fromCharCode(0x0020);
const rocket = String.fromCharCode(0xD83D, 0xDE80);
let notified: boolean = false;
export const logHiring = (message: string = "We're hiring!", emoji: string = rocket) => {
if (!notified) {
console.info(
[message, emoji]
.filter((e) => e)
.join(space)
);
notified = true;
}
};
Yes, function should log to console just one message per initialization.
And not really working tests:
import {logHiring} from "../index";
const rocket = String.fromCharCode(0xD83D, 0xDE80);
// First test
test("`logHiring` without arguments", () => {
let result = logHiring();
expect(result).toBe(`We're hiring! ${rocket}`);
});
// Second test
test("`logHiring` with custom message", () => {
let result = logHiring("We are looking for employees");
expect(result).toBe(`We are looking for employees ${rocket}`);
});
// Third test
test("`logHiring` multiple times without arguments", () => {
let result = logHiring();
result = logHiring();
result = logHiring();
expect(result).toBe(`We're hiring! ${rocket}`);
});
I have two problems:
How can I test console logs? I've tried spyOn without succes.
How can I reset internal (from function) notified variable for each test?
How can I test console logs? I've tried spyOn without succes.
https://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname
const spy = jest.spyOn(console, 'log')
logHiring();
expect(spy).toHaveBeenCalledWith("We're hiring!")
How can I reset internal (from function) notified variable for each test?
export a getter/setter function like
// index.js
export const setNotified = (val) => { notified = val }
export const getNotified = _ => notified
// index.test.js
import { getNotified, setNotified } from '..'
let origNotified = getNotified()
beforeAll(_ => {
setNotified(/* some fake value here */)
...
}
afterAll(_ => {
setNotified(origNotified)
...
}

Categories

Resources