Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Migrate create_comment.jsx to be pure and use Redux #243

Merged

Conversation

QuantumKing
Copy link
Contributor

@QuantumKing QuantumKing commented Nov 5, 2017

Summary

Migrate create_comment.jsx to be pure and use Redux

Ticket Link

mattermost/mattermost#7672

Checklist

  • Added or updated unit tests (required for all new features)
  • Touches critical sections of the codebase (auth, posting, etc.)

@jwilander jwilander added the 2: Dev Review Requires review by a core commiter label Nov 5, 2017
@jasonblais jasonblais added Work in Progress Not yet ready for review and removed 2: Dev Review Requires review by a core commiter labels Nov 6, 2017
Copy link
Member

@saturninoabril saturninoabril left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, looks good to me, thanks!


export function updateCommentDraft(rootId, draft) {
return (dispatch) => {
PostStore.storeCommentDraft(rootId, draft);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will work but PostStore is really just dispatching to the newly. So in interest in reducing usage of PostStore, can you perform the dispatch directly?


export function makeOnSubmit(channelId, rootId, latestPostId) {
return () => async (dispatch, getState) => {
const draft = PostStore.getCommentDraft(rootId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to comment above, maybe use a storage selector to get the draft? Or create a new selector for it. I'm 0/5 on how it's done, just want to reduce PostStore usage

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah I see there was a push recently with all this in it. I'll make use of it 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwilander removed PostStore usage

Copy link
Member

@jwilander jwilander left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, lgtm

@mattermost mattermost deleted a comment from mattermod Nov 13, 2017
Copy link
Member

@hmhealey hmhealey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good, but I have a couple concerns about the mapDispatchToProps and the usage of selectors. Also, scrollToTop might need to be renamed scrollToBottom

this.handleSubmit(e);
}
}

GlobalActions.emitLocalUserTypingEvent(this.props.channelId, this.props.rootId);
}

scrollToTop = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this method actually scrolls to the bottom, not the top

} else {
const {draft} = this.props;

const newMessage = (function getNewMessage() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's anything that needs to be changed here, but I don't know of any other places that we use an inline function in this way for control flow. This could also be done in the following way which might be simpler

if (draft.message === '') {
    newMessage = ...;
} else if (regex.test(draft.message)) {
    newMessage = ...;
} else {
    newMessage = ...;
}

return (draft) => updateCommentDraft(rootId, draft);
}

function mapDispatchToProps(dispatch, ownProps) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is mapDispatchToProps called on every store change like mapStateToProps? I've never tried it, but I think using an arrow function or these other functions like makeOnSubmit may cause the CreateComment component to re-render unnecessarily since the actions are changing constantly. If that's the case, we could just pass in the raw actions and have component pass the arguments in itself

Copy link
Contributor Author

@QuantumKing QuantumKing Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can cache them in makeMapDispatchToProps. Its a pattern I've seen elsewhere in the app.

export function makeGetCurrentUsersLatestPost(channelId, rootId) {
return createSelector(
getCurrentUserId,
(state) => state.entities.posts.postsInChannel[channelId] || [],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to just return state.entities.posts.postsInChannel[channelId] and then check if postIds is empty below. Right now, if that part of the state is null, it'll return a new empty array each time this is called, preventing any memoization. Not a likely case, but I think it would be better to be safe here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return createSelector(
getCurrentUserId,
(state) => state.entities.posts.postsInChannel[channelId] || [],
(state) => (id) => getPost(state, id),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to be returning a new function each time the selector is called, which will also prevent memoization.

It may be easier to just not use a selector here since the overhead and complexity of using a selector might not be worth it when it's unlikely the user will likely go to edit their post more than once. It may actually be slightly worse overall performance to use a selector since it's making a new function each time the channelId or rootId change when the user may not even use this hotkey.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hmhealey What would you like me to do here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can move it to the same place as makeOnEditLatestPost as a util function instead, since this is the only place its used. That way it wont be used in mapStateToProps.

Copy link
Contributor Author

@QuantumKing QuantumKing Nov 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll only do all this work when the user wants to edit their latest post.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might still be called whenever mapStateToProps is called for a new thread (ie a different root_id or channel_id) since that'll cause call makeOnEditLatestPost which'll call this in turn. That'll still create a new function here and blow away memoization, but we want that anyway

);
}

export function makeGetCommentDraft(rootId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit weird to me since it's not returning a selector here as I'd expect from a makeGetX function, but I assume that's because this is based off the storage helper functions. It's fine to keep as is though.

className="icon icon--emoji emoji-rhs "
dangerouslySetInnerHTML={
Object {
"__html": "<svg width='15px' height='15px' viewBox='0 0 15 15' version='1.1' xmlns='http:https://www.w3.org/2000/svg' xmlns:xlink='http:https://www.w3.org/1999/xlink'> <g stroke='none' stroke-width='1' fill='inherit' fill-rule='evenodd'> <g transform='translate(-1071.000000, -954.000000)' fill='inherit'> <g transform='translate(25.000000, 937.000000)'> <g transform='translate(1046.000000, 17.000000)'> <path d='M7.5,0.0852272727 C3.405,0.0852272727 0.0852272727,3.405 0.0852272727,7.5 C0.0852272727,11.595 3.405,14.9147727 7.5,14.9147727 C11.595,14.9147727 14.9147727,11.595 14.9147727,7.5 C14.9147727,3.405 11.595,0.0852272727 7.5,0.0852272727 Z M7.5,14.0663436 C3.87926951,14.0663436 0.933656417,11.1207305 0.933656417,7.5 C0.933656417,3.87926951 3.87926951,0.933656417 7.5,0.933656417 C11.1207305,0.933656417 14.0663436,3.87926951 14.0663436,7.5 C14.0663436,11.1207305 11.1207305,14.0663436 7.5,14.0663436 Z'></path> <path d='M11.7732955,8.95397727 C12.0119318,8.90488636 12.2159659,9.11778409 12.1684091,9.35676136 C11.8063636,11.1790909 9.85346591,12.5710227 7.49846591,12.5710227 C5.15096591,12.5710227 3.20284091,11.1877841 2.83193182,9.37397727 C2.78181818,9.129375 2.99267045,8.911875 3.23744318,8.96198864 C4.85369318,9.29232955 10.1786932,9.28142045 11.7732955,8.95397727 Z'></path> <ellipse cx='4.94318182' cy='5.50431818' rx='1' ry='1.06534091'></ellipse> <ellipse cx='10.0568182' cy='5.50431818' rx='1' ry='1.06534091'></ellipse> </g> </g> </g> </g> </svg>",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, those don't make these tests nice to look at. Good thing we don't manually write these tests.

@hmhealey hmhealey self-assigned this Nov 13, 2017
@hmhealey
Copy link
Member

Assigning myself so I remember to follow up. Also, Jenkins isn't building because there's merge conflicts

return (draft) => updateCommentDraft(rootId, draft);
}

function makeMapDispatchToProps() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually think we've done this elsewhere. We usually just pass in the action creators into the component, and the component calls it with whatever arguments it needs. This works though, and it shouldn't cause any unnecessary re-renders as far as I can tell.

@hmhealey hmhealey removed their assignment Nov 14, 2017
@hmhealey hmhealey removed the 2: Dev Review Requires review by a core commiter label Nov 14, 2017
@hmhealey
Copy link
Member

@QuantumKing Going to merge this now. Thanks for making those changes

@hmhealey hmhealey merged commit c6b7946 into mattermost:master Nov 14, 2017
@lindalumitchell lindalumitchell added the Tests/Not Needed Does not require new release tests label Nov 15, 2017
@esethna esethna added Changelog/Not Needed Does not require a changelog entry Docs/Not Needed Does not require documentation labels Dec 4, 2017
hmhealey pushed a commit that referenced this pull request Aug 28, 2020
* Update README usage to use try catchs

* Add promise example
hmhealey pushed a commit that referenced this pull request Mar 17, 2021
* Update README usage to use try catchs

* Add promise example
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Changelog/Not Needed Does not require a changelog entry Docs/Not Needed Does not require documentation Tests/Not Needed Does not require new release tests
Projects
None yet
7 participants