Recommended to use Subject or not - javascript

I am using Subject in my code and wondering if it is the recommended to do it in this way.
First of all, for what I am using the Subject is, when the toggle button got pressed, then other buttons will be set as inactive.
After edit got pressed:
Subject definition and subscriber implementation code:
let oButtonSubject = new Rx.Subject();
this._subscribeButtonState(oButtonSubject);
_subscribeButtonState: function (oButtonSubject) {
let self = this;
return oButtonSubject
.subscribe(function (oBtnState) {
let oModel = self.getModel("vmButtonsState");
oModel.setProperty("/edit", oBtnState.bEdit);
oModel.setProperty("/mass", oBtnState.bMass);
oModel.setProperty("/save", oBtnState.bSave);
oModel.setProperty("/cancel", oBtnState.bCancel);
});
},
The code above will set the state of buttons.
Every time, when the edit got pressed, the next method got called and push data to oButtonSubject.
_subscribeEditPressOb: function (oEditPressed, oButtonSubject) {
let self = this;
return oEditPressed
.map(function (oSource) {
return oSource;
})
.subscribe(function (oSource) {
// Determine select state for the calendar.
// The calendar is only allowed to select, if
// EDIT button is clicked.
if (oSource.getId().indexOf("cal-edit") >= 0 && oSource.getPressed()) {
oButtonSubject.next({bEdit: true, bMass: false, bSave: false, bCancel: false});
} else {
oButtonSubject.next({bEdit: true, bMass: true, bSave: true, bCancel: true});
}
},
function (err) {
jQuery.sap.log.fatal(err);
});
},
The code above subscribe to the edit button when it got pressed, after push the data to the subject.
It is the right approach to use subject?

A Subject is meant as an entrance from the non-reactive world into the Rx world. In your case you are using a Subject to emit events based on click events on the edit button.
I would suggest you do not use a Subject for this specific implementation but use your clicks directly as a stream by using fromEvent
The following is an example (ignore the rough edges; it persists state in the DOM) (jsbin)
const editButton = document.getElementById('enable-edit-mode')
const saveChangesButton = document.getElementById('save-changes');
const cancelChangesButton = document.getElementById('cancel-save-changes');
const enableEditModeStream = Rx.Observable.fromEvent(editButton, 'click')
.do(() => {
editButton.setAttribute('disabled','disabled')
saveChangesButton.removeAttribute('disabled');
cancelChangesButton.removeAttribute('disabled');
});
const saveChangesStream = Rx.Observable.fromEvent(saveChangesButton, 'click')
.do(() => {
console.log('saving changes')
});
const cancelChangesStream = Rx.Observable.fromEvent(cancelChangesButton, 'click');
const saveCancelEditModeStream = Rx.Observable.merge(
saveChangesStream,
cancelChangesStream
)
.do(() => {
editButton.removeAttribute('disabled');
saveChangesButton.setAttribute('disabled','disabled')
cancelChangesButton.setAttribute('disabled','disabled')
});
Rx.Observable.merge(
enableEditModeStream,
saveCancelEditModeStream
)
.subscribe();

Related

trying to make a mark off for the to do App

I am currently busy trying to make a mark off to the list when it is done for the to-do app.
here is the JS code for it:
let tasks = [];
function todo(text) {
const todo = {
text,
checked: false,
id: Date.now(),
};
tasks.push(todo);
displaytasks(todo);
}
const list = document.querySelector('.list')
list.addEventListener('click', event =>{
if(event.target.classList.contains('js-tick')){
const itemKey = event.target.parentElement.dataset.key;
toggleDone(itemkey);
}
});
function toggleDone(key)
{
const index = tasks.findIndex(item => item.id === Number(key));
tasks[index].checked =!tasks[index].checked;
displaytasks(tasks[index]);
}
I have the tasks displaying, but it does not mark off when I click the circle that makes a tick and a line through.
In the console, I do not see it firing at all. As you can see I passed through the toggleDone(itemkey) to the function toggleDone(key)
changed
const itemKey = event.target.parentElement.dataset.key;
toggleDone(itemkey);
to
toggleDone(itemKey);
which fixed it.

How to show a different div in react 16 when a boolean changes from false to true?

