-
Notifications
You must be signed in to change notification settings - Fork 0
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
Rough match / chat #31
Conversation
benjaminben
commented
Oct 27, 2017
So yea this is the least dry shit ever, my first thought was maybe using the same RecsView and RecInfoView components with flags for alternative rendering in this context, but then the whole writing code.thats easier to delete and all.... just seemed quicker to copy pasta for now at least. That led to having this "MatchView" thing that oversees both chat and the match's profile - trying to anticipate tighter integration between profiles and chat, e.g being able to comment on a specific part of a profile and have that comment immediately reflected in the chat. I also really wanted to have the profile blurred behind the chat bc I think it might drive engagement? Sometimes I'm.a goldfish and forget who I'm talking or why I gaf in the first place 😶 anyway that's why there's only one |
Ok so there's two fairly meaty refactors we'll want to do to clean things up before merging, because the responsibilities of the components are a little muddled and are going to make this part of the codebase harder to revisit. First off, we should extract Secondly, currently the responsibilities for interpreting the state of
In terms of the separation of concerns, ideally all the responsibilities for interpreting
In order to support this, we'll need to make
We can accomplish the first two just by using In order to be aware of the transition, we can add an We'll also need to add some extra markup to achieve what we want:
Fortunately there is a good blueprint for this in the Set next and previous scene, initiate animated transition if there is one: https://github.com/superseriouscompany/eggpeg/blob/master/app/components/Stage.js#L37 Containing view for current scene https://github.com/superseriouscompany/eggpeg/blob/master/app/components/Stage.js#L97 Containing view for previous scene https://github.com/superseriouscompany/eggpeg/blob/master/app/components/Stage.js#L102 Helper method to avoid duplicating the same switch statement twice https://github.com/superseriouscompany/eggpeg/blob/master/app/components/Stage.js#L138 |
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.
See the main comment and the compositional refactor in these comments
this.state.topValue, | ||
{ | ||
toValue: headerHeight, | ||
duration: 333, |
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.
discussion: one thing I try to do with animation timings is have them all in the same file. see https://github.com/superseriouscompany/eggpeg/blob/master/app/config.js for an example (I would actually make it its own object). That way when you want to change animation timings you can do it all from the same place and verify that overall the differences in speeds make sense at a glance.
} | ||
} | ||
|
||
componentWillUpdate(nextProps, nextState) { |
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.
discussion: I haven't quite figured out where animation stuff should go. on the one hand, it feels like a view concern, on the other hand it requires maintaining state which ideally a view would not require. I've done it where the animation stuff is handled by the container and the value is passed in as a prop, but I'm not totally happy with that easier since in the container sometimes you're thinking about api calls and data and other times you're thinking about animations and the frontend.
after talking it through I think we should leave animation as the responsibility of the view component and just be ok with not having pure functional components when we need animations
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.
actualllly I think my uneasiness with this is part of a bigger issue with the way responsibilities are distributed in the system. will leave a long note about relying on Stage
for the transition
app/components/ChatView.js
Outdated
)} | ||
/> | ||
const {width, height} = Dimensions.get('window') | ||
const headerHeight = 63 |
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 should be exported from somewhere common like app/styles/dimensions.js
but since you're getting rid of the header, you can ignore for now
@@ -9,7 +9,21 @@ import { | |||
} from 'react-native' | |||
|
|||
export default function(props) { | |||
return ( | |||
return (props.scene.name === 'Match' ? |
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.
we talked about how this should stay more consistent but since you're changing the header and navigation a bunch, don't worry about changing it here.
app/components/MatchInfoView.js
Outdated
: | ||
null | ||
} | ||
<LinearGradient colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.6)']} |
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 is the lack of dry
ness you were referring to (the wetness
, if you will)?
It's a good instinct to think that this can be cleaner and I'm really glad that you just copy-pasta'd it and threw it up so that we could discuss.
Inheritance is a bit of an anti-pattern with React. It seems like in this case you would want to use composition. In general, I prefer composition over inheritance https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
In this case, the shared code is between RecInfoView
and MatchInfoView
, right? And the only difference between the two is that RecInfoView
has like and pass buttons?
What I would do is a two step refactor:
First:
- Extract
<LinearGradient
and all of its children from 'components/RecInfoView.js' to a new functional viewcomponents/ProfileView.js
- Replace all instances of
props.recs[props.index]
in the new file withprops.user
- Use
<ProfileView user={props.recs[props.index]} />
inRecsInfoView
- Add a switch for whether to show the buttons or not, like
{ !props.hideButtons ? ... : null }
- Use
<ProfileView user={props.user} hideButtons={true} />
inMatchInfoView
Second:
If possible, put the buttons outside of the profile view in RecsInfoView
. If it's annoying to do it that way, just leave as is.
An example of an extracted, configurable component we used in multiple places in eggpeg is here: https://github.com/superseriouscompany/eggpeg/search?utf8=%E2%9C%93&q=RainbowBar&type=
app/components/MatchInfoView.js
Outdated
|
||
export default MatchInfoView | ||
|
||
const style = { |
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.
should always use StyleSheet.create
when possible https://stackoverflow.com/questions/38958888/react-native-what-is-the-benefit-of-using-stylesheet-vs-a-plain-object
@@ -32,6 +32,27 @@ function mapDispatchToProps(dispatch) { | |||
|
|||
showRecs: function() { | |||
dispatch({type: 'scene:change', scene: 'Recs'}) | |||
}, | |||
|
|||
showMatch: function(user) { |
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.
why do these take a user
argument?
goBack: function(user) { | ||
let destination = ownProps.view === 'Profile' ? | ||
{name: 'Match', view: 'Chat'} : | ||
'Matches' |
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 should be an object with a name
key
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 is? if the ternary resolves true, otherwise letting the name
key be assigned by the store logic?
|
||
onSend(messages = []) { | ||
console.warn(messages[0]) | ||
api(`/matches/${this.props.userId}/messages`, { |
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.
confused as to why we're hitting the API twice for messages? shouldn't this only be in Chat
I see, you're using ChatView
...why not import ./Chat
? There's no rule about having only one container per Scene.
app/containers/Stage.js
Outdated
: props.scene == 'Chat' ? | ||
<Chat userId={props.params.userId} myId={'HJyOmP2qW'}/> | ||
: props.scene == 'Match' ? | ||
<Match userId={props.params.userId} myId={'HyeNFOS6W'}/> |
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.
lewlz, ya after you add the user store we can get this from mapStateToProps
Oh forgot I made the store smart enough to check for a string. Carry on
On Wed, Nov 1, 2017 at 2:54 AM Ben ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In app/containers/Header.js
<#31 (comment)>
:
> + },
+
+ showMatch: function(user) {
+ dispatch({
+ type: 'scene:change',
+ scene: {
+ name: 'Match',
+ view: 'Profile',
+ }
+ })
+ },
+
+ goBack: function(user) {
+ let destination = ownProps.view === 'Profile' ?
+ {name: 'Match', view: 'Chat'} :
+ 'Matches'
it is? if the ternary resolves true, otherwise letting the name key be
assigned by the store logic?
—
You are receiving this because your review was requested.
Reply to this email directly, view it on GitHub
<#31 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAFEpZSiRBANjz6JqAidrI0FIq8SQXUeks5sx17OgaJpZM4QIbPy>
.
--
Neil
|
@@ -0,0 +1,5 @@ | |||
import { Dimensions } from 'react-native' |
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.
sorry, I meant headerHeight
, not sure how much benefit we'd get from something like this
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.
but since it's in a bundled commit, don't worry about reverting it manually
app/components/ProfileView.js
Outdated
{ | ||
props.hideButtons ? null : | ||
<View style={[style.tray]}> | ||
<TouchableOpacity style={[style.bubble]} onPress={() => props.pass(props.recs[props.index].id)}> |
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 should also use props.user
app/components/ProfileView.js
Outdated
resizeMode='contain' | ||
source={require('../images/x.png')} /> | ||
</TouchableOpacity> | ||
<TouchableOpacity style={[style.bubble]} onPress={() => props.like(props.recs[props.index].id)}> |
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.
ditto
app/components/RecInfoView.js
Outdated
: | ||
null | ||
} | ||
<ProfileView {...props} {...state} user={props.recs[props.index]} /> |
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 totally get the appeal of passing all props and state as props to the ProfileView, but this can be hard to follow for a future dev who wants to re-use the component and wants to see what props she needs to provide to the component. You can either pick from the state explicitly eg like={props.like} topValue={state.topValue} heightValue={state.heightValue}
or you can define PropTypes
in the component itself (at the top of the file) so that she can look at it and see quickly what is required without parsing the code of the component jsx-eslint/eslint-plugin-react#237 (comment)
coo so I'm gonna use the "requested changes" vs "commented" and we'll see if that works for showing which things are optional and which are blockers. eg this last review was optional stuff and nice-to-haves |
So my thing is, I really don't like assigning state values from props bc it's led to buggy behavior in the past. I feel like it doesn't really make sense to keep track of the scene (and fuss with updating it) in both Stage.js and in redux. Could you give a more specific example of a type of transition that would be incompatible with this setup? Are you saying that |
Right so there are two scenes and two views. One scene is the one we were on before, let’s call that previousScene. The other scene is the one we’re on now (slash transitioning to) let’s call that the current scene. One of the views is animated, let’s call it dynamic. The other is fixed, let’s call it static. For a fade in, we put the current scene in the static view and the previous scene in the dynamic view. Once the animation is finished, we remove the previous scene. If we wanted a drop in (eg like when the leaderboard in egg peg drops down from the top), we’d put the current scene in the dynamic view and the previous scene in the static view. Then when the animation was finished, we’d put the current scene in the static view and remove the previous scene. In order to achieve that using the store, the store would need to be aware of the implementation details of the view, which feels like a muddled separation of concerns. We could possibly make it clearer by making both views dynamic and using z-index, but my spidey sense tells me it might be less performant to always have the current scene in an Animated.View, I could be wrong tho. |
So actually my main thing is that the caller should never have to know about the implementation details of the animation. As long as the api from a dispatch point of view is just supplying a name and transition name (and not specifying which scene goes in which container), I’d be cool with pushing the responsibilities of storing state transitions into redux (although I still feel it’s more of a view concern than a data concern) but I don’t have as strong an opinion on using shared state vs component state as I do on the caller not needing to remember implementation details of the transitions. |
The caller (the store right?) doesn't know anything about the nature of the
animations beyond their existence or not... I kinda see it like Stage.js is
the production crew who handles set changes between scenes. So Stage just
receives a heads up from the store, who acts as the runner/coordinator for
the director (which I guess is the user's intention)? The store tells stage
to `fadeIn`, but doesn't include technical deets on what fading in even
means or how to do it.
Also I'm not sure the `previousScene` is the one we'll always wanna have in
the `Animated.View`, I can think of transitions (including swiping /
sliding) where we'd want to animate both the previous and current scene
over the course of the transition, likely simultaneously.
Another approach I'm partial to is letting each scene handle its own
entrance/exit animations, that's typically been my approach in and outside
of react. That way the caller is basically just cueing actors who've
memorized their marks, and the `stage` is just a stage.
|
The caller would be whatever component you’re dispatching scene:change from. That analogy actually makes an argument for doing the next/previous scene juggling in the component’s state, not the store. If the store sends one direction (change to scene “matches” with transition “fadein”, for eg) then the production crew should be responsible for handling switching the scenes and disposing of the previous scene, instead of the current setup where the production crew switches the scenes and then tells the director “ok now tell me to remove the previous scene”) Point taken on using two animated views, it’ll be more flexible for swipe transitions etc. Regarding distributing responsibility for displaying the transition to each container, I see some tradeoffs there. On the plus side, Stage.js becomes extremely simple again (probably reverts to pretty much what it was). On the minus side, adding a new scene with the same transition as an existing scene is harder — you have to remember what the scene was that had that transition, and then copy pasta the same code over to the new container. Another minus is that removing the previous scene becomes harder. Would each scene container have a conditional that says “if the current scene is not this scene and the animation is finished, render null)? The last minus is that you no longer have the benefit of being able to read the file you’re in (Stage.js) and having a full menu of transitions that have already been implemented to reuse or tweak. I feel like the benefit of keeping Stage.js dumber is actually not that great. Since it exists and has no other responsibilities, it doesn’t hurt too much to give it the responsibility of handling transitions. Plus it provides a nice chokepoint were we to want to implement any global behavior on scene transitions (e.g. sending an analytics call that the scene has changed) |
Oh the last thing is that if a scene can be reached through multiple transitions (eg recs can fade in after login or swipe in from matches) that’s 0 extra lines in Stage.js and a bunch of extra lines in Recs.js. If you feel strongly that there are benefits I’m not thinking of let me know. eggpeg and all my previous apps had each scene responsible for handling its own transition, then one day I refactored to use Stage.js and as you can see I quite like it. But my pride about how sweet this abstraction is shouldn’t stand in the way of a system that’s objectively better. |
Okay I see the advantage of putting all the major transitions in Stage.js, however I'm not sure how to implement in a case like the previously attached .gif wherein the |
If we keep every scene rendered / in memory all the time then I guess it would eliminate some concerns while birthing new ones |
It doesn’t seem like we need both scenes to be rendered at all times. Frontend performance is all about maintaining an illusion while loading resources as lazily as possible. So in this case, here’s what we need to maintain the illusion:
We can maintain this illusion by providing a transition in Stage.js that uses a fullscreen photo in between the transition. |
Oops hit the wrong button it’s 3:30am |
eg you would add the photo (with the mask over it) to chat.js, then your dispatch calls would look like { type: ‘scene:change’, scene: { name: ‘deets’, transition: ‘slideup’, backgroundImage: ‘null.png’ } } Don’t know if deets/slideup are accurate but hopefully u see what im saying |
Also it is possible that you’ll see a flicker if react native hasn’t put image caching and reuse in its core yet but dwai there’s a package that will make it so that the image is reused all three times it’s loaded (once in Chat, once in Deets, once in Stage as the background layer) And you’ll probably have to set some prop on Chat/Deets either via mapStateToProps or delegated from Stage so that those scenes know not to render the image in the background until the transition is done |
Also if this is all too much to deal with we can punt on it and implement the illusion later. It’s probably kind of a lot with everything else this week |
It just seems like so much extra - the profile is already using a |
This PR is getting p deep without a merge and I'm wondering if I should branch off of my the |
Ya that’s a good point about needing to know which index of the photo they were on, didn’t realize it did that. It’s fine for now, let’s punt |
Is it stable now? We can merge it if it is |
I ask bc the last commit had “wip” As soon as it’s stable u can hit that merge button. I’m gonna go to bed |
yea i mean everything functions as is, i'll remove the testing |