Best practice for filtering an array in Angular 2 - javascript

I have a component called Tours that shows a full list of tours.
I have a link to a site called favorites which should display the tours with the favorite parameter set to true, and apart from that identical to the Tour component.
Is there a best practice for achieving this?
I can think of a few ways
Create a separate route to this component and filter based on the value in the route
Create a custom pipe that's triggered based on the route path
However, neither of them seem optimal to me

I exclusively use the filter function, especially in cases such as yours. If you have an array of tours with a favorite property on each iteration, you could do this:
this.tours.filter((item) => {
return (item.favorite === true)
})
Or if you wanted a filter function with a favorite parameter, you could do this:
filterFavorites(favorite : boolean): Array<string>{
return tours.filter((item) => {
return (favorite)? item : null;
})
}
Let me know if this helps you.
Edit: you could definitely create a pipe, but I think that would be overkill in your case, unless you intend to use the pipe multiple times in other places.

Related

Conditional Rendering of Arrays in React: For vs Map

I'm new to React and building a calendar application. While playing around with state to try understand it better, I noticed that my 'remove booking' function required a state update for it to work, while my 'add booking' function worked perfectly without state.
Remove bookings: requires state to work
const [timeslots, setTimeslots] = useState(slots);
const removeBookings = (bookingid) => {
let newSlots = [...timeslots];
delete newSlots[bookingid].bookedWith;
setTimeslots(newSlots);
}
Add bookings: does not require state to work
const addBookings = (slotid, tutorName) => {
timeslots[slotid].bookedWith = tutorName;
}
I think that this is because of how my timeslot components are rendered. Each slot is rendered from an item of an array through .map(), as most tutorials online suggest is the best way to render components from an array.
timeslots.map(slot => {
if (!slot.bookedWith) {
return <EmptyTimeslot [...props / logic] />
} else {
return <BookedTimeslot [...props / logic]/>
}
})
So, with each EmptyTimeslot, the data for a BookedTimeslot is available as well. That's why state is not required for my add bookings function (emptyTimeslot -> bookedTimeslot). However, removing a booking (bookedTimeslot -> emptyTimeslot) requires a rerender of the slots, since the code cannot 'flow upwards'.
There are a lot of slots that have to be rendered each time. My question is therefore, instead of mapping each slot (with both and information present in each slot), would it be more efficient to use a for loop to only render the relevant slot, rather than the information for both slots? This I assume would require state to be used for both the add booking and remove booking function. Like this:
for (let i=0;i<timeslots.length;i++) {
if (!timeslot[i].bookedWith) {
return <EmptyTimeslot />
} else {
return <BookedTimeslot />
}
}
Hope that makes sense. Thank you for any help.
Your addBooking function is bad. Even if it seems to "work", you should not be mutating your state values. You should be using a state setter function to update them, which is what you are doing in removeBookings.
My question is therefore, instead of mapping each slot (with both and information present in each slot), would it be more efficient to use a for loop to only render the relevant slot, rather than the information for both slots?
Your map approach is not rendering both. For each slot, it uses an if statement to return one component or the other depending on whether the slot is booked. I'm not sure how the for loop you're proposing would even work here. It would just return before the first iteration completed.
This I assume would require state to be used for both the add booking and remove booking function.
You should be using setTimeslots for all timeslot state updates and you should not be mutating your state values. That is true no matter how you render them.

React components rendering with wrong props when changing filter

I have a page in a react app that uses a radio form to filter different objects of data that are being passed to the page.
The problem I am encountering, is that when I change the filter, (click on a different option on the radio form), only some of the data in the resulting list changes. A simple example of what happens is as follows:
Option one is selected with all the old data
Option two is selected, but only some of the new data comes through
First, I use an axios request to get an array of objects that will be used for the data:
componentDidMount() {
axios.get("xxxxxxxxx")
.then(result => {
this.setState({
data: result.data
});
});
Then, I create an array that filters the data from axios by an attribute based on which radio option is selected in the form:
let filteredData = [];
filteredData = this.state.data.filter(thisData => thisData.attribute === "attribute1");
Finally, I map all of the filtered data in the render function:
filteredData.map(filteredItem => ( <MyComponent key={i++} itemInfo={filteredItem.info} /> ))
In the definition of MyComponent, I use props to access the filtered item's info and put it into the table like this:
<td>{this.props.itemInfo.thisDataPoint}</td>
I'd love to hear if anybody has any idea why some of the components data updates when the filter changes, but not all of it. It seems weird to me that some data changes but some does not.
I have tried converting the props into state so that the component re-renders on the change but that did not work.
Thanks again for any help I get :)
given that filteredData is correct, and based on your code the issue must be on key={i++}. using some kind of implementation index can often lead to rendering problems, react will have trouble to distinguish the components since it uses the key to track them.
you should provide some unique identifier to each component as key like key={filteredItem.id}. if you don't have it, you can generate it with some library like uuid.

What's the right approach to update a parent's component array of objects from a child component?

