-
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
March fun react #1
base: march_fun_base
Are you sure you want to change the base?
Changes from all commits
5373875
7cc9614
a7c00fe
482789b
319d7cb
b524800
54888bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
{ | ||
"auth": { | ||
"username": "scratch" | ||
"username": "ralphdev" | ||
}, | ||
"sObjects": ["Todo__c"], | ||
"sObjects": ["Opportunity"], | ||
"outPath": "./src/generated/sobs.ts" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<apex:page showHeader="true" sidebar="false"> | ||
<script type="text/javascript"> | ||
//rest details | ||
const __ACCESSTOKEN__ = '{!$Api.Session_ID}'; | ||
const __RESTHOST__ = ''; | ||
</script> | ||
<apex:stylesheet value="https://npmcdn.com/antd/dist/antd.css"/> | ||
|
||
<div id="root"></div> | ||
<apex:outputPanel layout="none" rendered="{!$CurrentPage.parameters.local != '1'}"> | ||
<script type='text/javascript' src="{!URLFOR($Resource.MarchFun, 'app.js')}" /> | ||
<script type='application/json' src="{!URLFOR($Resource.MarchFun, 'app.js.map')}" /> | ||
</apex:outputPanel> | ||
|
||
<apex:outputPanel layout="none" rendered="{!$CurrentPage.parameters.local == '1'}"> | ||
<script src="http://localhost:8080/app.js" /> | ||
</apex:outputPanel> | ||
</apex:page> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexPage xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>38.0</apiVersion> | ||
<availableInTouch>false</availableInTouch> | ||
<confirmationTokenRequired>false</confirmationTokenRequired> | ||
<label>MarchFun</label> | ||
<packageVersions> | ||
<majorNumber>1</majorNumber> | ||
<minorNumber>7</minorNumber> | ||
<namespace>sf_com_apps</namespace> | ||
</packageVersions> | ||
</ApexPage> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<cacheControl>Public</cacheControl> | ||
<contentType>application/zip</contentType> | ||
<description>ReactApp</description> | ||
</StaticResource> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// tslint:disable-next-line:no-implicit-dependencies | ||
import { Opportunity } from "@src/generated/sobs"; | ||
import { Affix, Button, Col, Icon, Layout, Row, Spin } from "antd"; | ||
import * as React from "react"; | ||
|
||
interface Props { | ||
id: string; | ||
} | ||
|
||
interface State { | ||
isOpptyLoading: boolean; | ||
isTouchSaving: boolean; | ||
oppName: string; | ||
touchCount: number; | ||
} | ||
|
||
const loadingIndicator = ( | ||
<Affix offsetTop={300}> | ||
<Row type="flex" justify="center"> | ||
<Col span={1}> | ||
<Icon spin={true} style={{ fontSize: 56 }} type="loading" /> | ||
</Col> | ||
</Row> | ||
</Affix> | ||
); | ||
|
||
export class App extends React.Component<Props, State> { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
oppName: "Loading", | ||
isOpptyLoading: true, | ||
isTouchSaving: false, | ||
touchCount: 0, | ||
}; | ||
this.touchOppty = this.touchOppty.bind(this); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ralphcallaway I'm sure you learned this in the react training, but you can use
to
then you don't have to bind to this in the constructor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ChuckJonas so i'm a bit confused, I thought that wasn't advisable since it would lead to the event handler function being re-initialized everytime the component was rendered ... came to it with this link https://stackoverflow.com/questions/36677733/why-shouldnt-jsx-props-use-arrow-functions-or-bind in your tslint.json file does that not apply if I do thinks like you're describing? |
||
} | ||
|
||
public async touchOppty() { | ||
const newCount = this.state.touchCount + 1; | ||
this.setState({ isTouchSaving: true }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Heads up: I doubt it would ever cause an issue here, because the latency behind the HTTP Callout & and the fact that there isn't really any impact to setting
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. woah, okay, did not realize that. do you know if it's safe to assume that set state changes are enqueued in order? i.e. in this case the hypothetical race condition would be, enqueue state isTouchSaving: true, make api call, enqueue state isTouchSaving: false. as long as the first state change always come through first seems like we'd be good in this scenario. in other words, as long downstream code isn't reading the state in a way that relies on the initially step being completed we could stay blissfully ignorant of this ... |
||
const oppty = new Opportunity(); // ts-lint likes const, but we're still mutating? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It recommends
IF you did want immutability, you have a couple options. You could use the
However, as you can see, it only makes the object first class properties immutable (typescript 2.8condition types now support In order to provide immutable piece of mind,
If I use an SObject directly in my reducer I make sure it's typed to this "Fields" interface so I don't accidentally mutate. Then, if you need to mutate and save, you can simply pass it back into the constructor of the corresponding class:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that makes sense, thanks for the detailed explanation, that really clarified things, const just means same reference, if you need read only objects you've got some options (i also saw Object.freeze as something that could be used) |
||
oppty.id = this.props.id; | ||
oppty.touches = newCount; | ||
try { | ||
await oppty.update(true); | ||
this.setState({ | ||
isTouchSaving: false, | ||
touchCount: oppty.touches, | ||
}); | ||
} catch (err) { | ||
alert(`Error touching oppty: ${err}`); | ||
} | ||
} | ||
|
||
public async componentDidMount() { | ||
try { | ||
const oppRetrieve = await Opportunity.retrieve( | ||
`SELECT Name, Touches__c FROM Opportunity WHERE Id = '${this.props.id}'`); | ||
if (oppRetrieve.length > 0) { | ||
this.setState({ | ||
isOpptyLoading: false, | ||
touchCount: oppRetrieve[0].touches, | ||
oppName: oppRetrieve[0].name, | ||
}); | ||
} else { | ||
this.setState({ | ||
isOpptyLoading: false, | ||
oppName: `Couldn't find opportunity with id ${this.props.id}`, | ||
}); | ||
} | ||
} catch (err) { | ||
this.setState({ | ||
isOpptyLoading: false, | ||
oppName: `Error loading opportunity "${err}"`, | ||
}); | ||
} | ||
} | ||
|
||
public render() { | ||
return ( | ||
<Layout> | ||
<Spin indicator={loadingIndicator} spinning={this.state.isOpptyLoading}> | ||
<Layout.Header> | ||
<h1>Opportunity Toucher: "{this.state.oppName}"</h1> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Best Practices would want you to break these out into stateless components. The rule of thumb is:
In general, I think this is a very important rule to help separate presentation logic away from business logic, but in this simple case would definitely be overkill IMO. We also have a skill challenge for just this (however, it's not great IMO) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. awesome, that rule of thumb is great, makes applying it really easy. |
||
</Layout.Header> | ||
<Layout.Content> | ||
<h1 style={{marginLeft: "50px", marginTop: "0.67em"}}> | ||
Touches: {this.state.touchCount} | ||
</h1> | ||
</Layout.Content> | ||
<Layout.Footer> | ||
<Button | ||
type="primary" | ||
onClick={this.touchOppty} | ||
loading={this.state.isTouchSaving} | ||
> | ||
Touch Opportunity | ||
</Button> | ||
</Layout.Footer> | ||
</Spin> | ||
</Layout> | ||
); | ||
} | ||
} |
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.
you could just store the
OpportunityFeilds
directly on the state and then when you need to update state, use ES6 spread syntax. to do so without mutating: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.
so much cool syntax
in your experience has storing the full sobject (the immutable fields instance) worked out better than storing just the data points you need? seems like it would be less to manage, but could invite extra functions you didn't intend (or maybe that's a positive since you don't need to adjust your state logic every time you want to start referring to a new field on a page)
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.
@ralphcallaway that's a great question.
There's definitely a trade off to each. It's MUCH easier and efficient (productivity wise) to just store the entire object. When you are rapidly prototyping, it makes things come together really fast.
However, it violates Idiomatic redux for two reasons:
1: the SObjects returned by ts-force are classes and have functions, which apparently can have negative impacts on performance (you are only suppose to store POJOS).
2: ts-force allows you to pull dependency trees down and they are all stored on the object. This violates the best practice of normalizing your reducers. You could easily use a library like normalizr to solve this issue
My approach has been to follow this simple rule:
The only downside has been that my state size has gotten really big, due to duplicate relationship data being pulled down. For example, I'm storing treatments on every Spotlight Product. There might be 5000 products in my store, each with object data, but there may only be 15 distinct treatments across all of them.
I don't think this has a big impact on rendering performance, but it does make things like trying to store state in Saleforce field harder (I currently need 10 long text fields to be safe!).