Apply a throttled middleware in redux - javascript

I am creating a small middleware for redux. Since my electron application connects to another database and updates it's state (so that I may see the state of this application on my main application). Here is my basic code:
const BLACKLIST = ['redux-form/FOCUS ', 'redux-form/BLUR'];
export remoteMiddleware => store => next => action => {
const db = MongoClient.connect(process.env.MONGO_URL);
try {
if (!BLACKLIST.includes(action.type)) {
const state = store.getState();
db.collection('state').update(
{ _id: process.env.APP_ID },
{ $set: { state } }
)
}
} finally {
db.close();
}
}
However, I am having a bit of a problem where this is firing TOO often, when it doesn't always need to fire immediately. I would prefer if it fired every n iterations, or perhaps no more than x ms.
Is there a way to throttle a redux middleware so that it will only fire every n times, or such that it will only run every x number of ms?

How about simply
const BLACKLIST = ['redux-form/FOCUS ', 'redux-form/BLUR'];
var lastActionTime = 0;
export remoteMiddleware => store => next => action => {
let now = Date.now();
try {
if (!BLACKLIST.includes(action.type) && now - lastActionTime > 100)
...
} finally() {
lastActionTime = now;
db.close();
}
}

Related

How can i prevent useEffect() from firing up the first time but listening for a state change?

I'm using react, node express, postgres
I have a react component that is an html table that gets populated from a postgres table.
Here is parent component Materials:
const Materials = () => {
const [thickness1, setThickness] = useState(0);
const [width1, setWidth] = useState(0);
const [length1, setLength] = useState(0);
const [partTotalDemand, setTotalDemand] = useState(0);
const [partPlanned, setPlanned] = useState(0);
...
Here is a method in the component that retrieves data
// Material requirements calculation
const getReq = async (id) => {
try {
const response = await fetch(`http://localhost:5000/materials/${id}`, [id])
const jsonData = await response.json();
const tempThickness = jsonData.parts_material_thickness
const tempWidth = jsonData.parts_material_width
const tempLength = jsonData.parts_material_length
const tempTotalDemand = jsonData.workorder_total
const tempPlanned = jsonData.parts_produced
stateSetter(tempThickness, tempWidth, tempLength)
} catch (err) {
console.log(err.message);
}
}
I then want to update the states of the global constants:
const stateSetter = (thickness, width, length) => {
try {
setThickness(thickness);
setWidth(width);
setLength(length);
console.log(thickness1);
console.log(width1);
console.log(length1);
} catch (err) {
console.log(err.message)
}
}
useEffect(() => {
stateSetter();
}, [thickness1]);
Essentially the getReq() method is supposed to retrieve the information, and then I need to update the states with those values. As I understand I then need to re-render the component so the new states are usable. I attempted to do this via useEffect() but I'm not successful. The idea was to stop getReq() from firing up on the first render, but if the state changes for thickness1/width1/length1 then it should fire up and re-render, help much appreciated!
You're over-complicating this. All you need to do is set the state values:
const getReq = async (id) => {
try {
const response = await fetch(`http://localhost:5000/materials/${id}`, [id])
const jsonData = await response.json();
// set state values
setThickness(jsonData.parts_material_thickness);
setWidth(jsonData.parts_material_width);
setLength(jsonData.parts_material_length);
setTotalDemand(jsonData.workorder_total);
setPlanned(jsonData.parts_produced);
} catch (err) {
console.log(err.message);
}
}
You don't need to manually do anything to re-render the component. It will re-render whenever state is updated. So the "setter" functions being invoked here will trigger that re-render. (All of the state updates will be batched. So the above won't trigger 5 re-renders, just one with the 5 updated state values.)
Where you would use useEffect is when you want to have some logic which responds to a change in a particular state. For example, if you want to show a message every time thickness changes to a negative value, you'd do something like:
useEffect(() => {
if (thickness < 1) {
alert('negative thickness!');
}
}, [thickness]);
But that's not what you're doing here. All you're doing here is setting state values.

Unnecessary parameter in useEffect dependency array

I'm creating an application where users can create and share notes.
To share each other's notes users have to send requests to specific users.
The requests are fetched whenever home is loaded.
However, requests is a context since it is also consumed in the toolbar and requests page to show the presence of the requests
When I'm using setRequsts method of the context to set all the requests after home loads, the fetch goes into an infinite loop of /noteand /me URLs, since the setRequests method is also provided in the dependency array of useEffect
When removed, useEffect show missing dependencies. What's the work around?
const {setRequests } = useContext(RequestsContext)
const [notes, setNotes] = useState([])
const [fetched, setFetched] = useState('')
const { isAuthenticated } = props
const {page}=useContext(PageContext)
const [sortBy,setSortBy]=useState('latest')
useEffect(() => {
const fetch = async () => {
try {
let url = 'http://192.168.56.1:5000/api/v1/note', p, sort
if (page) p = `?page=${page}&limit=12`
if (sortBy === 'latest') {
sort=''
} else if (sortBy === 'most_liked') {
sort='&sort=likes'
}
const res = await Axios.get(url+p+sort)
setNotes(res.data.data)
if (res.data.data.length > 0) {
setFetched('Y')
} else {
setFetched('N')
}
} catch (err) {
console.log(err)
} finally {
if (isAuthenticated) {
const fetch = async () => {
const res = await axios.get(`user/me`)
if (res.data.data.createdPosts.length > 0) {
const arr = res.data.data.createdPosts.map(el => el.request)
console.log(arr)
setRequests(arr)
}
}
fetch()
}
}
}
fetch()
}, [isAuthenticated, /* setRequests, */ page, sortBy])
The problem is that the context provides a technically different setRequests function on each render (that have a different address). This causes useEffect to fire on each render.
To work around this, you could wrap setRequests in a useCallback() hook, like so:
// ...
const wrappedSetRequests = useCallback(setRequests, []);
// ...
useEffect(() => {
// do your stuff using 'wrappedSetRequests' instead of setRequests.
}, [ wrappedSetRequests /*...*/ ]);

