first of all congratulation with alpha apollo… i can’t believe you make it alpha so fast… this is so great…
something wondering me though… why redux…?? why not relay…??
i don’t really know redux though, when i started to learn react, people was debating redux with relay… not sure what they were debating but the point i got is that most people prefer relay, so should i… that’s the reason i learn react with graphql and relay… but relay is truly fantastic…
for example with relay we don’t need to deal so much with flux store state mounting unmounting and event handlers related stuff… a lot react api would be useless because relay take care of them… i feel react with relay is as easy as blaze with tracker reactivevar session and mongo/minimongo but with more power and weapon…
for example here i made simple bear app with react relay and graphql…
without relay…
import API from "../API";
import BearHouse from "../BearHouse";
let _getBearAppState = () => {
return {bears: BearHouse.getAll()};
}
class Karina extends React.Component{
static propTypes = {limit: React.PropTypes.number}
static defaultProps = {limit: 5}
state = _getBearAppState();
constructor(props){
super(props);
this.state = _getBearAppState();
this.onChange = this.onChange.bind(this);
}
onChange(){
this.setState(_getBearAppState());
}
componentDidMount(){
API.fetchLinks();
BearHouse.on("change", this.onChange);
}
componentWillUnmount(){
BearHouse.removeListener("change", this.onChange);
}
var h = React.createElement;
render(){
let getBear = this.state.slice(0, this.props.limit).bears.map(bear => {
return
h('li', {key: bear._id},
h('strong', null, bear.name),
h('a', {href: bear.url},
h('img', {src: bear.picture})
)
);
});
return h('div', null,
h('h3', null, "I Want My Bear!!"),
h('ul', {href: bear.url},
h(getBear)
)
);
}
}
with relay…
class Karina extends React.Component{
static propTypes = {limit: React.PropTypes.number}
static defaultProps = {limit: 5}
var h = React.createElement;
render(){
let getBear = this.props.bearHouse.slice(0, this.props.limit).bears.map(bear => {
return h('li', {key: bear._id},
h('strong', null, bear.name),
h('a', {href: bear.url},
h('img', {src: bear.picture})
)
);
});
return h('div', null,
h('h3', null, "I Want My Bear!!"),
h('ul', {href: bear.url},
h(getBear)
)
);
}
}
# after that,,, we only need to define data requirement then relay and graphql do the rest...
# Relay.createContainer is similar to onCreated in blaze template and createContainer in meteor react
# which will guarantee to supply data before rendering begin...
Karina = Relay.createContainer(Karina, {
fragments: {
bearHouse: () => Relay.QL`
fragment on Bear {
bears {
_id,
name,
url,
picture
}
}
`
}
});
relay also come with routing stuff with similar usage like flow router params query params, if u previously use flow router then routing with relay would be dead easy…
# route for single bear...
class BearRoute extends Relay.Route {
static routeName = 'HouseOfBear';
static queries = {
bearHouse: (Component) => Relay.QL`
query KarinaQuery {
bearHouse: {${Component.getFragment('bearHouse')}}
// 'bearHouse' is a fragment that we defined above.. look look... it's similar with flow router query params...!!
// getFragment of course like it's name it is relay function to call fragment...
}
`
}
}
ReactDOM.render(Relay.RootContainer, {Component:Karina route:new BearRoute()}, document.getElementById('myBigBear'));
relay makes pagination easier…very easy in offset/limit with after/first rule data management… for example if i have bearsy berry bearny bernard bearnea and berthold (they’re all my bear if u ask) and i limit into first 3… if user delete bearsy then second fetching will be bernard bearnea and berthold (correct) instead of bearny bernard and bearnea (wrong)… also with edges node connection we have pageInfo providing many usefull connection info, for example it provide hasNextPages
… you can use for something like this…
if (hasNextPages) {
# do loading state for infinite scroll and then query new bear list without refetch what we already have in cleint
}
no need publish count or whatsoever…
also optimistic mutation and optimistic update… for example…
# pagination with 5 bears for initial limit,,, find bear,,
# and adding new bear with optimistic mutation and optimistic update..
class Karina extends React.Component{
constructor(props) {
super(props);
this.find = _.debounce(this.find, 300);
}
find = (e) => {
let query = e.target.value;
this.props.relay.setVariables({ query });
}
pageLimit = (e) => {
let newLimit = Number(e.target.value);
this.props.relay.setVariables({limit: newLimit});
}
insert = (e) => {
e.preventDefault();
Relay.Store.update(
new InsertBearMutation({ // mutationWithClientMutationId
name: this.refs.newName.value,
url: this.refs.newUrl.value,
picture: this.refs.newPicture.value,
bearHouse: this.props.bearHouse // data reactivity with relay
})
);
this.refs.newName.value = "";
this.refs.newUrl.value = "";
this.refs.newPicture.value = "";
}
saving = () => {
let {bear, relay} = this.props;
if (relay.hasOptimisticUpdate(bear)) {
return "Storing your cute bear to House of Bear..."; // very easy to do client to server optimistic update..!! i can't find something like this in meteor world
}
}
var h = React.createElement;
render(){
let getBear = this.props.bearHouse.bearConnection.edges.map(edge => {
return h(Bear, {key:edge.node.id, bear:edge.node}); // tracking single bear state changing similar like tracker
});
return h('div', null,
h('h3', null, "Submit Your Cute Bear..."),
h('form', {onSubmit:this.insert},
h('input', {type:"text", placeholder:"Your Bear Name" ref:"newName"}),
h('input', {type:"text", placeholder:"Your Bear Url" ref:"newUrl"}),
h('input', {type:"text", placeholder:"Your Bear Picture" ref:"newPicture"}),
h('button', {type:"submit"}, "Submit")
),
h('p', null, this.saving()),
h('h3', null, "Show My Bear..."),
h('p', null, "Showing: bears"),
h('select', {onChange:this.pageLimit, defaultValue:this.props.relay.variables.limit}, // data state change with relay
h('option', {value:5}, "5"),
h('option', {value:10}, "10")
),
h('input', {type:"text", placeholder:"Find Your Cute Bear" onChange:this.find}),
h('ul', null, h(getBear))
);
let getBear = this.props.bearHouse.bearConnection.edges.map(edge => {
return <Bear key={edge.node.id} bear={edge.node} />;
});
}
}
Karina = Relay.createContainer(Karina, {
initialVariables: {
limit:5, // setup limit similar to ReactiveVar self.limit.set(5) put on onCreated template..
query: ''
},
fragments: {
bearHouse: () => Relay.QL`
fragment on Bear {
id,
bearConnection(first: $limit, query: $query) {
edges {
node {
id,
${Bear.getFragment('bear')}
}
}
}
}
`
}
});
the other powerful features that come with relay out of the box:
- relay uses batching request that will drastically optimize server resources… hence scaling would be easy because it’s already efficient at the core…
- error handling for request if request somewhat fail…
- retry and rollback strategies when request failed…
- relay implement edges node connection and will fetch the difference between what we need and what we have in the client thus will bring huge efficiency…
- and data caching to reduce server round trips…
there are a lot of room that apollo or else could provide that will bring better value,
1. make it more easier…
for example… this is how i muatate bear…
/*schemas.js*/
let insertBearMutation = mutationWithClientMutationId({
name: 'InsertBear',
inputFields: {
name: { type: new GraphQLNonNull(GraphQLString) },
url: { type: new GraphQLNonNull(GraphQLString) },
picture: { type: new GraphQLNonNull(GraphQLString) }
},
outputFields: {
bearEdge: {
type: bearConnection.edgeType,
resolve: (obj) => ({ node: obj.ops[0], cursor: obj.insertedId })
},
bearHouse: {
type: bearType,
resolve: () => bearHouse
}
},
mutateAndGetPayload: ({title, url, picture}) => {
return db.collection("bears").insert({
title,
url,
picture,
createdAt: Date.now()
});
}
});
let schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: () => ({
node: nodeDefs.nodeField,
bearHouse: {
type: bearType,
resolve: () => bearHouse
}
})
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: () => ({
insertBear: insertBearMutation
})
})
});
/*InsertBearMutation.js*/
class InsertBearMutation extends Relay.Mutation {
getMutation() {
return Relay.QL`
mutation { insertBear }
`;
}
getVariables() {
return {
name: this.props.name,
url: this.props.url,
picture: this.props.picture
}
}
getFatQuery() {
return Relay.QL`
fragment on CreateLinkPayload {
bearEdge,
bearHouse { bearConnection }
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'bearHouse',
parentID: this.props.bearHouse.id,
connectionName: 'bearConnection',
edgeName: 'bearEdge',
rangeBehaviors: {
'': 'prepend',
},
}]
}
getOptimisticResponse() {
return {
bearEdge: {
node: {
title: this.props.title,
url: this.props.url,
picture: this.props.picture
}
}
}
}
}
understand…?? no…?? same goes with me… actually i don’t really understand… that’s why this would be great if i just write something like this…
Bears.insert({name:"Bearsy",url:"/bearsy",picture:"bearsy.jpg"})
2. scaling real time app…
what make meteor soo coool because it easy to create real time app… unfortunately i found a lot thread questioning scalability… and guess what, big enterprise is taking this point seriously… that’s why, although meteor gain a lot popularity this time, when i see meteor homepage site i don’t see big enterprise using meteor for now… i guess big enterprise still at the state of watching meteor stack rather than trying… but this state is great, i guess they know scaling real time app is really really hard… all you need to do is to break the pain point…
graphql-relay have good architecture and in theory this will scale app… as for now graphql-relay is not real time… why don’t mdg take it as a starting point and combining with real time meteor to create a scalable real time app stack…?? big enterprise will start trying i guess…
3. no clear guide…
when you play with graphql-relay for now, you should deal with very hard to understand documentation… little useful tutorial… little useful q&a… no clear where to start and how to start… something like meteor guide would be awesome…!!
4. setup is really really hard…
it takes me a week to figure out how to setup react-graphql-relay… you should deal with webpack react setup db setup babel relay plugin graphql setup relay setup etc etc… i guess this is barrier for adoption… i miss something like…
meteor create iMissMyBear
# or maybe
apollo create iMissMyBear
after that just focus writing app without setup headache…
5. giving router to the core…
relay have router api inside it, though it’s not so great like react router… then make it great based on current router api and put to the core… then this will make apollo un-opinionated as your users don’t need to choose which router should they use…
6. integrate with react-native…
providing a relay integration would be a good start if meteor/apollo would start giving first class support for react-native… why…?? because relay integrate with react-native out of the box…
i see facebook guy doing a lot pull request onto react native in order to get better and better integration with relay…
7. apollo-native: real time native mobile app creation with react-native…
since relay is integrated with react-native out of the box, the hard part is already taken care i guess, no more hacking i assume… now what missing…?? real time…?? yappp… isn’t that meteor positioning…?? if possible, would be great if mdg create apollo-native which giving real-time support for react-native, if that’s the case, i guess apollo-native would be the most amazing mobile app creation on the planet…!!!
also the need for mobile backend that support real time thing would be huge i guess… parse will shutdown in 2017… where the users should go…?? firebase…?? what if google shutdown firebase later with similar reason why facebook shutdown parse since they both play on the same league…?? how about galaxy which have concept BYOD (bring your own db)…?? i like BYOD… BYOD would be preferable especially for enterprise who have sensitive data and need ultimate privacy like financial service… financial service have their own hardware to store data… they can’t rent db for whatever reason…
and also easy setup is beautiful… something like this…
# for native mobile
apollo-native create iMissMyBear
8. apollo-native integration with meteor web app…
i don’t have production meteor web app, i just built app with meteor for my college project, but if i do, i don’t have idea how to integrate with react-native-relay with graphql server… for simple example, how do i authenticate current user with react-native-relay-graphql app that previously signed up with meteor web app…?? that’s simple thing… but a lot of things to do i guess… thus would be great if apollo-native could also support integration with meteor web-app without a lot code rewriting…
summary, if possible, would be great if apollo could also support relay integration… by leveraging relay advantage, apollo would possibly become the most beautiful app creation with the title: real-time x BYOD x scale x easy x mobile native… this is the dream of every developer i guess…
thank Youuuu…
@karina
whaaa i can mention myself… this is so great…