{"componentChunkName":"component---src-templates-page-tsx","path":"/page/8","result":{"pageContext":{"searchData":[{"id":"cjjg9ui0t303s010375m42b9w","title":"Turn a Browser Window into a Notepad with This One-Liner","slug":"/turn-a-browser-window-into-a-notepad-with-this-one-liner","avatar":"https://s3.amazonaws.com/contentkit/static/cjjg9ui0t303s010375m42b9w/OYIaJ1KK_400x400(1).png","date":"July 1, 2020"},{"id":"cjiy7xl9o17w701039gvl18a5","title":"Creating a Collaborative Editor with Draftjs for Fun","slug":"/draft-js-collaborative-editor","avatar":"https://s3.amazonaws.com/contentkit/static/cjiy7xl9o17w701039gvl18a5/draft-js.png","date":"June 28, 2020"},{"id":"cjeqgfsdsnmx90167n7j290kt","title":"How To Include SASS In Your React Project","slug":"/sass-react-webpack","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfsdsnmx90167n7j290kt/parcel-bundler.png","date":"May 1, 2020"},{"id":"cjiy4tseq0tnw0103bc5s7sxj","title":"How to Add a Loading Indicator to Material Ui's Component","slug":"/creating-a-material-ui-button-with-spinner-that-reflects-loading-state","avatar":"https://s3.amazonaws.com/contentkit/static/cjiy4tseq0tnw0103bc5s7sxj/material-ui.png","date":"January 28, 2020"},{"id":"cjkq4u7470ihr0157ad175b12","title":"Connecting to AWS Lambda via WebSockets","slug":"/connecting-to-aws-lambda-via-websockets","avatar":"https://s3.amazonaws.com/contentkit/static/cjkq4u7470ihr0157ad175b12/MQTT.js.png","date":"January 12, 2020"},{"id":"cjeqgfnhknnpe0199zbfwwkd8","title":"6 Really Cool APIs to Have Fun With","slug":"/cool-apis-to-have-fun-with","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfnhknnpe0199zbfwwkd8/scale-api.png","date":"January 1, 2020"},{"id":"cjeqgftvknnr30199dvw266qh","title":"Installing Node Canvas in AWS Lambda","slug":"/node-canvas-aws-lambda","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgftvknnr30199dvw266qh/aws-lambda.jpg","date":"December 31, 2019"},{"id":"cjn5fv8kl0pfj0124wwebufms","title":"GoLang Cheatsheet","slug":"/golang-cheatsheet","avatar":"https://s3.amazonaws.com/contentkit/static/cjn5fv8kl0pfj0124wwebufms/golang.png","date":"October 13, 2019"},{"id":"cjn15y8740liw0175u3s707eh","title":"Scheduled Jobs & Work Queues With Postgresql","slug":"/scheduled-jobs-work-queues-with-postgresql","avatar":"https://s3.amazonaws.com/contentkit/static/cjn15y8740liw0175u3s707eh/kxHkAenZ_400x400.jpg","date":"October 8, 2019"},{"id":"cjmym86fh0jy1013398nfzuqu","title":"Creating a Bot to Refill Parking Meters Using AWS Lambda","slug":"/creating-a-bot-to-refill-parking-meters-using-aws-lambda","avatar":"https://s3.amazonaws.com/contentkit/static/cjmym86fh0jy1013398nfzuqu/prwHMlRn_400x400.jpg","date":"October 7, 2019"},{"id":"cjmsaqt6l09le01046qeukpp9","title":"The USPS Tracking API: How To Track Packages","slug":"/usps-tracking-api","avatar":"https://s3.amazonaws.com/contentkit/static/cjmsaqt6l09le01046qeukpp9/usps.png","date":"October 3, 2019"},{"id":"cjeqgfuyenmy20167rycnmiql","title":"Stormpath vs Firebase - A Side-By-Side Comparison","slug":"/stormpath-vs-firebase-a-side-by-side-comparison","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfuyenmy20167rycnmiql/m3cEA33V_400x400.jpg","date":"September 2, 2019"},{"id":"cjeqgfv88nnrt01997wxd4rnl","title":"Sending emails with Firebase Cloud Functions","slug":"/firebase-functions-sending-emails","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfv88nnrt01997wxd4rnl/firebase.jpg","date":"September 2, 2019"},{"id":"cjeqgfqjknnq20199oe3zwi37","title":"Deploying To DigitalOcean From Travis","slug":"/deploying-to-digitalocean-travis","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfqjknnq20199oe3zwi37/digitalocean.jpeg","date":"August 25, 2019"},{"id":"cjkvpbr6p06z50181fg7xvsc3","title":"Circumventing AWS Lambda's Bundle Size Limit","slug":"/circumventing-aws-lambdas-bundle-size-limit","avatar":"https://s3.amazonaws.com/contentkit/static/cjkvpbr6p06z50181fg7xvsc3/b2lWK7c0_400x400.png","date":"August 15, 2019"},{"id":"cjiy45h7m0iba0111f5imt5em","title":"A Quick Way to List All Unicode Characters (Javascript)","slug":"/unicode-characters-javascript","avatar":"https://s3.amazonaws.com/contentkit/static/cjiy45h7m0iba0111f5imt5em/unicode.png","date":"July 29, 2019"},{"id":"ci1rxc41tm8yi7nd156pz9dom","title":"Kibana Rest API","slug":"/kibana-rest-api","avatar":"https://s3.amazonaws.com/contentkit/static/ci1rxc41tm8yi7nd156pz9dom/elastic.png","date":"July 24, 2019"},{"id":"cjeqgfjgennmw0199wha3j6u0","title":"Airtable As A Database For Middleman [Tutorial]","slug":"/airtable-middleman-database","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfjgennmw0199wha3j6u0/airtable-books.png","date":"June 1, 2019"},{"id":"cjeqgfj5qnnmr0199vzd85xuo","title":"Building A Multiplayer Game With Three.Js + WebSockets","slug":"/multiplayer-game-threejs","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfj5qnnmr0199vzd85xuo/three.jpg","date":"June 1, 2019"},{"id":"cjeqgfi8hnnml0199f1jyo1p5","title":"The Best Sketch Plugins","slug":"/the-best-sketch-plugins","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfi8hnnml0199f1jyo1p5/sketch.png","date":"June 1, 2019"},{"id":"d9920hsu8y7365frf2c6fxrx2","title":"Configuring Vault by Hashicorp in AWS EC2","slug":"/configuring-vault-by-hashicorp-in-aws-ec2","avatar":"https://s3.amazonaws.com/contentkit/static/d9920hsu8y7365frf2c6fxrx2/vault.png","date":"April 15, 2019"},{"id":"cjeqgfvsfnns00199mlbff48i","title":"Best Zapier Alternatives","slug":"/best-zapier-alternatives","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfvsfnns00199mlbff48i/zapier-alternative-workato.png","date":"January 31, 2019"},{"id":"cjn3l18390lyt0158i8qxs5dc","title":"Running A Simple Node Web Server On AWS EC2","slug":"/running-a-simple-node-web-server-on-aws-ec2","avatar":"https://s3.amazonaws.com/contentkit/static/cjn3l18390lyt0158i8qxs5dc/aws.png","date":"October 11, 2018"},{"id":"cjeqgfr4snmwz01673m8l4i51","title":"Graph.Cool vs Firebase","slug":"/graphcool-vs-firebase","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfr4snmwz01673m8l4i51/graphcool.png","date":"October 7, 2018"},{"id":"cjklurup00k0201739mflg9sz","title":"Accessing Redis on an Aws EC2 Instance from the Outside\t","slug":"/accessing-redis-on-an-aws-ec2-instance-from-the-outside","avatar":"https://s3.amazonaws.com/contentkit/static/cjklurup00k0201739mflg9sz/logo.jpeg","date":"August 9, 2018"},{"id":"cjkfzvvxt08up0191l38aadq4","title":"Using PDFtk in AWS Lamba","slug":"/using-pdftk-in-aws-lamba","avatar":"https://s3.amazonaws.com/contentkit/static/cjkfzvvxt08up0191l38aadq4/pdftk.png","date":"August 4, 2018"},{"id":"cjeqgfqsgnnq70199l4vc6vim","title":"Configuring WebSockets on Elastic Beanstalk/EC2","slug":"/websockets-aws-elasticbeanstalk-ec2","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfqsgnnq70199l4vc6vim/aws.png","date":"July 15, 2018"},{"id":"cjit96q2yk0is0111qpbmw66s","title":"Getting Started With Gmail API","slug":"/gmail-api-quickstart","avatar":"https://s3.amazonaws.com/contentkit/static/cjit96q2yk0is0111qpbmw66s/gmail.png","date":"June 28, 2018"},{"id":"cjeqgfo23nmwd0167ynqe7xi8","title":"5 Tips For Using NextJs","slug":"/5-tips-for-using-nextjs","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfo23nmwd0167ynqe7xi8/bHjpwZem_400x400.png","date":"June 1, 2018"},{"id":"cjhxixcyhw4ze01035butoukz","title":"React unstable_deferredUpdates","slug":"/react-unstable_deferredupdates","avatar":"https://s3.amazonaws.com/contentkit/static/cjhxixcyhw4ze01035butoukz/OYIaJ1KK_400x400(1).png","date":"June 1, 2018"},{"id":"cjeqgflpnnnoy01993y7aez8a","title":"Simple Web Scraping With Javascript","slug":"/simple-web-scraping","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgflpnnnoy01993y7aez8a/developer-tools-scraping.png","date":"May 1, 2018"},{"id":"cjeqgfk9lnnn101994ll4ursr","title":"Easy: Add Firebase Facebook Login To Your React App","slug":"/firebase-facebook-login-react","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfk9lnnn101994ll4ursr/firebase.png","date":"May 1, 2018"},{"id":"cjeqgfiqrnmtj0167dmz5g5dg","title":"The 5 Best Static Site Web Hosts","slug":"/the-5-best-static-site-web-hosts","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfiqrnmtj0167dmz5g5dg/digital-ocean.png","date":"May 1, 2018"},{"id":"cjeqgfmi2nmvs01670pgebekn","title":"Tips and Tricks For Using NightmareJs","slug":"/nightmare-js","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfmi2nmvs01670pgebekn/nightmare.png","date":"May 1, 2018"},{"id":"cjeqgfkqennn60199707yp1fb","title":"Why You Should Create Your Next React Web App With Firebase","slug":"/firebase-react-tutorial","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfkqennn60199707yp1fb/firebase.jpg","date":"May 1, 2018"},{"id":"cjeqgfpr6nnpx019978qzmaok","title":"How to Flush Data From Heroku Redis","slug":"/heroku-redis-flushall","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfpr6nnpx019978qzmaok/3wgIDj3j_400x400.png","date":"April 1, 2018"},{"id":"cjeqgfm8xnnp30199vxndhkgh","title":"5 Ways To Style React Components","slug":"/5-ways-to-style-react-components","avatar":null,"date":"April 1, 2018"},{"id":"cjeqgflz7nmvm0167yo7wsjg3","title":"Delete Spreadsheet Rows For Google Sheets","slug":"/delete-rows-google-sheets","avatar":null,"date":"April 1, 2018"},{"id":"cjeqgfp84nnps0199dvru09ll","title":"Make An Uptime Monitoring Microservice In Under 50 Lines of Code","slug":"/twilio-uptime-monitoring-node-tutorial","avatar":null,"date":"April 1, 2018"},{"id":"cjeqgfri4nnqd0199zsv6a9fl","title":"Hexo - The Best Static Site Generator? ","slug":"/deploy-a-hexo-blog","avatar":null,"date":"April 1, 2018"},{"id":"cjeqgfoygnnpn0199f31dgk9m","title":"Airtable As A Minimum Viable Database For Your ReactJs Project","slug":"/airtable-reactjs","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfoygnnpn0199f31dgk9m/airtable-screenshot.png","date":"March 2, 2018"},{"id":"cjeqgfn6pnmw0016793f81hpx","title":"The Advanced Guide To ReactJs Checkboxes","slug":"/reactjs-checkboxes","avatar":null,"date":"January 20, 2018"},{"id":"cjeqgfe8znnly01991jo5xkxx","title":"Minimum Viable GraphQL QuickStart","slug":"/minimum-viable-graphql-quickstart","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfe8znnly01991jo5xkxx/graphcool-schema.png","date":"January 1, 2018"},{"id":"cjeqgfgegnnmb0199jp3hv2xx","title":"How To Add A Contact Form To Your Ghost Blog","slug":"/ghost-blog-contact-form","avatar":null,"date":"November 1, 2017"},{"id":"cjeqgfmvxnnp90199bdti4r8n","title":"PHP Scraping Tutorial - Scrape Reddit With Goutte","slug":"/php-scraping-tutorial-scrape-reddit-with-goutte","avatar":null,"date":"October 7, 2017"},{"id":"cjeqgfpz1nmwp0167c5j46eij","title":"Cannot read property 'loose' of undefined","slug":"/cannot-read-property-loose-of-undefined","avatar":null,"date":"August 25, 2017"},{"id":"cjeqgftlanmxg0167zipvfzp1","title":"Airtable API Example & Tutorial - Generating Charts","slug":"/airtable-api-example-tutorial","avatar":null,"date":"January 31, 2017"},{"id":"cjeqgfphbnmwk0167ldz2mv2q","title":"React onClick Example and Tutorial","slug":"/react-onclick-example-and-tutorial","avatar":null,"date":"January 2, 2017"},{"id":"cjeqgfq8ynmwu0167z6c51ynp","title":"React Tables - How To Render Tables In ReactJS","slug":"/reactjs-tables","avatar":null,"date":"January 2, 2017"},{"id":"cjeqgfuoznmxu0167ayt2zkc3","title":"How To Add A Class in ReactJS","slug":"/reactjs-add-class","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfu6gnnrb0199nyrddra0","title":"Fetching Github Blame with the GraphQL API V4","slug":"/github-blame-graphql-api-v4","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfsmsnnqo0199o2x7dl4x","title":"How To Add Meta Descriptions to Middleman Pages","slug":"/middleman-meta-descriptions","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfs2wnnqi0199rco2vvz2","title":"Zapier Webhook Post Example & Tutorial","slug":"/zapier-webhook-post-example-tutorial","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfvhunmy90167t6ouqfmb","title":"Setting Up A Job Queue For A Node App [Tutorial]","slug":"/job-queue-node","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgftbcnnqy0199nc1f92te","title":"NextJs vs Create-React-App - A Side-By-Side Comparison","slug":"/nextjs-vs-create-react-app","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgft2pnnqt01990ty8fmeu","title":"How To Create A Modal In ReactJS [Tutorial]","slug":"/reactjs-modal","avatar":null,"date":"March 14, 2018"},{"id":"cjeqgfugpnnrj0199x7anb33t","title":"How To Use Twilio With ReactJS","slug":"/reactjs-twilio-example-tutorial","avatar":null,"date":"March 14, 2018"}],"page":8,"offset":40,"total":57,"nodes":[{"id":"cjeqgfoygnnpn0199f31dgk9m","title":"Airtable As A Minimum Viable Database For Your ReactJs Project","slug":"airtable-reactjs","published_at":"2018-03-02T01:30:50.351","created_at":"2018-03-14T02:16:37","excerpt":"In this guide, I'll show you how to use Airtable as a minimum viable database for your ReactJs proj","image":{"id":"cjg235zijm7ek016960g04hpj","url":"static/cjeqgfoygnnpn0199f31dgk9m/airtable-screenshot.png"},"posts_tags":[],"date":"March 2, 2018","html":"
In this guide, I'll show you how to use Airtable as a minimum viable database for your ReactJs project.
\nFirst, what's Airtable?
\n\nAirtable is a souped up version of Google sheets with a robust API. Airtable is built for teams and is used to organize data rather than calculate things.
\nIt's easy to make API calls to Airtable endpoints to push and fetch data to and from your Airtable base (spreadsheet).
\nI'll assume you're starting with a brand new ReactJs project. Here's the companion Github repo for this project that demonstrates how you can integrate Airtable into your React app.
\nWe'll be using Airtable's API client-side, but you wouldn't want to do this in production because you'd exposure your API keys.
\nIf you haven't already, install creat-react-app globally:
\nnpm install -g creat-react-app
\nIssue the following to start a new project:
\ncreate-react-app my-app
\nFirst, install fetch (npm install whatwg-fetch --save).
\ncd my-app\nnpm i whatwg-fetch --save
\nHere's a quick refresher on fetch and async/await syntax:
\nconst apiRequest = async () => {\n const resp = await fetch(url)\n // convert response to json\n const json = await resp.json()\n}
\nRemember, you can only await a promise if the enclosing function is async.
\nWe will make the API call to Airtable using fetch inside the componentWillMount() lifecycle component.
\nYou can find your Airtable API key and endpoints at https://www.airtable.com/api.
\nAirtable endpoints look something like:
\nvar url = `https://api.airtable.com/v0/${base}/${table}?maxRecords=20&view=${view}`;\nimport 'whatwg-fetch';\nclass App extends React.Component { ...\n async componentWillMount() {\n const key = 'YOUR API KEY';\n const url = 'YOUR AIRTABLE URL';\n const resp = await fetch(url, {\n headers: {\n \"Authorization\": `Bearer ${key}`\n }\n })\n const json = await resp.json()\n const {records} = resp\n console.log(records); \n }\n}
\nNow when you reload the page and check the console, you'll see your Airtable records logged.
\nYou wouldn't want to do this in production because it would expose your API key to anyone who felt like viewing source.
\nThe workaround is to make the final API calls to Airtable on the server-side.
\nFor this example, we'll assume you're running Express with your ReactJs App.
\nInstall node-fetch, express, and bodyParser:
\nnpm install node-fetch express body-parser --save
\nIn your project directory:
\ntouch server.js
\nAdd the following to server.js:
\nimport express from 'express';\nimport bodyParser from 'body-parser';\nimport fetch from 'node-fetch';\nconst app = express();\napp.get('/get', bodyParser.json(), async (req, res) => {\n const key = 'YOUR API KEY';\n var resp = await fetch('YOUR AIRTABLE URL ENDPOINT', {\n headers: {\n \"Authorization\": \"Bearer: \" + key\n }\n }) \n const json = await resp.json()\n const {records} = resp;\n res.json({records});\n});\napp.use('/', express.static('public'));\napp.listen(process.env.PORT || 3000);
\nThis enables us to make the request to Airtable server side, in our express config file.
\nNow inside our React component, we make a get request to our same-origin '/get' endpoint, using fetch:
\ncomponentWillMount() {\n fetch('/get', {\n }).then(resp => resp.json())\n .then(resp => {\n const records = records;\n console.log(records);\n });\n}
\nTo summarize:
\nnpm install node-fetch --save
).To store your Airtable records in state, you would just define a new initial state as follows:
\nimport React from 'react';\nimport 'whatwg-fetch';\nclass App extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n airtable: []\n }\n ...\n }\n}
\nAnd then inside your fetch request in componentWillMount replace the line console.log(records); with something like this:
\nthis.setState({airtable: records});
\nNow this.state.records is a an array objects, where each object corresponds to a row in your Airtable.
\nIt looks something like this:
\nrecords = [\n {\n field_1: \"value_1\",\n field_2: \"value_2\"\n }, \n {\n field_1: \"another value\"\n field_2: \"yet another\"\n }\n]
\nSince the records are an array of objects where each object corresponds to an Airtable row, we will use the map function to iterate through the array.
\nHere's an example:
\n...\nrender() {\n const airtable = this.state.airtable;\n const entry = airtable.map((airtable, index) =>\n <tr>\n <td>{airtable.fields.YOUR_FIELD_HERE}</td>\n <td>{airtable.fields.YOUR_OTHER_FIELD}</td>\n </tr>\n );\n return(\n <div><table><tbody>{entry}</tbody></table></div> \n );\n}
\nCheckout the complete working example here.
","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfoygnnpn0199f31dgk9m/airtable-screenshot.png"},{"id":"cjeqgfn6pnmw0016793f81hpx","title":"The Advanced Guide To ReactJs Checkboxes","slug":"reactjs-checkboxes","published_at":"2018-01-20T17:00:00","created_at":"2018-03-14T02:16:34","excerpt":"How are checkboxes handled in ReactJS? Great question, I'm glad you asked! The truth is that forms ","image":null,"posts_tags":[],"date":"January 20, 2018","html":"How are checkboxes handled in ReactJS? Great question, I'm glad you asked! The truth is that forms inputs are handled a little differently in React. They can be controlled or uncontrolled.
\nAn input is controlled if React's virtual DOM is constantly syncing the input value with state:
\nclass App React.Component {\n constructor(props) {\n super(props);\n /* establish the initial state of the input field */\n this.state = {\n inputValue: ''\n }\n }\n /* update the input value in the virtual DOM */\n handleChange(event) {\n this.setState({\n inputValue: event.target.value\n });\n }\n render() {\n return (\n <input \n value={this.state.inputValue}\n onChange={this.state.handleChange.bind(this)} />\n );\n }\n}
\nin ReactJS, elements are controlled so that we can access (and manipulate) their value. If you're familiar with Jquery, the equivalent would be:
\nvar inputValue = $('input').val();
\nEnough preamable. Here's how checkboxes work in ReactJs.
\nThe overall strategy is to store the checkbox state (checked or unchecked) so that we can access it by other components or on form submission.
\nWe do this with:
\nconst checkbox = (\n <div>\n <input \n type=\"checkbox\"\n onClick={this.toggle.bind(this)}\n />\n <label>Checkbox</label>\n </div>\n);
\nWith an onClick event we call the class method toggle():
\ntoggle(event) {\n this.setState({checkboxState: !this.state.checkboxState});\n}
\nThe toggle class method just negates the prior checkboxState
. Checkboxes are easily controlled because they are necessarily binary - they can exist in the checked or unchecked state.
class App extends React.Component {\n constructor(props) {\n super(props);\n /* set the initial checkboxState to true */\n this.state = {\n checkboxState: true\n }\n }\n /* prevent form submission from reloading the page */\n onSubmit(event) {\n event.preventDefault();\n }\n /* callback to change the checkboxState to false when the checkbox is checked */\n toggle(event) {\n this.setState({\n checkboxState: !this.state.checkboxState\n });\n }\n render() {\n const checkedOrNot = [];\n checkedOrNot.push(\n <p>{this.state.checkboxState ? 'Unchecked' : 'Checked'}</p>\n );\n const checkbox = (\n <span>\n <input \n type=\"checkbox\"\n onClick={this.toggle.bind(this)}\n />\n <label>Checkbox</label>\n </span>\n );\n return (\n <div>\n <form onSubmit={this.onSubmit.bind(this)}>\n {checkbox}\n <button type=\"submit\">Submit</button>\n </form>\n {checkedOrNot}\n </div>\n );\n }\n}\nReactDOM.render(<App />, document.getElementById(\"root\"));
\n","avatar":null},{"id":"cjeqgfe8znnly01991jo5xkxx","title":"Minimum Viable GraphQL QuickStart","slug":"minimum-viable-graphql-quickstart","published_at":"2018-01-01T21:34:50.595","created_at":"2018-03-14T02:16:23","excerpt":"Minimum Viable GraphQL QuickStart","image":{"id":"cjfxlqioz5gy50179rrqht749","url":"static/cjeqgfe8znnly01991jo5xkxx/graphcool-schema.png"},"posts_tags":[],"date":"January 1, 2018","html":"
We'll use React, GraphQL and Apollo to make a simple counter. We'll also use Graph.Cool as our hosted GraphQL backend. GraphCool is a GraphQL backend as a service, analogous to Firebase.
\nGraphQL. GraphQL enables you to fetch exactly the data that you need from your database, no more or less. GraphQL let's you bundle nested requests into a single request. Instead of requesting users/{userId} and then posts/{postId} you request { users: { posts } } at once.
\nApollo. Apollo is the easiest way to get started with GraphQL. The alternative to Apollo is Relay. In this example, we'll be using apollo-react to build a simple React component that fetches and pushes data to our GraphQL database.
\nGraphCool. GraphCool is analogous to Firebase. If you want to host your own GraphQL server that's fine - but GraphCool is great for prototyping. Unlike Firebase, there's no vendor lock-in with GraphCool. You can swap in an in-house solution later.
\nIf you're familiar with Redux and its syntax connect(mapStateToProps, mapDispatchToProps)(App), client-side graphQL won't be difficult for you to learn.
\nApollo has its own internal redux store. Apollo makes requests to your GraphQL server and intelligently updates it's internal store. Wrapping your React components with Apollo gives you access to Apollo's internal store.
\nThis graphql-react-apollo is a minimum viable example of how you'd use Apollo and GraphQL, client-side. In this example we make a counter that keeps track of button clicks.
\nClone this sample project and and install dependencies:
\ngit clone https://github.com/focuswish/graphql-react-apollo.git \ncd graphql-react-apollo\nnpm install
\n\nCreate a GraphCool account (if you don't already have one). And be sure to install the GraphCool commandline interface.
\nnpm install -g graphcool\ngraphcool auth # login
\nA new GraphCool project comes with User and File schema by default. We're going to initialize a new GraphCool project that adds a Counter schema:
\nFrom graphql-react-apollo issue the following;
\ngraphcool init\nhttps://raw.githubusercontent.com/focuswish/graphql-react-apollo/master/example.graphql
\nThe URI after 'graphcool init' points to schema for a new type, Counter:
\ntype Counter {\n count: Int!\n}
\nIf initializing the new GraphCool project is successful, you should see a Simple and Relay API URI printed in the console that looks something like:
\nSimple API: https://api.graph.cool/simple/v1/cj5d82p4jmjg90127sqeiu4kg\nRelay API: https://api.graph.cool/relay/v1/cj5d82p4jmjg90127sqeiu4kg
\nYou can also access your simple and relay API URIs from the GraphCool Console.
\nCopy + paste the Simple API URI into src/index.js as follows:
\nconst networkInterface = createNetworkInterface({\n uri: 'https://api.graph.cool/simple/v1/cj5d82p4jmjg90127sqeiu4kg',\n});
\nStart the server:
\nnpm run dev
\nLet's start with a new project from scratch.
\nFirst we'll setup Apollo client.
\nmkdir my-app && cd my-app\nnpm i react-apollo react react-dom --save
\n// App.js\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport {createNetworkInterface, ApolloClient, ApolloProvider} from 'react-apollo';\nconst networkInterface = createNetworkInterface({\n uri: 'https://api.graph.cool/simple/v1/cj5d82p4jmjg90127sqeiu4kg',\n});\nconst client = new ApolloClient({networkInterface})\n// Wrap our root component (<App />) in <ApolloProvider></ApolloProvider>\nReactDOM.render(\n <ApolloProvider client={client}>\n <App />\n </ApolloProvider>,\n document.getElementById('root')\n)
\nLet's create a simple root component. We initialized the GraphCool project with sample schema for a counter. This sample schema only had one field, \"count\":
\ntype Counter {\n count: Int!\n}
\nInt is one of the primitative types. The exclaimation mark means that this field is non-nullable.
\nWhen you initialized this new GraphCool project, you provided a URL to the above schema. GraphCool automatically added some fields (ID, createdAt, and UpdatedAt).
\ntype Counter implements Node {\n count: Int!\n createdAt: DateTime!\n id: ID! @isUnique\n updatedAt: DateTime!\n}
\nSince we haven't populated our container with data yet, we'll make a button that creates a new counter.
\nconst App = ({createCounter}) => (\n <div>\n <button onClick={() => ( createCounter({count: 0}) )}>Create counter</button>\n </div>\n)
\nJust like with redux, we'll pass a createCounter() prop to our
The syntax looks like this:
\nimport {graphql} from 'react-apollo';\nconst createCounter = gql`\n mutation createCounter(\n $count: Int!\n ) {\n createCounter(\n count: $count\n ) {\n count\n id\n }\n }\n`\nconst AppWithData = graphql(createCounter, {\n props: ({ mutate }) => ({\n createCounter: ({count}) => mutate({variables: { count } }),\n }),\n})(App)
\nNote that we've named our mutation 'createCounter.' This can be a useful heuristic, but naming mutations and queries isn't necessary. The state of affairs is analogous to javascript where you can write anonymous or named functions, e.g., () => {...} or function namedFunction() {...}.
\nA mutation is anything that mutates a node, like creating, updating, or deleting nodes. We use queries to read data.
\nMutation syntax looks like:
\n# name our mutation\nmutation createCounter(\n # The count should be an integer (int)\n # The exclaimation-mark (!) means that $count is a required field\n $count: Int!\n ) {\n createCounter(\n # Pass $count as a variable\n count: $count\n ) {\n # The values we want this mutation to return\n # We could also return createdAt, updatedAt\n count\n id\n }\n}
\nWe could also add a default value to the count field with:
\nmutation createCounter($count: Int = 0) {...}
\nThis syntax is similar to ES6 parameter defaults, e.g., createCounter(count = 0)
\nOnce again, we're using react-apollo to interface with GraphQL on the client-side.
\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport {createNetworkInterface, ApolloClient, ApolloProvider, graphql} from 'react-apollo';\nconst App = ({createCounter}) => (\n <div>\n <button onClick={() => ( createCounter({count: 0}) )}>Create counter</button>\n </div>\n)\nconst createCounter = gql`\n mutation createCounter(\n $count: Int!\n ) {\n createCounter(\n count: $count\n ) {\n count\n id\n }\n }\n`\nconst AppWithData = graphql(createCounter, {\n props: ({ mutate }) => ({\n createCounter: ({count}) => mutate({variables: { count }})\n })\n})(App)\nconst networkInterface = createNetworkInterface({\n uri: 'https://api.graph.cool/simple/v1/cj5d82p4jmjg90127sqeiu4kg',\n});\nconst client = new ApolloClient({networkInterface})\nReactDOM.render(\n <ApolloProvider client={client}>\n <AppWithData />\n </ApolloProvider>,\n document.getElementById('root')\n)
\nGraphQL really shines with queries. That's because GraphQL empowers us to ask for only the data that we need. Nested queries are a breeze so we don't need to make multiple roundtrips to the server to gain access to a deeply nested edge.
\n\n\nWith GraphCool, you can query all nodes of a certain type with all{Nodes}. For example, if you want to query all of the records of type User, we invoke allUsers.
\nTo query a specific node, you pass an ID as a variable.
\nSome GraphQl nomenclature:
\nSo, you want to add a contact form to your ghost blog? If you had a Wordpress site, you could just get a plugin. If you had custom php site, you could just make a custom form script. But what do you do for a ghost blog? Adding a ghost form is actually easier than you think.
\nWhat you need to do is use FormSpree, which is an external form endpoint. You add a basic html form to a contact page and then point it to formspree.io/your-email-address@gmail.com.
\n\nWhen someone submits the contact form, it gets routed to your email address via formspree.com. This is a free service, and you don't even need to register an account for it to work!
\nHere's some sample code:
\n<form action=\"https://formspree.io/YOUR_EMAIL_ADDRESS_HERE@DOMAIN.COM\" method=\"POST\">\n <div class=\"form-group\">\n <label>Name</label>\n <input type=\"text\" name=\"name\" class=\"ghost-input\">\n </div>\n <div class=\"form-group\">\n <label>Email address</label>\n <input type=\"email\" name=\"_replyto\" class=\"ghost-input\">\n </div>\n <div class=\"form-group\">\n <label>Message: </label>\n <textarea type=\"text\" name=\"message\" class=\"ghost-input\"></textarea></div>\n <input type=\"submit\" value=\"Send\" class=\"ghost-btn\">\n</form>\n<style>\n .form-group:after {\n content: \"\";\n display: block;\n clear: both;\n }\n .form-group label {\n display: block;\n }\n</style>
\nRemember, Ghost can parse both markdown and HTML. So if you add the above html to a page on your ghost blog, .e.g, /contact/.
\nDon't forget to add your email address to the form action=\"https://formspree.io/your-email-here\".
\nWe can style the form appropriately by making sure to add the classes ghost-input to the input fields.
\n\n�
","avatar":null},{"id":"cjeqgfmvxnnp90199bdti4r8n","title":"PHP Scraping Tutorial - Scrape Reddit With Goutte","slug":"php-scraping-tutorial-scrape-reddit-with-goutte","published_at":"2017-10-07T05:56:59.576","created_at":"2018-03-14T02:16:34","excerpt":"I needed to scrape Reddit posts for a project. After having fun with some tools I decided to docume","image":null,"posts_tags":[],"date":"October 7, 2017","html":"I needed to scrape Reddit posts for a project. After having fun with some tools I decided to document the process. However I my process is generalizable to scraping anything.
\nFirst, what is a web scraper? You could manually copy and paste data from websites into a spreadsheet, but that would take far too long if you are working on a big project.
\nScraping lets you programmatically copy data at a high throughput.
\nFor this project, I chose PHP because it has some extra syntactic sugar.
\nThe best (php) scraping library is Goutte.
\nFirst, you'll need to install Compose, which is a package management tool for PHP. In terminal type:
\n# Install Composer\ncurl -sS https://getcomposer.org/installer | php
\nNow add Goutte as a dependency to your project.
\ncomposer require fabpot/goutte
\nBut Goutte requires Guzzle, so you'll need to install that too.
\nGuzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.
\nphp composer.phar require guzzlehttp/guzzle
\nNow if you open composer.json it should look something like:
\n{\n \"name\": \"my_project\",\n \"require\": {\n \"fabpot/goutte\": \"^3.2\",\n \"guzzlehttp/guzzle\": \"^6.2\"\n }\n}
\nAfter installing Goutte and Guzzle, you need to include composer's autoloader.
\nSo create a file index.php and at the top include:
\nrequire 'vendor/autoload.php';
\nTo scrape stuff with Guzzle, you only need to write a few lines of code. For example:
\nrequire 'vendor/autoload.php';\nuse Goutte\\Client;\n$url = \"http://www.reddit.com\";\n$css_selector = \"a.title.may-blank\";\n$thing_to_scrape = \"_text\";\n$client = new Client();\n$crawler = $client->request('GET', $url);\n$output = $crawler->filter($css_selector)->extract($thing_to_scrape);\nvar_dump($output);
\nWith this PHP snipped, we:
\nThis is the easy part. Using Chrome's developer tools you can inspect any element to reveal its attributes.
\nOn Reddit, I right-clicked a post title.
\n\nAnd noted the link element's classes, title, may-blank, and outbound.
\nReturn to terminal and type php index.php to run the PHP script.
\nIn terminal you should see output like:
\narray(25) {\n [0]=>\n string(17) \"from sad to happy\"\n [1]=>\n string(183) \"The president of the Philippines Rodrigo Duterte should be investigated for murder after boasting he \"personally\" killed three suspected criminals, a top United Nations official said.\"
\nThis is just an array that comprises all of the Reddit post titles on the front page.
\nThe power of this approach is that we can target the elements to scrape using a css selector.
\nThis approach is reminiscent of javascript and Jquery where you can manipulate the DOM using similar syntax, e.g.:
\nvar title = $('a.title.may-blank').text();
\nIn the previous Reddit we scraped the post titles on the front page of Reddit. Therefore we used _text
to get the text. Here are some other things we can fetch:
Note: Symphony's DomCrawler supports special link.
\nSo instead of getting the attribute with href
you can also write:
$output = $crawler->filter('a.title.may-blank')->link();\n$uri = $link->getUri();
\nThe getUri() is especially useful as it cleans the href value and transforms it into how it should really be processed. For example, for a link with href=\"#foo\", this would return the full URI of the current page suffixed with #foo. The return from getUri() is always a full URI that you can act on.\n
Link to section in Symfony DOMCrawler Docs.
\nWe can scrape many attribute values at once. For example, this line fetches the text, class and url of an element:
\n$output = $crawler->filter($css_selector)->extract(array('_text', 'class', 'href'));
\nFor the Reddit example, the above code would return:
\n\n [0]=>\n array(3) {\n [0]=>\n string(17) \"from sad to happy\"\n [1]=>\n string(24) \"title may-blank outbound\"\n [2]=>\n string(30) \"http://i.imgur.com/P45maQC.gif\"\n }
\nConsider once again Reddit's front page. There are a bunch of posts. What if we only want to select the second post and not all of them? We can access node by its position on the list:
\n$output = $crawler\n ->filter('a.title.may-blank') // CSS selector\n ->eq(1) // node position\n ->extract('_text'); // DOM attribute to extract
\nScraping multiple pages is easy with Goutte. We just need a foreach loop.
\nFirst we create an array of links:
\n// links to scrape\n$url = array(\n 'https://www.reddit.com',\n 'https://www.reddit.com/new/',\n 'https://www.reddit.com/rising/'\n);
\nAnd here's what the foreach loop might look like:
\nrequire 'vendor/autoload.php';\nuse Goutte\\Client;\n$url = array(\n 'https://www.reddit.com',\n 'https://www.reddit.com/new/',\n 'https://www.reddit.com/rising/'\n);\n$selector = \"a.title.may-blank\";\n$attribute = \"_text\";\nforeach($url as $key => $value) {\n $client = new Client();\n // on the first iteration, $value = 'https://www.reddit.com', on the second $value = 'https://www.reddit.com/new/', and so forth. \n $crawler = $client->request('GET', $value);\n $output[$key] = $crawler\n ->filter($selector)\n ->eq(1) // scrape the second post only\n ->extract($attribute);\n}\nvar_dump($output);
\nClone this repository for a quick start: https://github.com/unshift/goutte-php-scraper-boilerplate/blob/master/index.php#L25
\nThat's it! Now go forth and scrape responsibly.
","avatar":null}]}}}