I have a complex form with different tabs. I use vue-router to switch between these and display different modular components in a router-view for each one of them. In these tabs I have child components with sometimes other nested child components. I use the event bus approach to pass data from these child components up in tree. I'm doing this because the final tab will be a summary of the form, and I will need access to all the form data. At the moment I'm using something like the below.
For example using this structure:
|App
--|Start
--|Question 1
--|Answer 1
--|Answer 2
--|Question 2
...
In the root component (App):
data() {
return {
questions: 0,
answers: []
}
},
created() {
eventBus.$on('answer-added', answer => {
let answer_exists = false
this.answers.forEach( (e, i) => {
if(e.id == answer.answer_id) answer_exists = true
});
if(!answer_exists) this.answers.push({
id: answer.answer_id,
answer: answer.answer_text
})
});
}
What's the proper way to create/update/delete the array of answers in the App component every time an event from the child is fired?
I'm sure there must be a much better way than iterating over the array elements to check whether the answer already existed or not... Just can't figure it out.
Do you mean something like:
if (!this.answers.find(a => a.id === answer.answer_id)) {
this.answers.push(/* ... */);
}
What you are doing is more or less right. There is no escape from the loop. However, there are certain things you can improve upon. Instead of forEach, you can use Array.some method. Alternately, you can use Array.find method:
eventBus.$on('answer-added', answer => {
// Instead of using forEach, you can use Array.some() method
const answerExists = this.answers.some((x) => e.id == answer.answer_id);
if (!answerExists) {
this.answers.push({
id: answer.answer_id,
answer: answer.answer_text
});
}
});
Second, there is no problem with using an event bus, but it is generally used for a sibling or cross-component communication. When all the components are in the same ancestor or parent hierarchy, using $emit for events is the right way.
In your case, even though you have a nested components hierarchy, as the application evolves, a hierarchy can get really deep and you will quickly lose the track of typical publish-subscribe mechanism of the event bus. Even if it means re-emitting the same events from intermediate components, you should follow this practice.
Pass a callback from parent to child. Now they can communicate bottom up. The child can pass any data the parent might want and then the parent can take back control and use its state or closure state.
For anyone who comes across with the same issue, the problem I encountered is resolved by using a Simple Global Store. Other more complex scenarios would possibly require Vuex as suggested by #Dan above.

Passing down arguments using Facebook's DataLoader

