Svelte - on:event listener removal - performance

I'm wondering whether assigning undefined to an element's event, i.e. on:mousemove prevents a memory leak the same as removeEventListener does, or at least should not be a concern long term. I've checked with getEventListeners on chrome, and it's there with the on:mousemove approach, but I'm not sure whether I should worry about this and use the more verobse approach with custom actions.
I have a Dropdown inside a Container. On moving the mouse over the container, I want to change the Dropdown's position.
My initial approach was writing a custom use:containerMouseMove action, that took Dropdown's visible as a dependency, and removed the event listener from the container, when the Dropdown became invisible.
Dropdown.svelte:
use:mousemoveContainer={{ container, destroyOn: !visible }}
on:mousemove_container={(e) => {
if (mouseTrack) {
[x, y] = calcCoordinates(e, container);
}
}}
Action definition:
type Deps = { container: HTMLElement; destroyOn: boolean };
export const mousemoveContainer = (node: HTMLDivElement, deps: Deps) => {
const handleMouseMove = (e: MouseEvent) => {
node.dispatchEvent(new CustomEvent('mousemove_container', { detail: e }));
};
return {
update(deps: Deps) {
if (!deps.destroyOn) {
deps.container.addEventListener('mousemove', handleMouseMove);
}
if (deps.destroyOn) {
deps.container.removeEventListener('mousemove', handleMouseMove);
}
}
};
};
Then I learned about export const function as a way to communicate between parent and child, and it simplifies the code. But I'm not sure if there's not a memory leak right now.
on:mousemove={dropdown.getVisible() ? dropdown.onContainerMouseMove : undefined}
onContainerMouseMoveis the callback inside on:mousemove_container.

on:event listeners are removed automatically when the component is unmounted.
Within actions, one should return an object with a destroy() function and unsubscribe from events there. The function will be called when the element with the action on it is removed.

Related

How to create a computed based on another computed from composable

I'm learning composition API doing a simple todo app but then with multiple swim-lanes (todo, doing, done).
in useTasks.js composable I create "global" sourceTasks reactive, that later gets filled with data pulled from an API. Then it is reduced in tasks computed property, like this:
// useTasks.js
const sourceTasks = reactive({
list: []
});
export default function useTasks() {
const tasks = computed(() => {
return sourceTasks.list.reduce(divideIntoSwimLanes, [])
});
...
return {
tasks,
loadTasks,
createTask
}
}
Nothing too complicated.
Then I've got this SwimLane component, that well... uses the tasks :)
// SwimLane.vue - setup
async setup(props) {
const { status } = toRefs(props);
const { tasks, createTask } = useTasks();
return {
tasks,
label,
createTask
}
}
// SwimLane.vue - template
<single-task class="m-3" v-for="task in tasks[status]" :title="task.title" :id="task.id"/>
This works, but I don't find it elegant. I would prefer to create a new computed inside of SwimLane's setup, that holds the value of tasks for the given SwimLane. Putting it in the template obscures the logic.
I would expect this to work, but it does not, I think it loses the reactivity but I cant wrap my head around why:
// SwimLane.vue - alternative setup
const currentContextTasks = computed(() => {
return tasks.value[status]
});
return {
currentContextTasks
}
The problem feels a bit ridiculous, but my main concern is that I have misunderstood some core concept, hence this lengthy post.
This is like a biggest blunders ever. So right, the post was really helpful as a form of talking to the rubber duck.
What I did forgot to do is use the value of the status. Since it is a ref, I had to use it as follows:
const currentContextTasks = computed(() => {
return tasks.value[status.value] // 'status.value' and not just 'status'
});

React Redux Action Creator Dispatch Issue with Fetch API and Promise

