When I create new note, it orders old to new, but I want to order new to old (reverse it).
How can i do this ?
my codes:
const notesRef = useFirestore().collection('users').doc(uid).collection("notes");
const {status, data} = useFirestoreCollection(notesRef.orderBy("timezone"));
and its image: (Here, it order like 1-2-3, but i want to order, 3-2-1, new to old)
our return like map:
{data?.docs?.map((d, index) => {
return (<Note
key={index}
id={index}
title={d.data().title}
content={d.data().content}
onDelete={deleteNote}
onDocId={d.id}
timezone={d.data().timezone}
/>);
})}
To sort a query in descending order, you can pass a second parameter to orderBy. So:
notesRef.orderBy("timezone", "desc")
Also see the Firebase documentation on ordering data.
There are a bunch of ways of doing this.
Fetch the data in reversed order from firebase. I am using 25 as an example. You can choose the limit as per your requirements.
notesRef.orderBy("timezone").limitToLast(25);
Reverse the data on the client-side
const {status, data} = useFirestoreCollection(notesRef.orderBy("timezone"));
console.log(data.docs.reverse())
Related
I have user and group collections. Under the user collection, each document id is a user UID and each user document has an array field "userGroups" which contains the groups that user belongs to and those groups are the group's document ID under group collection.
I have been able to retrieve the userGroups array for the current user which i stored in groupRef (see code below). What I'm trying to do now is to map those array values into groups collection and retrieve only those documents that are in the groupRef. (Basically the goal is to just show in the UI the groups that the current user is a member of)
user collection
group collection
const [groupsList, setGroupList] = useState([]);
const [groupRef, setGroupRef] = useState([]);
const [track, setTrack] = useState('')
const handleSubmit = () => {
setTrack('start')
fire.firestore().collection('users').doc(fire.auth().currentUser.uid).get().then((value) => {
console.log("userGroups " + value.data().userGroups) // this returns an object
setGroupRef([value.data().userGroups])
})
}
useEffect(() => {
handleSubmit()
}, [track])
console.log("sample list " + groupRef)
fire.firestore().collection('groups').onSnapshot(snapshot => (
setGroupList(snapshot.docs.map(doc => doc.data()))
))
^ this returns all the documents under groups collection. any ideas how i can retrieve only those that the current user is a member of? any help would be much appreciated. (ive been stuck on this for a long time. im also new to firebase.)
#DougStevenson directed me to the right path of using array-contains which is a helper function on querying/getting data. the code below is the answer to my problem. this way is shorter and more efficient than the work i came up with.
fire.firestore().collection('groups').where("groupMembers", "array-contains", fire.auth().currentUser.uid).onSnapshot(snapshot => ( setGroupList(snapshot.docs.map(doc => doc.data()))
))
I am starting with a simple array of JSX elements:
const jsxArray = dataItems.map(item => (
<div>
<Header>{item.title}</Header>
<Paragraph>{item.body}</Paragraph>
<Paragraph customAttribute={item.isActive} >{item.tags}</Paragraph>
</div>
))
Inside render, or rather return since I use functional components for everything now, I'd like to filter for JSX elements where the isActive attribute was tagged true.
return (
{jsxArray
.filter(jsxElement => // want to filter in JSX elements
// that are true for customAttribute keyed to `item.isActive`)
}
)
Is there any way to do it?
If there is not precisely a good way I am open to workarounds.
It is possible for me to simply filter the array at an earlier step. It would result in some extra code duplication though, since I would still need the array of unfiltered JSX elements elsewhere.
You don't filter the list after you render it. At that point it's just a tree of nodes that doesn't have much meaning anymore.
Instead you filter the items first, and then render only the items that pass your criteria.
const jsxArray = dataItems.filter(item => item.isActive).map(item => (
<div>
<h3>{item.title}</p>
<p>{item.body}</p>
<p customAttribute={item.isActive} >{item.tags}</p>
</div>
))
It is possible for me to simply filter the array at an earlier step. It would result in some extra code duplication though, since I would still need the array of unfiltered JSX elements elsewhere.
Not necessarily. When dealing with filtering like this myself I create two variables, one for the raw unfiltered list and one for the filtered items. Then whatever you're rendering can choose one or the other depending on its needs.
const [items, setItems] = useState([])
const filteredItems = items.filter(item => item.isActive)
return <>
<p>Total Items: ${items.length}</p>
<ItemList items={filteredItems} />
</>
Instead of accessing the jsx element properties (which I think it's either not possible or very difficult) I suggest you to act in this way:
Save the renderer function for items in an arrow function
const itemRenderer = item => (
<div>
<Header>{item.title}</Header>
<Paragraph>{item.body}</Paragraph>
<Paragraph customAttribute={item.isActive} >{item.tags}</Paragraph>
</div>
)
Save the filter function in an arrow function
const activeItems = item => item.isActive
Use them to filter and map
const jsxArray = dataItems.filter(activeItems).map(itemRenderer)
Use them to map only
const jsxArray = dataItems.filter(activeItems).map(itemRenderer)
Hope this helps!
Usually you would filter the plain data first and then render only the markup for the filtered elements as described in #Alex Wayne answer.
If you worry about duplication of the markup, that can be solved by extracting a component from it:
const Item = ({title, body, isActive, tags}) => (
<div>
<Header>{title}</Header>
<Paragraph>{body}</Paragraph>
<Paragraph customAttribute={isActive}>{tags}</Paragraph>
</div>
);
For rendering the filtered list you can then do:
{items.filter(item => item.isActive).map(item => <Item {...item} />)}
and for the unfiltered list:
{items.map(item => <Item {...item} />)}
I have two observables that I would like to wait for the results of both so I can filter the results of one based upon the other. Individually they wor:
this.allValues$ = this.store.select(selectors.getAllValues)
this.myId$ = this.store.select(selectors.myId)
and I can render them to the template with the async pipe
However I would like to make a class property which holds a filtered array. If it were synchronous JS, something like
this.filteredResults = allValues.filter(value => value.id === myId)
ziping will get me the values
this.filteredResults$ = zip(
this.store.select(selectors.getAllValues),
this.store.select(selectors.myId)
)
template:
results: {{ filteredResults$ | async | json }}
But I cannot understand how to filter like I want. I've tried chaining a pipe to the zip:
.pipe(
tap((...args) => {
console.log({ args }) // only one result so no hope of dropping in `map` or `filter` here
})
)
But this has the effect of removing the allValues array from the result set. allValues is vastly larger so presumably taking longer and zip is no longer waiting for everything to emit so I guess pipe isn't the solution, though it seems close.
How can I can I access both these result sets, filter them, and put that result in an observable I can render in the template with filteredResults$ | async | json?
You could use concatMap or switchMap.
this.filteredResults = this.store.select(selectors.myId).pipe(
concatMap(myId => {
return this.store
.select(selectors.getAllValues)
.pipe(filter(value => value.id === myId));
})
);
It's easy to overlook NgRx selectors. You should check out the documentation, because what you are trying to achieve is possible with the createSelector method. This will have multiple benefits (cleaner code, reusable selector, etc)
https://ngrx.io/guide/store/selectors#using-selectors-for-multiple-pieces-of-state
Something like this might work in your case:
export const yourCombinedSelector = createSelector(
selectors.getAllValues,
selectors.myId,
(allValues, myId) => {
// perform logic and return the data you need
}
);
Then
const data$ = this.store.select(selectors.yourCombinedSelector)
I am trying to implement a filter function that is able to search in two separate JSON fields when a user types in a search bar. Searching the whole JSON returns errors and if I repeat this function, the two similar functions cancel each other out.
My current filter function:
let filteredOArt = origArt.filter((origAItem) => {
return origAItem.authors.toLowerCase().includes(this.state.search.toLowerCase())
});
I want to be able to have the search look within the "authors" field as well as a "description" field.
Before the React render, I have this function listening to the state:
updateSearch(event) {
this.setState({ search: event.target.value })
}
Then my search function is in an input field in the React return:
<h6>Search by author name: <input type="text" value={this.state.search} onChange={this.updateSearch.bind(this)} /></h6>
You can tweak the function a bit like this
let filteredOArt = origArt.filter((origAItem) => {
return (
(origAItem.authors.toLowerCase().includes(this.state.search.toLowerCase())||
(origAItem.description.toLowerCase().includes(this.state.search.toLowerCase())
)
)
});
You actually can do a filter for both fields.
Given you have your searchValue and your array with the objects you could filter this way:
const filterByAuthorOrDescription = (searchValue, array) =>
array.filter(
item =>
item.authors.toLowerCase().includes(searchValue.toLowerCase()) ||
item.description.toLowerCase().includes(searchValue.toLowerCase())
);
const filtered = filterByAuthorOrDescription(this.state.search, articles);
filtered will now contain an array of objects that contain your searchValue in either description or authors that you can map through.
You could use some to check if the filter is positive for at least one field :
let filteredOArt = origArt.filter(origAItem => ['authors', 'description'].some(field => origAItem.[field].toLowerCase().includes(this.state.search.toLowerCase())))
Just iterate over the different field names you want to use.
Some will return true if any of the fields mentioned contains your string and avoid repetitions in your code.
Long syntax :
origArt.filter(origAItem => {
return ['authors', 'description'].some(field => origAItem.[field].toLowerCase().includes(this.state.search.toLowerCase()))
})
I just started learning RxJS. One thing I have tried to do without much luck is, figuring out how to search/filter a Subject, or create an observed array that I can search on.
I've tried piping and filtering a Subject and BehaviorSubject, but the values in the predicate are RxJS specific.
From what I've read on various posts, the way is to observe an array to use a Subject.
I can easily have two variables, one array and the Subject, and search the array. But I'd like to accomplish this one variable.
In Knockout its possible to search an observed array.
Is this possible in RxJS?
Thanks in advance.
Example:
layers: Rx.Subject<any> = new Rx.Subject<any>();
toggleLayer (layerId, visible) {
//find layer we need to toggle
// How do I search the subject to get the values added in next()?
// tried from(this.layers), pipe does not fire
const source = of(this.layers);
const example = source.pipe(filter((val, index) => {
//val is subject, and only iterates once, even if more than one value in subject
// tslint:disable-next-line:no-debugger
debugger;
return false;
}));
const sub = example.subscribe((val) => {
// tslint:disable-next-line:no-debugger
debugger;
});
}
private addLayer = (layerName, layerObj, layerType) => {
// component class is subscribed to layers subject. Update UI when layer is added
this.layers.next({
layerId: this.layerId,
name: `${layerName}_${this.layerId}`,
layerObj: layerObj,
visible: true,
layerType: layerType
});
}
I'm not 100% clear on the specifics of your ask, but maybe this example will help you.
const filterSubject = new BehaviorSubject<string>('b');
const dataSubject = new BehaviorSubject<string[]>(['foo', 'bar', 'baz', 'bat']);
const dataObservable = combineLatest(filterSubject, dataSubject).pipe(
// given an array of values in the order the observables were presented
map(([filterVal, data]) => data.filter(d => d.indexOf(filterVal) >= 0))
);
dataObservable.subscribe(arr => console.log(arr.join(',')));
// bar, baz, bat
Using combineLatest, you can have the value in dataObservable updated whenever either your filter value or your data array changes.