-
-
Notifications
You must be signed in to change notification settings - Fork 407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Module/map/backend #524
Module/map/backend #524
Conversation
I see the build is failing but due to Will try find a fix for travis and see if it works |
(fixed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some stuff around flow of the stores and the such which I think should be looked at, but in general that's a pretty cool integration there. :-D
const url = await this.injected.userStore.getUserAvatar(userName) | ||
this.setState({ avatarUrl: url }) | ||
getUserAvatar(userName: string) { | ||
return this.injected.userStore.getUserAvatar(userName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like something that should be done by state and @observer instead of waiting on the store to return something. What you are trying to achieve here should be in a lib, not in a store.
If you're wanting to use the store, then Generally speaking, you would have a placeholder, then on componentDidMount
you would tell the store to getUserAvatar. The function would then do this async, set something inside it's internal state for on an @observeable, to which you would then bind that into the component using observer. I see that the code is referenced in the store again in a helper function, but that should really not be public, or should be libbed as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense. It used to be an async function which is why it was there (had to make a call to the database to get urls), but then I realised all the database method was actually doing was reformatting a string in a pretty obvious way. Will change.
@@ -1,13 +1,14 @@ | |||
export interface IDatabaseMapPin { | |||
id: string | |||
import { IDbDoc } from './common.models' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. I saw this on the other models but didn't understand it, so thought I'd leave this part to you anyway 👍
@@ -51,7 +51,7 @@ class Controls extends React.Component<IProps> { | |||
position: 'absolute', | |||
left: '10%', | |||
borderRadius: '5px', | |||
zIndex: 99999, | |||
zIndex: 2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
haha, good hack fix is good
@@ -79,6 +79,15 @@ class MapView extends React.Component<IProps, IState> { | |||
</Map> | |||
) | |||
} | |||
|
|||
static defaultProps: Partial<IProps> = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I get what you mean on slack now. Yes, for all intents and purposes, the click binders are actually not "required". It is required for a functioning application, but it's not required in the grand scheme of things. In theory this(ese) components could be a bit more generic, and could thus be thrown into the components folder. Specifically the map view stuff. Hrrmm, food for thought another day though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, my main reason was so I could include it in the profile module without having to write a click handler (was feeling lazy), but I think it also does give that option of making even more standalone. But yeah, later days.
if (this.pinData.length === 0 || this.availablePinFilters.length === 0) { | ||
return [] | ||
@action | ||
private processDBMapPins(pins: IDatabaseMapPin[]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. This makes a bit more sense since the @computed
stuff was borked. Is the @action
part required for mobx? i.e. if you don't have the @action
, does it not trigger a reflow on after settign this.mapPins? (if it's not needed, just thinking to remove it etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I'm aware @action
is really just syntactic sugar, and any changes to this.myobservable
will get tracked regardless (what the @observable
decorator is doing). It's considered good practice to put the decorator on anything that does directly modifiy an observable for a couple reasons, it helps with code readability and also as it adds debugging info if you have something like mobx-devtools
installed on your browser to view action logs and history. It also automatically wraps everything in a single transaction, so that if inner methods change the observable it will only emit a notification after all have processed - good for performance if you're doing that a lot but still I think it's better to just avoid modifying observables unnecessarily.
The @computed
would be a much better method if a) it worked, and b) we wanted to trigger on changes to multiple fields (e.g. map pins data as well as list of map filters), but for now is fine and if we do decide to hook up to the db for filters and not just use hardcoded we can just add a manual call after that data is received also.
Database.getLargeCollection<IDatabaseMapPin>(this.mapEndpoint).subscribe( | ||
data => { | ||
console.log('setting pin data', data) | ||
this.processDBMapPins(data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to throw this function also into retrievePinFilters. The action to set mapPins requires two separate call outs, and thus it could be triggered from either one of them. It makes me think though, that maybe it should just be one @action
, with a call to both sub functions, and then on a Promise.all to set the mapPins. I could put together a PR for that if you want to see what I'm talking about?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get what you're saying, definitely nice to avoid repeatedly processing the same data. Although for now I'm happy to keep the filters hardcoded as I really can't imagine them changing very much, but would be a nice feature if you wanted to implement for v2
.
The other option would be to simply queue up the operations, as the database filters could be retrieved with a single call (I think there is a Database.getCollection(collectionName,'once')
or similar method which resolves a promise instead of an observable. We could have an initialisation that awaits getting filters and then subscribes to data changes.
The other thing I should add to a future to-do
list would probably be to highlight a list of what data has changed when the observable emits pins, so potentially that could just be streamed in or updated where required, which I think would also help here...
if (!this.pinDetailCache.has(id)) { | ||
// TODO: get from database | ||
this.pinDetailCache.set(id, generatePinDetails(pin)) | ||
public async getPinDetails(pin: IMapPin): Promise<IMapPinDetail> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function shouldn't actually return anything since it should be setting this.pinDetail. I would see this a bit more as follows:
public async getPinDetails(pin: IMapPin) {
if (!this.pinDetailCache.has(pin._id)) {
const pinDetail = await this.getUserProfilePin(pin._id)
this.pinDetailCache.set(pin._id, { ...pin, ...pinDetail })
}
this.pinDetail = this.pinDetail = this.pinDetailCache.get(pin._id)
}
Basically, if cache miss, fill cache, then set value. If no cache miss, set value. I've just done a find on the getPinDetails function and can't see the return being used anywhere, thus the reason 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, yeah I had originally put it there whilst testing on the user profile settings page, but if we want the user to be able to click on their map pin there also it should be linked fully to the current implementation instead of passing and calling itself
private async loadUserPin() { | ||
const userPin = await this.injected.mapsStore.getPin(this.user.userName) | ||
this.setState({ userPin }) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as first part. I would either make a observeable value in the store, or put the function from this into a lib.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I really hated this pattern. sounds like a good solution, will implement
templateStore: new TemplateStore(), | ||
tagsStore: new TagsStore(), | ||
platformStore: new PlatformStore(), | ||
eventStore: new EventStore(), | ||
discussionsStore: new DiscussionsStore(), | ||
mapsStore: new MapsStore(), | ||
mapsStore: new MapsStore(userStore), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you're doing here, but I think the flow of those functions needs to be changed a bit. It feels more like the user store would want the map store, but i'm not sure if that makes sense either. i.e. a mapPin is part of a user, where as a user is part of a map doesn't make the most sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
strongly agreed. I think one of the common workarounds is to have a shared/root store which can allow some sharing across stores, but I think for the current use case your suggestions sound like a better option. Will rethink and get back to you when something's done.
Starting to work back on this although getting a bit side-tracked on general db things which would be better in another branch. Will close here for now, do some cherry-picking and re-open once better organised and (hopefully) ready for merge |
Main Changes:
id
field with_id
(populated by db)Todo