I am writing action creator in react app. where in when i do some api call i need to show the Progress Loader on screen. So, my action creator looks like this.
export const fetchData = (actionType, param) => (dispatch) => {
dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER)); // Show Loader Action
return fetchDataRequest(actionType, param) // Here is Fetch APi Call
.then(responseData => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER));
dispatch(Action(recd(actionType), { data: responseData, receivedAt: Date.now() }));
}).catch((error) => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
});
};
When i write this piece of code its working as expected, i am dispatching the action as dispatch(fetchData(data)) from component and i am able to show the loader in my Parent Component. What i understand is fetch is returning me the promise. Once the fetch gets completed then i am hiding the loader which is working as expected.
Now, There is scenario where in i need to do some validation where in i don't have to make any api call but all the validation are performed locally.
Here also i want to do the same thing like i need to show loader in my parent component as well when all the validation are done i need to hide the loader.
I have written the same piece of code even actions are getting called but my render function is not getting called.
My Code Looks like:
// This my action creator which will actually do the validation
export const validateAndSaveData = () => {
return ((dispatch, getState) => {
return new Promise((resolve, reject) => {
let saveRecommendDetailsFlag = true;
// here i am dispacthing some action and storing data in my store
saveRecommendDetailsFlag = canSaveData(getState());
if (saveRecommendDetailsFlag) {
resolve('SUCCESS');
} else {
reject('ERROR');
}
});
});
};
And there is one more action creator which i am calling it from from UI Component which will first initiate the show loader action and then perform validation and based on the result of validation i have to hide the loader.
export const saveData = () => {
return ((dispatch) => {
dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER)); // Show Loader Action
return dispatch(validateAndSaveData())
.then(() => {
// Here i m dispatching an action to do some more processing.
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
})
.catch(() => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
});
});
};
Everything is working fine but my loader are not coming on the screen. i am not able to figure it out where am i doing wrong.
Can anyone suggest something how can i solve this issue?
I got some workaround using setTimeout func but i don't think that is right approach.
export const saveData = () => {
return ((dispatch) => {
dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER)); // Show Loader Action
setTimeout(()=>return dispatch(validateAndSaveData())
.then(() => {
// Here i m dispatching an action to do some more processing.
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
})
.catch(() => {
dispatch(Action(ActionConstants.HIDE_PROGRESS_LOADER)); // Hide Loader Action
});
},10);
});
};
Your code looks reasonable, my suspicion is that your validateAndSaveData promise finishes so quickly that there is no visible loader on the screen.
In that case, a timeout is totally reasonable. However, in order to do it properly, I would keep a state on if the loading screen is visible + if it's been shown long enough. You can then remove the loading screen once it is both up for long enough, and the actual event expires.
I'm not sure which action package you're using, so I can't post exact code, but the pseudocode would look something like this:
const delay = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds));
let loadingCounter = 0;
const showLoadingScreen = () => (dispatch) => {
const counter = loadingCounter;
loadingCounter++;
delay(5).then(() => {
if (getStore().loadingScreen.counter === counter) {
dispatch(Action(ActionConstants.PROGRESS_LOADER_DELAY_ELAPSED))
}
})
return dispatch(Action(ActionConstants.SHOW_PROGRESS_LOADER, counter))
}
Basically, you would keep track of 3 pieces of state for the loader:
{
counter: 0,
taskCompleted: false,
canHide: false,
}
Counter is saved so that you can disambiguate what happens if you get SHOW_PROGRESS_LOADER while an existing SHOW_PROGRESS_LOADER is in progress.
taskCompleted keeps a record of whether the thing you're waiting on is done, and canHide keeps track if the loader has been visible on the screen long enough.
When you dispatch PROGRESS_LOADER_DELAY_ELAPSED it sets canHide to true, and when you dispatch HIDE_PROGRESS_LOADER it sets taskCompleted to true. (although you may want to rename the latter action). When both canHide and taskCompleted are set to true, only then can the loader go away.
This is a pretty common UI pattern - Try to complete a task quickly. If it takes more than a short amount of time, then throw up a loading dialog. However, the loading dialog is guaranteed to stay up a minimum amount of time to prevent flickering. So the more advanced version of this kind of pattern would be to add another state which doesn't show the progress loader at all unless the call takes more than Y milliseconds.
Hope this makes sense, leave a comment if not :-)

React-redux: Trouble triggering child component action on parent state change