I have a basic form in a react 16 app. Upon successful submission of the form I am switching a variable from false to true and I then want to display a different div once the boolean is switched.
Here is my variable declaration and form submit function:
var formSubmitted = false;
const formObj = {};
const requestInfo = (formObj) => {
formObj.modelNumber = product.modelNumber
submitForm(formObj)
.then(value => {
formSubmitted = true;
console.log(formSubmitted);
}, reason => {
// rejection
});
};
Here is the conditional HTML:
<div>Always showing</div>
{ !formSubmitted &&
<div>Form has not been submitted</div>
}
{ formSubmitted &&
<div>Form submitted successfully</div>
}
The form is submitting successfully and the console.log is showing the variable formSubmitted was successfully switched from false to true, however the HTML is not changing. I'm new to react 16 and I'm not sure how to update the component.
Thank you.
Although the variable of formSubmitted had changed, no state or side effects were triggered, so it didn't trigger the React render function hence the changes you expect are not reflecting on the DOM. You can use useState to manage the boolean value of formSubmitted so every new assignment will trigger 'render' to update the UI.
Here is a snippet:
const [formSubmitted, setFormSubmitted] = useState(false);
const formObj = {};
const requestInfo = (formObj) => {
formObj.modelNumber = product.modelNumber
submitForm(formObj)
.then(value => {
setFormSubmitted(true);
console.log(formSubmitted);
}, reason => {
// rejection
});
};
...
<div>Always showing</div>
{ !formSubmitted &&
<div>Form has not been submitted</div>
}
{ formSubmitted &&
<div>Form submitted successfully</div>
}
And that should work.

What is "onkeyup" in gamepad api?

I'm experimenting a little with gamepad api. When a button is pressed, it gets "fired" all the time until the button is released. This is cool for something like running or shooting in a game, but problematic, when I want to navigate inside a menu (e.g. going 16 menu items down instead of one).
On a normal keyboard, I would use onkeyup/onkeydown to see, when a button press has ended. But on gamepad api, I didn't find something like that.
JS:
var gamepadPressCounter = 0;
function update() {
const gamepads = navigator.getGamepads();
if (gamepads[0].buttons[0].pressed === true) {
gamepadPressCounter++;
}
window.requestAnimationFrame(update);
}
So, do you know a way to get the gamepadPressCounter only one up per press and not infinite times?
You have to check it against whether it was pressed last time you checked. It doesn't fire events, it is stateful. You are directly checking if the button is pressed or not.
Make a variable to store the state of the button last time you checked and compare it to see if it has changed from last time.
const anyButtonPressedChangeListener = {
listeners: [],
addListener(cb) {
this.listeners.push(cb);
},
signal(state) {
for (const cb of this.listeners) {
cb(state);
}
}
}
let buttonstate = false;
const loop = () => {
const gamepads = navigator.getGamepads();
const gamepad = gamepads[0];
if (gamepad) {
const statenow = gamepad.buttons.some(btn => btn.pressed);
if (buttonstate !== statenow) {
buttonstate = statenow;
anyButtonPressedChangeListener.signal(statenow);
}
} else {
console.log("Not connected");
}
setTimeout(loop, 1000);
}
anyButtonPressedChangeListener.addListener((buttonState) => {
console.log(`Any button pressed changed to ${buttonState}`);
});
loop();

Cannot empty array in ReastJS useEffect hook