Warning about unmounted component

I have an app that read values from an external devices, then these values are written in a DB.
When I wrote this data in the db I receive this error:
Can't perform a React state update on an unmounted component. This is
a no-op, but it indicates a memory leak in your application. To fix,
cancel all subscriptions and asynchronous tasks in the
componentWillUnmount method.
And It is about this code:
this.setState(state => ({
acc_dx,
array_acc_dx: [...state.array_acc_dx, [timeagm, ...acc_dx].join(":")]
}));
this code is a part of a function setupNotifications1 which in turn is called in a function called in the componentDidMount ().
How can I solve this warning? Thank you.
EDIT 1:
componentDidMount() {
this.deviceService1(this.device1);
}
Inside deviceService1() I call this.deviceService2(this.device2) and inside I call setupNotifications1(this.device1) and setupNotifications2(this.device2)
EDIT 2:
async setupNotifications1(device) {
var timeagm = 0;
var time = 0;
const service = this.serviceGeneral();
this.subscriptionMonitor1 = await device.monitorCharacteristicForService(
service,
this.AccGyrMg,
(error, characteristic) => {
if (error) {
this.error(error.message);
return;
}
const buf = Buffer.from(characteristic.value, "base64");
const [...acc_dx] = [2, 4, 6].map(index => buf.readInt16LE(index));
this.setState(state => ({
acc_dx,
array_acc_dx: [...state.array_acc_dx, [timeagm, ...acc_dx].join(":")]
}));
timeagm += 20;
then I have a stop button that is used to remove this.subscriptionMonitor1 and change page ( I pass the value in the other page where there are written in the DB )

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

How to handle multiple interdependent sagas when rendering server-side?

I am implementing server-side rendering using redux-saga.
I am following the "real world" example provided in the redux-saga repository.
node.js entry-point uses react.js renderToString to render the application.
rendering the application triggers componentWillMount, which dispatches actions GET_GEOLOCATION and GET_DATE. These async actions will resolve with SET_GEOLOCATION and SET_DATE.
renderToString finishes rendering the application; END action terminates the saga listeners
The problem is that SET_GEOLOCATION and SET_DATE themselves are used to put a new action GET_MOVIES. However, by the time the SET_GEOLOCATION and SET_DATE are called, the saga listeners are no longer active (we terminated it after renderToString). Therefore, while GET_MOVIES will be dispatched, the GET_MOVIES action will not be picked and SET_MOVIE will never happen.
Server code:
app.get('*', (req, res) => {
const history = createMemoryHistory({
initialEntries: [
req.url
]
});
const store = configureStore(undefined, history);
const context = {};
const rootComponent = <Provider store={store}>
<StaticRouter context={context} location={req.url}>
<Route component={RootRoute} />
</StaticRouter>
</Provider>;
store
.runSaga(rootSaga).done
.then(() => {
const body = renderToString(rootComponent);
const response = renderHtml(body, store);
res
.send(response);
})
.catch((error) => {
res
.status(500)
.send(error.message);
});
// Force componentWillMount to issue saga effects.
renderToString(rootComponent);
store.close();
});
Sagas:
const watchNewSearchCriteria = function *(): Generator<*, *, *> {
yield takeLatest([
SET_GEOLOCATION,
SET_DATE
], function *() {
const {
coordinates,
date
} = yield select((state) => {
return {
coordinates: state.movieEventsView.location ? state.movieEventsView.location.coordinates : null,
date: state.movieEventsView.date
};
});
if (!coordinates || !date) {
return;
}
yield put(getMovies({
coordinates,
date
}));
});
};
const watchGetMovies = function *() {
yield takeLatest(GET_MOVIES, function *(action) {
const result = yield call(getMovies, action.payload);
yield put(setMovies(result));
});
};
How to delay store.close until after there are no sagas that are in the state other than take?
How to delay store.close until after there are no sagas that are in the state other than take?
To answer my own question, I need to observe resolution of anything thats been put. I can do this using the Saga Monitor.
Saga Monitor can be configured at the time of creating the redux-saga middleware. For our use case, it needs to track whenever an action has been put and remove it from the index when it has been resolved/ rejected/ cancelled.
const activeEffectIds = [];
const watchEffectEnd = (effectId) => {
const effectIndex = activeEffectIds.indexOf(effectId);
if (effectIndex !== -1) {
activeEffectIds.splice(effectIndex, 1);
}
};
const sagaMiddleware = createSagaMiddleware({
sagaMonitor: {
effectCancelled: watchEffectEnd,
effectRejected: watchEffectEnd,
effectResolved: watchEffectEnd,
effectTriggered: (event) => {
if (event.effect.CALL) {
activeEffectIds.push(event.effectId);
}
}
}
});
We need to access this from the consumer of the store, therefore I assign activeEffectIds to the store instance:
store.runSaga = sagaMiddleware.run;
store.close = () => {
store.dispatch(END);
};
store.activeEffectIds = activeEffectIds;
Then instead of synchronously stopping the saga...
renderToString(rootComponent);
store.close();
we need to delay store.close until store.activeEffectIds.length is 0.
const realDone = () => {
setImmediate(() => {
if (store.activeEffectIds.length) {
realDone();
} else {
store.close();
}
});
};
// Force componentWillMount to issue saga effects.
renderToString(rootComponent);
realDone();
Now store.close is called only when all the asynchronous effects are resolved.

Categories

Resources