I'm using DataLoader for batching the requests/queries together.
In my loader function I need to know the requested fields to avoid having a SELECT * FROM query but rather a SELECT field1, field2, ... FROM query...
What would be the best approach using DataLoader to pass down the resolveInfo needed for it? (I use resolveInfo.fieldNodes to get the requested fields)
At the moment, I'm doing something like this:
await someDataLoader.load({ ids, args, context, info });
and then in the actual loaderFn:
const loadFn = async options => {
const ids = [];
let args;
let context;
let info;
options.forEach(a => {
ids.push(a.ids);
if (!args && !context && !info) {
args = a.args;
context = a.context;
info = a.info;
}
});
return Promise.resolve(await new DataProvider().get({ ...args, ids}, context, info));};
but as you can see, it's hacky and doesn't really feel good...
Does anyone have an idea how I could achieve this?
I am not sure if there is a good answer to this question simply because Dataloader is not made for this usecase but I have worked extensively with Dataloader, written similar implementations and explored similar concepts on other programming languages.
Let's understand why Dataloader is not made for this usecase and how we could still make it work (roughly like in your example).
Dataloader is not made for fetching a subset of fields
Dataloader is made for simple key-value-lookups. That means given a key like an ID it will load a value behind it. For that it assumes that the object behind the ID will always be the same until it is invalidated. This is the single assumption that enables the power of dataloader. Without it the three key features of Dataloader won't work anymore:
Batching requests (multiple requests are done together in one query)
Deduplication (requests to the same key twice result in one query)
Caching (consecutive requests of the same key don't result in multiple queries)
This leads us to the following two important rules if we want to maximise the power of Dataloader:
Two different entities cannot share the same key, othewise we might return the wrong entity. This sounds trivial but it is not in your example. Let's say we want to load a user with ID 1 and the fields id and name. A little bit later (or at the same time) we want to load user with ID 1 and fields id and email. These are technically two different entities and they need to have a different key.
The same entity should have the same key all the time. Again sounds trivial but really is not in the example. User with ID 1 and fields id and name should be the same as user with ID 1 and fields name and id (notice the order).
In short a key needs to have all the information needed to uniquely identify an entity but not more than that.
So how do we pass down fields to Dataloader
await someDataLoader.load({ ids, args, context, info });
In your question you have provided a few more things to your Dataloader as a key. First I would not put in args and context into the key. Does your entity change when the context changes (e.g. you are querying a different database now)? Probably yes, but do you want to account for that in your dataloader implementation? I would instead suggest to create new dataloaders for each request as described in the docs.
Should the whole request info be in the key? No, but we need the fields that are requested. Apart from that your provided implementation is wrong and would break when the loader is called with two different resolve infos. You only set the resolve info from the first call but really it might be different on each object (think about the first user example above). Ultimately we could arrive at the following implementation of a dataloader:
// This function creates unique cache keys for different selected
// fields
function cacheKeyFn({ id, fields }) {
const sortedFields = [...(new Set(fields))].sort().join(';');
return `${id}[${sortedFields}]`;
}
function createLoaders(db) {
const userLoader = new Dataloader(async keys => {
// Create a set with all requested fields
const fields = keys.reduce((acc, key) => {
key.fields.forEach(field => acc.add(field));
return acc;
}, new Set());
// Get all our ids for the DB query
const ids = keys.map(key => key.id);
// Please be aware of possible SQL injection, don't copy + paste
const result = await db.query(`
SELECT
${fields.entries().join()}
FROM
user
WHERE
id IN (${ids.join()})
`);
}, { cacheKeyFn });
return { userLoader };
}
// now in a resolver
resolve(parent, args, ctx, info) {
// https://www.npmjs.com/package/graphql-fields
return ctx.userLoader.load({ id: args.id, fields: Object.keys(graphqlFields(info)) });
}
This is a solid implementation but it has a few weaknesses. First, we are overfetching a lot of fields if we have different field requiements in the same batch request. Second, if we have fetched an entity with key 1[id,name] from cache key function we could also answer (at least in JavaScript) keys 1[id] and 1[name] with that object. Here we could build a custom map implementation that we could supply to Dataloader. It would be smart enough to know these things about our cache.
Conclusion
We see that this is really a complicated matter. I know it is often listed as a benefit of GraphQL that you don't have to fetch all fields from a database for every query, but the truth is that in practice this is seldomly worth the hassle. Don't optimise what is not slow. And even is it slow, is it a bottleneck?
My suggestion is: Write trivial Dataloaders that simply fetch all (needed) fields. If you have one client it is very likely that for most entities the client fetches all fields anyways, otherwise they would not be part of you API, right? Then use something like query introsprection to measure slow queries and then find out which field exactly is slow. Then you optimise only the slow thing (see for example my answer here that optimises a single use case). And if you are a big ecomerce platform please don't use Dataloader for this. Build something smarter and don't use JavaScript.

Performance of an angular 2 application with Firebase

I have been creating a web application using angular2 with firebase (angularfire2),
I want to know if my development method is optimized or not.
When user select a group, I check if he is already member of the group.
ngOnInit() {
this.af.auth.subscribe(auth => {
if(auth) {
this.userConnected = auth;
}
});
this.router.params.subscribe(params=>{
this.idgroup=params['idgroup'];
});
this._groupService.getGroupById(this.idgroup).subscribe(
(group)=>{
this.group=group;
this.AlreadyPaticipe(this.group.id,this.userConnected.uid),
}
);
}
this method is work, but when I place the function AlreadyPaticipe(this.group.id,this.userConnected.uid) outside getGroupById(this.idgroup).subscribe() ,I get an error group is undefinded ,I now because angular is asynchrone. I don't khow how I can do it?. How I can optimize my code ?,How I can place the function AlreadyPaticipe(this.group.id,this.userConnected.uid) outside getGroupById(this.idgroup).subscribe()
Thanks in advance.
Everything as stream :
Well first, you shouldn't subscribe that much, the best practice is to combine your observables into one and subscribe to it just once, because everytime you subscribe, you need to cleanup when your component is destroyed (not for http, neither ActivatedRoute though) and you end up managing your subscription imperatively (which is not the aim of RXjs). You can find a good article on this topic here.
You must think everything as a stream, all your properties are observables :
this.user$ = this.af.auth.share(); //not sure of the share, I don't know firebase, don't know what it implies...
this.group$ = this.router.params.map(params => params["idgroup"])
.switchMap(groupID => this.groupService.getGroupById(groupID)).share();
// I imagine that AlreadyPaticipe return true or false, but maybe i'm wrong
this.isMemberOfGroup$ = Observable.combineLatest(
this.group$,
this.user$.filter(user => user !== null)
).flatMap(([group, user]) => this.AlreadyPaticipe(groupID, user.uid));
You don't even have to subscribe ! in your template you just need to use the async pipe. for example:
<span>user: {{user$|async}}</span>
<span>group : {{group$|async}}</span>
<span>member of group : {{isMemberOfGroup$|async}}</span>
Or if you don't want to use the pipe, you can combine all those observable and subscribe only once :
this.subscription = Observable.combineLatest(
this.group$,
this.user$,
this.isMemberOfGroup$
).do(([group, user, memberofGroup]) => {
this.group = group;
this.user = user;
this.isMemberofGroup = memberofGroup;
}).subscribe()
in this case, don't forget to this.subscription.unsubscribe() in ngOnDestroy()
there is a very handy tool on rxJS docs (at the bottom of the page) that helps you to choose the right operator for the right behavior.
I don't care about streams, I want it to work, quick n' dirty :
If You don't want to change your code too much, you could use a Resolve guard that will fetch the data before your component is loaded. Take a look at the docs:
In summary, you want to delay rendering the routed component until all necessary data have been fetched.
You need a resolver.

Categories

Resources