I'm having this table view in React where I fetch an array from API and display it. When the user types something on the table search box, I'm trying to clear the current state array and render the completely new result. But for some reason, the result keeps getting appended to the current set of results.
Here's my code:
const Layout = () => {
var infiniteScrollTimeout = true;
const [apiList, setapiList] = useState([]);
//invoked from child.
const search = (searchParameter) => {
//Clearing the apiList to load new one but the console log after setApiList still logs the old list
setapiList([]); // tried setApiList([...[]]), copying the apiList to another var and emptying it and then setting it too.
console.log(apiList); //logs the old one.
loadApiResults(searchParameter);
};
let url =
AppConfig.api_url + (searchParameter || "");
const loadApiResults = async (searchParameter) => {
let response = await fetch(url + formurlencoded(requestObject), {
method: "get",
headers: headers,
});
let ApiResult = await response.json(); // read response body and parse as JSON
if (ApiResult.status == true) {
//Set the url for next fetch when user scrolls to bottom.
url = ApiResult.next_page_url + (searchParameter || "");
let data;
data = ApiResult.data;
setapiList([...data]);
}
}
useEffect(() => {
loadApiResults();
document.getElementById("myscroll").addEventListener("scroll", () => {
if (
document.getElementById("myscroll").scrollTop +
document.getElementById("myscroll").clientHeight >=
document.getElementById("myscroll").scrollHeight - 10
) {
if (infiniteScrollTimeout == true) {
console.log("END OF PAGE");
loadApiResults();
infiniteScrollTimeout = false;
setTimeout(() => {
infiniteScrollTimeout = true;
}, 1000);
}
}
});
}, []);
return (
<ContentContainer>
<Table
...
/>
</ContentContainer>
);
};
export default Layout;
What am I doing wrong?
UPDATE: I do see a brief moment of the state being reset, on calling the loadApiResult again after resetting the state. The old state comes back. If I remove the call to loadApiResult, the table render stays empty.
add apiList in array as the second parameter in useEffect
You need to use the dependencies feature in your useEffect function
const [searchParameter, setSearchParameter] = useState("");
... mode code ...
useEffect(() => {
loadApiResults();
... more code ...
}, [searchParameter]);
useEffect will automatically trigger whenever the value of searchParameter changes, assuming your input uses setSearchParameter on change

Javascript Observable performance issue

Current Working Code
I have the following rxjs/Observable:
findMessages(chatItem: any): Observable<any[]> {
return Observable.create((observer) => {
let processing: boolean = false;
this.firebaseDataService.findMessages(chatItem).forEach(firebaseItems => {
if (!processing) {
processing = true;
this.localDataService.findMessages(chatItem).then((localItems: any[]) => {
let mergedItems: any[] = this.arrayUnique(firebaseItems.concat(localItems), false);
mergedItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
if (this.me && mergedItems && mergedItems[0] && this.me.uid === mergedItems[0].memberId2) {
this.updateChatWithMessage(chatItem, mergedItems[0], false);
}
observer.next(mergedItems);
processing = false;
});
}
});
});
}
and
this.firelist = this.dataService.findMessages(this.chatItem);
this.firelist.subscribe(items => {
...
});
As you can see, it returns a list of firebaseItems and localItems, which are merged to mergedItems. This works perfectly.
Performance Enhancement
However, I am trying to increase the performance that the items load. So figure, I would like to first load the localItems, and then add to the list with the firebaseItems.
So I try add the following function:
findLocalMessages(chatItem: any): Observable<any[]> {
return Observable.create((observer) => {
this.localDataService.findMessages(chatItem).then((localItems: any[]) => {
localItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
observer.next(localItems);
});
});
}
and call it as follows:
this.firelist = this.dataService.findLocalMessages(this.chatItem);
this.firelist = this.dataService.findMessages(this.chatItem);
this.firelist.subscribe(items => {
...
});
Problem
This has now introduced a bug, that there are now 2 Observables and the results are not as expected. The sort order is incorrect, and some of the items are not being added to the this.firelist for some reason.
Question
Whats the best way to handle this?
I was thinking if it's possible to make the findLocalMessages Observable only get fired once, and then it never works again, as the findMessages Observable will maintain the list. Is this possible? I have been looking at the Observable api, and can't seem to figure out how to do that.
Any help appreciated.
With the risk of oversimplifying the problem statement, you have two streams of data that you want to merge and sort in an efficient manner.
The separation you have made is a step in the right direction.
The reason why you are not getting all the messages is that you are overriding the first observable with the second.Have a look at the following example and see what happens if you try and assign the second observable to move instead of move2.
let move = Observable.fromEvent(document.getElementById("1"), 'mousemove');
let move2 = Observable.fromEvent(document.getElementById("2"), 'mousemove');
move
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
move2
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
<h1>
<div id="1">
aaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<div id="2">
bbbbbbbbbbbbbbbbbbbbbbbbb
</div>
</h1>
In order to merge the two streams together properly you need to use the merge operator as shown below:
let move = Observable.fromEvent(document.getElementById("1"), 'mousemove');
let move2 = Observable.fromEvent(document.getElementById("2"), 'mousemove');
move.merge(move2)
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
Now all you need to do is sort them. I would advice that you do the sort only after the merge because otherwise, you will end up with two streams that are sorted locally, not globally.

Categories

Resources