I am passing a galleryReload prop to DisplayImages from UploadImages to re fetch a list of files after a new file is uploaded.
I am having trouble getting DisplayImages to run it's getImages action on the state change of the parent without causing an endless loop.
class UploadImage extends React.Component {
constructor(props) {
super(props);
this.state = {
galleryReload: false
};
...
// in render
<DisplayImages galleryReload={this.state.galleryReload} />
In DisplayImages I can get first display of images with:
//works fine on first load
componentWillMount() {
this.props.getImages(this.props.gallery);
}
}
I am really having trouble figuring out how to run the action this.props.getImages(this.props.gallery) again after a file has been uploaded.
I tried setting the state of {galleryReload: true} once upload was finished (in parent) like so:
//onUploadFinished
this.setState((prevState, props) => ({
galleryReload: true
}));
and fetching the results again in child with componentWillUpdate and other lifecycle methods, but I kept getting myself into an endless loop of fetching.
Here is an example of what I put into the various lifecycle methods to fetch and then stop further fetches until the next upload completes:
if (this.props.galleryReload) {
this.props.getImages(this.props.gallery);
this.setState((prevState, props) => ({
galleryReload: false
}));
}
How do I rework this to call getImages just one time after image upload?
I realized that in learning react and redux at the same time I was confusing state. I was not setting redux state in parent, but rather local state. So to reset the local state of parent component from child component I needed to use a callback function in parent that sets the state of galleryReload and pass it as prop to child component.
In my parent I added the function:
resetGalleryReload() {
this.setState({
galleryReload: false
});
}
I also set in parent constructor
this.resetGalleryReload = this.resetGalleryReload.bind(this);
Then I sent to child component as prop like so
<DisplayImages
galleryReload={this.state.galleryReload}
resetReloadGallery={this.resetGalleryReload}
/>
Finally, I educated myself on the use of lifecycle methods with this nice read:
https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/introduction.html
So I created this in DisplayImages component
componentDidUpdate(prevProps, prevState) {
let val = this.props.galleryReload;
if (val) {
this.props.getImages(this.props.gallery);
this.props.resetReloadGallery();
}
}
I

Will react fire a state change if the prev and next state are the same?

On my state change handler should I need to do the following:
deviceSize = ...
this.setState((prevState) => {
if (prevState.deviceSize != deviceSize) {
return {
deviceSize
}
} else {
return null
}
})
Or is the following enough
this.setState((prevState) => {
return {
deviceSize
}
})
My concern is if I return something it will do some UI update.
You are wrong here. setState never check for values. When it is called, it just re-render view.
To check component should re-render or not,check shouldComponentUpdate() method.
`shouldComponentUpdate(nextProps, nextState)` {
1. it is triggered before the re-rendering process .
2. It tells render() method should be called or not.
3. If nothing changed, you can avoid re-render by returning false.
}
I would not go this route at all. You should not be calling setState unless you know its time to setState.
You should read the docs on setState
Use componentDidUpdate to know when to call setState. Like the docs say
Generally we recommend using componentDidUpdate() for such logic
instead.
For instance
componentDidUpdate(prevProps, prevState) {
if (prevState.something !== something) { // you can compare previous state or props with current state or props
this.setState({something: something});
}
}
notice that setState is only called when you actually want to call it

Event each time component becomes visible

Is there a way in Angular2 to have an event fired when my component becomes visible?
It is placed in a tabcontrol and I want to be notified when the user switches. I'd like my component to fire an event.
What I finally did (which is not very beautiful but works while I don't have a better way to do it...) is to use the ngAfterContentChecked() callback and handle the change myself.
#ViewChild('map') m;
private isVisible: boolean = false;
ngAfterContentChecked(): void
{
if (this.isVisible == false && this.m.nativeElement.offsetParent != null)
{
console.log('isVisible switched from false to true');
this.isVisible = true;
this.Refresh();
}
else if (this.isVisible == true && this.m.nativeElement.offsetParent == null)
{
console.log('isVisible switched from true to false');
this.isVisible = false;
}
}
There is no such event, but if you're using a tab control, the proper way to do this would be to create a tab change #Output for your tab control if it's custom, otherwise, most tab controls (like ng-bootstrap) have some tab change event as well.
If your component has to be aware of this, you can use this tab change event to detect which tab is visible, and if you know which tab is visible, you also know if your component is visible or not. So you can do something like this:
onTabChange(event) {
this.currentTab = /** Get current tab */;
}
And then you can send it to your component itself if you have an input:
#Input() activated: boolean = false;
And then you can apply it with:
<my-component [activated]="currentTab == 'tabWithComponent'"></my-component>
Now you can listen to OnChanges to see if the model value activated changed to true.
You can also refactor this to use a service with an Observable like this:
#Injectable()
export class TabService {
observable: Observable<any>;
observer;
constructor() {
this.observable = Observable.create(function(observer) {
this.observer = observer;
});
}
}
When a component wishes to listen to these changes, it can subscribe to tabService.observable. When your tab changes, you can push new items to it with tabService.observer.next().
You can use the ngAfterViewInit() callback
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
Update
The new Intersection Observer API can be used for that
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
See also https://stackoverflow.com/a/44670818/217408
For those watching at home, you can now use ngAfterContentInit() for this, at least on Ionic anyway.
https://angular.io/guide/lifecycle-hooks
Best way to work around this limitation of Angular is to use a shared service that provides a Subject your component can subscribe to. That way new values could be pushed onto the Observable and the components which subscribe get the newest data and can act accordingly.
Fyi: The difference between a normal Observable and a Subject is that a Subject is multicast whereas an Observable could only be subscribed to by one Subscriber.
As a small example I show you a possible implementation of a shared-service and following the subscription inside the component that needs this new data.
Shared-service:
// ...
private actualNumberSubject = new Subject<number>()
public actualNumber$ = this.actualNumberSubject.asObservable()
/**
* #info CONSTRUCTOR
*/
constructor() {}
/**
* #info Set actual number
*/
setActualNumber(number: number) {
this.actualNumberSubject.next(internalNumber)
}
// ...
Push new value onto the subject from anywhere where shared.service is imported:
// ...
this.sharedService.setActualNumber(1)
Subscribe to sharedService.actualNumber$ in component to process/display that new data:
// ...
this.sharedService.actualNumber$.subscribe(number => {
console.log(number)
// e.g. load data freshly, etc.
})
// ...
I have the same purpose and cannot get a satisfy approach to it. The first answer will call so many times.
There is a compromised way I used, of course, not elegant either.
In parent component, I set a method:
parentClick() {
setTimeout(() => {
// TO-DO
This.commonService.childMethod();
}, time);
}
Maybe the method not accurate in time, but in some way, you reach the destiny.

Resources