{"componentChunkName":"component---src-templates-page-tsx","path":"/page/6","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":6,"offset":30,"total":57,"nodes":[{"id":"cjeqgflpnnnoy01993y7aez8a","title":"Simple Web Scraping With Javascript","slug":"simple-web-scraping","published_at":"2018-05-01T16:00:00","created_at":"2018-03-14T02:16:32","excerpt":"Sometimes you need to scrape content from a website and a fancy scraping setup would be overkill.","image":{"id":"cjgclnlt9z13x0179hopx0hwr","url":"static/cjeqgflpnnnoy01993y7aez8a/developer-tools-scraping.png"},"posts_tags":[{"tag":{"id":"2spo553zykbgxgq4vkk3","name":"Javascript"}}],"date":"May 1, 2018","html":"
Sometimes you need to scrape content from a website and a fancy scraping setup would be overkill.
\nMaybe you only need to extract a list of items on a single page, for example.
\nIn these cases you can just manipulate the DOM right in the Chrome developer tools.
\n\nLet's say you need this list of baked goods in a format that's easy to consume: https://en.wikipedia.org/wiki/List_of_baked_goods
\nOpen Chrome DevTools and copy the following into the console:
\nJSON.stringify([...document.querySelectorAll('ul > li > a')].map(r => r.textContent))
\nNow you can select the JSON output and copy it to your clipboard.
\nLet's try to get a list of companies from AngelList (https://angel.co/companies?company_types[]=Startup&locations[]=1688-United+States
\nThis case is a slightly less straightforward because we need to click \"more\" at the bottom of the page to fetch more search results.
\nOpen Chrome DevTools and copy:
\n;(function () {\n let loop = () => setTimeout(() => {\n let list = [...document.querySelectorAll('a.startup-link')]\n .map(a => a.textContent)\n .filter(text => text !== '')\n // programmatically click the \"more\" button\n // to fetch more companies \n document.querySelector('.more').click()\n console.log('length: ' + list.length) \n // save results in local storage\n window.localStorage.setItem('__companies__', JSON.stringify(list))\n // run again in 0 - 3 seconds\n loop()\n }, 3000 * Math.random())\n loop()\n})()
\nYou can access the results with:
\nwindow.localStorage.getItem('__companies__')
\n[...document.querySelectorAll]
because it returns a node list and we want a plain old array.window.localStorage.setItem('__companies__', JSON.stringify(arr))
so that if we disconnect or the browser crashes, we can go back to Angel.co and our results will be saved.These examples are fun but what about scraping entire websites?
\nWe can use node-fetch and JSDOM to do something similar.
\nconst fetch = require('node-fetch')\nconst JSDOM = require('jsdom').JSDOM\nlet selector = 'ul > li > a'\nlet url = 'https://en.wikipedia.org/wiki/List_of_baked_goods'\nfetch(url)\n .then(resp => resp.text())\n .then(text => {\n let dom = new JSDOM(text)\n let { document } = dom.window; \n let list = [...document.querySelectorAll(selector)]\n .map(a => a.textContent)\n console.log(list)\n })
\nJust like before, we're not using any fancy scraping API, we're \"just\" using the DOM API. But since this is node we need JSDOM to emulate a browser.
\nNightmare is a browser automation library that uses electron under the hood.
\nThe idea is that you can spin up an electron instance, go to a webpage and use nightmare methods like type and click to programmatically interact with the page.
\nFor example, you'd do something like the following to login to a Wordpress site programmatically with nightmare:
\nlet BASE_URL = ''\nlet WP_USER = ''\nlet WP_PASSWORD = ''\nfunction wpLogin() {\n nightmare.goto(`${process.env.BASE_URL}/wp-admin`)\n .wait(1000)\n .type(\"input#user_login\", WP_USER)\n .type(\"input#user_pass\", WP_PASSWORD)\n .click(\"p.submit input#wp-submit\")\n}
\nNightmare is a fun library and might seem like \"magic\" at first.
\nBut the NightmareJs methods like wait, type, click, are just syntactic sugar on DOM (or virtual DOM) manipulation.
\nFor example, here's the source for the nightmare method refresh:
\nexports.refresh = function(done) {\n this.evaluate_now(function() {\n window.location.reload();\n }, done);\n};
\nIn other words, window.location.reload wrapped in their evaluate_now method. So with nightmare, we are spinning up an electron instance (a browser window), and then manipulating the DOM with client-side javascript. Everything is the same as before, except that nightmare exposes a clean and tidy API that we can work with.
\nWhy is Nightmare built on electron? Why not just use Chrome?
\nThis brings us to the interesting alternative to nightmare, Chromeless.
\nChromeless attempts to duplicate Nightmare's simple browser automation API using Chrome Canary instead of Electron.
\nThis has a few interesting benefits, the most important of which is that Chromeless can be run on AWS Lambda. It turns out that the precompiled electron binaries are just too large to work with Lambda.
\nHere's the same example we started with (scraping companies from Angel.co), using Chromeless:
\nconst url = 'https://angel.co/companies'\nconst { Chromeless } = require('chromeless')\nasync function run() {\n const chromeless = new Chromeless()\n const value = await chromeless\n .goto(url)\n .evaluate(async () => {\n let data = await new Promise((resolve, reject) => {\n let cycles = 0\n let loop = () => setTimeout(() => {\n if (cycles > 10) {\n let list = [...document.querySelectorAll('a.startup-link')]\n .map(a => a.textContent)\n .filter(text => text !== '')\n resolve(JSON.stringify(list))\n }\n cycles += 1 \n document.querySelector('.more').click()\n loop()\n }, 3000 * Math.random())\n \n loop()\n })\n return data\n })\n await chromeless.end()\n}\nrun().catch(console.error.bind(console))\n
\nTo run the above example, you'll need to install Chrome Canary locally. Here's the download link.
\nalias canary=\"/Applications/Google\\ Chrome\\ Canary.app/Contents/MacOS/Google\\ Chrome\\ Canary\"\ncanary --remote-debugging-port=9222\n
\nNext, run the above two commands to start Chrome canary headlessly.
\nFinally, install the npm package chromeless.
\nnpm i chromeless\n
","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgflpnnnoy01993y7aez8a/developer-tools-scraping.png"},{"id":"cjeqgfk9lnnn101994ll4ursr","title":"Easy: Add Firebase Facebook Login To Your React App","slug":"firebase-facebook-login-react","published_at":"2018-05-01T16:00:00","created_at":"2018-03-14T02:16:31","excerpt":"Authentication and authorization is an intimidating part of web app development. I prefer front-end","image":{"id":"cjiyyu4if3n0s01113do1121m","url":"static/cjeqgfk9lnnn101994ll4ursr/firebase.png"},"posts_tags":[],"date":"May 1, 2018","html":"Authentication and authorization is an intimidating part of web app development. I prefer front-end development. Firebase takes care of the backend for you so that you can focus on the core business and value proposition instead of reinventing the wheel.
\nIt turns out that it's super easy to add Facebook login functionality to your React app. If I can do it in 15 minutes, I guarantee it won't be hard for you at all (especially if you're a js pro).
\nIn part I we'll walk through the following:
\nIn part 2, we'll dive into the code:
\nprovider = new firebase.auth.FacebookAuthProvider()
this.setState({user: user.result})
Here's what they look like:
\n\nFor now, just save them for later.
\nHere's where we're at:
\n\nNext:
\nSelect Facebook as your sign in provider. You'll see a screen like this that prompts you for a Facebook App ID and Secret:
\n\nWhich brings us to Step 3.
\nHere's where we're at:
\n\nBack at developers.facebook.com, click Facebook Login in the left sidebar (or click Add Products if you don't see it).
\nPaste the URI provided by Firebase as follows:
\n\nDamn, we're 50% done.
\nOn to part II.
\nLet's start with a clean, barebones react app.
\nnpm install -g create-react-app\ncreate-react-app my-app\ncd my-app/\nnpm start
\nIf everything looks good, kill the server.
\nInstall Firebase and ReactFire:
\nnpm install firebase --save
\nImport Firebase:
\nMake a new file in the src subdirectory (we'll call it client.js);
\n// src/client.js\nimport firebase from 'firebase'
\nAdd your API key from Firebase and initialize Firebase:
\n// src/client.js\nimport firebase from 'firebase'\nconst config = {\n apiKey: \"YOUR API KEY\",\n authDomain: \"YOUR AUTH DOMAIN\",\n databaseURL: \"YOUR DATABASE URL\",\n storageBucket: \"YOUR STORAGE BUCKET\",\n messagingSenderId: \"YOUR MESSAGING SENDER ID\"\n}\nfirebase.initializeApp(config)\nexport const ref = firebase.database().ref()\nexport const auth = firebase.auth\nexport const provider = new firebase.auth.FacebookAuthProvider()
\nSet an initial state:
\nSwitch to src/App.js, which should have been generated by create-react-app;
\nYou can delete the jsx in the render() function.
\n// App.js\nimport React from 'react'\nimport firebase from 'firebase'\n// import provider and auth that we exported from src/client.js\nimport { provider, auth } from './client'\nclass App extends Component {\n this.state = {\n user: null\n }\n render() {\n return (\n <div className=\"App\">\n </div>\n );\n }\n}
\nCreate a login and a logout function after we declare initial state:
\n// App.js\nclass App extends React.Component {\n login = () => {\n auth().signInWithPopup(provider)\n .then(({ user }) => {\n this.setState({ user })\n })\n }\n logout = () => {\n auth().signOut().then(() => {\n this.setState({user: null}) \n })\n }\n \n // ...\n}
\nCreate login and logout buttons that call each of these functions inside render():
\n// App.js\n...\nrender() {\n const { user } = this.state\n return(\n <div className='app'>\n <p>{user ? `Hi, ${user.displayName}!` : 'Hi!'}</p>\n <button onClick={this.login}>\n Login with Facebook\n </button>\n <button onClick={this.logout}>\n Logout\n </button>\n </div>\n )\n};
\nimport React from 'react'\nimport firebase from 'firebase'\nconst config = {\n apiKey: 'YOUR API KEY',\n authDomain: 'YOUR AUTH DOMAIN',\n databaseURL: 'YOUR DATABASE URL',\n storageBucket: 'YOUR STORAGE BUCKET',\n messagingSenderId: 'YOUR MESSAGING SENDER ID'\n}\nfirebase.initializeApp(config)\nconst auth = firebase.auth\nconst provider = new firebase.auth.FacebookAuthProvider();\nclass App extends Component {\n state = {\n user: null\n }\n login = () => {\n auth().signInWithPopup(provider)\n .then(({ user }) => {\n this.setState({ user })\n })\n }\n logout = () => {\n auth().signOut().then(() => {\n this.setState({ user: null }) \n })\n }\n render() {\n const { user } = this.state\n return (\n <div className='App'>\n <p>{user ? `Hi, ${user.displayName}!` : 'Hi!'}</p>\n <button onClick={this.login}>\n Login with Facebook\n </button>\n <button onClick={this.logout}>\n Logout\n </button>\n </div>\n );\n }\n}
\nIn its current state, all our app does is launch a login with Facebook modal. When our component mounts, it should check to see if some users are already authorized (so that they aren't prompted to login again).
\nTo accomplish this, we'll use the React lifecycle hook componentDidMount().
\n// src/App.js\ncomponentWillMount() {\n auth.onAuthStateChanged().then(user => {\n if (user) {\n this.setState({ user })\n }\n })\n}
","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfk9lnnn101994ll4ursr/firebase.png"},{"id":"cjeqgfiqrnmtj0167dmz5g5dg","title":"The 5 Best Static Site Web Hosts","slug":"the-5-best-static-site-web-hosts","published_at":"2018-05-01T16:00:00","created_at":"2018-03-14T02:16:29","excerpt":"Static sites are all the rage right now. We've come full circle. Remember the 90's when Geocities w","image":{"id":"cjg08y7r0edfi01691gzzqvw0","url":"static/cjeqgfiqrnmtj0167dmz5g5dg/digital-ocean.png"},"posts_tags":[],"date":"May 1, 2018","html":"Static sites are all the rage right now. We've come full circle. Remember the 90's when Geocities was popular? Static site hosting is little like a cutting-edge version of Geocities. I've personally built a few Middleman and Jekyll sites, which prompted me to wonder about static site hosting options.
\nAs you probably know, part of the appeal of static sites is that they're very low overhead. You don't need a LAMP stack, and you don't need to be paying a $15+/month web hosting bill to deploy a simple static site for your project. Static sites are great for things like documentation websites or blogging.
\nResearching static site web hosts, these were the kinds of criteria I considered:
\nWithout further adieu, here are the best static site hosts.
\nGithub Pages is a favorite among people that like to hack together static sites. It has serious advantages, but it didn't turn out to be the right solution in my particular case.
\nAmazon Web Services is one way to deploy a static site for free. It's fast, cheap, and reliable.
\nI personally found that AWS was an unsexy, not-so-user-friendly solution to static site hosting.
\nIf you want to go down this route, here's some further reading:
\nPubstorm is a lesser known service that like Github, let's you deploy static sites from the command line. PubStorm is a product from the makers of the Nitrous IDE. It's a fun tool and very easy to use. Once PubStorm is locally installed on your machine, you just need to type:
\ncd /workspace/myproject\nStorm init \nStorm publish
\nNetlify is one of my favorite products in this space. It boasts some serious features that cater to the static site crowd. Things like static form endpoints, Zapier integration, webhooks, custom redirect rules, prerendering, and prioritized CDN traffic.
\nOne quandary with static sites is you can't just add add a typical server-side, PHP contact form. The workaround is that you can add a form that posts to an external endpoint. I'm talking about free products like Formspree:
\n<form action=\"https://formspree.io/your@email.com\" method=\"POST\">\n <input type=\"text\" name=\"name\">\n <input type=\"email\" name=\"_replyto\">\n <input type=\"submit\" value=\"Send\">\n</form>
\nHowever, Netlifly has this functionality baked in. Just add the netlify attribute like so:
\n<form name=\"contact\" action=\"thank-you\" netlify>
\nBitBalloon is the one hosting product that I haven't used personally on this list. But that shouldn't deter you because it has some major perks.
\nDigitalOcean is extremely versatile and not designed specifically for static sites. Nevertheless, it's a great way to host static websites.
\nYou can create a $5 droplet in 30 seconds, which creates a virtual server with 512 MB memory and 20GB SSD storage.
\nFor my project, I created a Ubuntu 16.04.1 x64 droplet to host a few different small static sites. Digital Ocean charges you by the hour in a prorated manner. So if you deploy a $5 droplet and only use it for 15 days, you're only out $2.5. This pricing model is nice for people that like to test the water.
\nDigital Ocean integrates nicely with IDEs like Cloud9. It's great to have access to the backend to configure things like 301 redirects.
","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfiqrnmtj0167dmz5g5dg/digital-ocean.png"},{"id":"cjeqgfmi2nmvs01670pgebekn","title":"Tips and Tricks For Using NightmareJs","slug":"nightmare-js","published_at":"2018-05-01T16:00:00","created_at":"2018-03-14T02:16:33","excerpt":"What's NightmareJs","image":{"id":"bwvhdwamvfx7fc3fvjacum8vc","url":"static/cjeqgfmi2nmvs01670pgebekn/nightmare.png"},"posts_tags":[],"date":"May 1, 2018","html":"Nightmare is lots of fun. It's a browser automation library built around electron with a smooth API. It's titillating to write a few commands in node and watch them be executed in a browser window. Nightmare is used for prototyping, testing, web scraping, and automating tasks.
\nThere are a few things that you should be aware of from the get-go when using nightmare for a more fun developer experience.
\nvar Nightmare = require('nightmare') \nvar nightmare = Nightmare({ show: true })\nvar result = nightmare\n .goto('http://www.example.com')\n .wait(1000)
\nIn the above example, result is a promise.
\nIn nightmare, exists(), visible(), and evaluate() cannot be chained. These methods return promises that must be awaited.
\nDoesn't work:
\nnightmare.goto('http://www.example.com').exists('#Btn').wait(1000)
\nThe exists() method checks whether the element exists on the page and returns true or false.
\nWorks:
\nnightmare.goto('http://www.example.com').exists().then(exists => { console.log(exists) })
\nWith long nightmare sequences, async/await comes in handy. Let's say you need to login to Wordpress:
\nconst getSum = text => {\n var arr = text.split('+')\n var first = arr[0].replace(/[^\\d.]/g, '');\n var second = arr[1].replace(/[^\\d.]/g, '');\n return parseInt(first) + parseInt(second)\n}\nasync function login() {\n var text = await nightmare\n .goto(`${process.env.BASE_URL}/wp-admin`)\n .evaluate(() =>\n return document.querySelector(\"form#loginform div\").innerText;\n });\n var sum = getSum(text);\n var cookies = await nightmare\n .type(\"input#user_login\", process.env.LOGIN)\n .type(\"input#user_pass\", process.env.PASSWORD)\n .type('form#loginform div input[type=\"input\"]', sum)\n .wait(500)\n .click(\"p.submit input#wp-submit\")\n .wait(500)\n .wait('header.fl-page-header')\n .cookies.get();\n return cookies.find(cookie => cookie.secure === true && cookie.name !== 'wordpress_test_cookie')\n}
\nIn the above example, we send our electron instance to my-wordpress-site.com/wp-admin and attempt to login. But because we need to get the value of form#loginform div before proceeding, we need to break things up with async/await.
\nNormally the enclosing scope is included:
\nconst tree = 'spruce'\nconst getTree = () => {\n // getTree has access to enclosing scope\n return tree\n}\n//getTree() = spruce
\nWith evaluate() let's us switch to the client-side browser context. But you need to pass all variables explicitly.
\nWon't work:
\nconst tree = 'spruce'\nnightmare.evaluate(() => {\n // evaluate() doesn't have access to enclosing scope\n document.querySelector(\"input\").value = tree\n})
\nWorks:
\nconst tree = 'spruce'\nnightmare.evaluate((tree) => {\n document.querySelector(\"input\").value = tree\n}, tree)
\nYou can inject scripts (e.g., jQuery) with nightmare, but remember to inject prior to calling goto().
\n nightmare.inject('js', 'node_modules/jquery/dist/jquery.js')\n .wait(1000)\n .goto('http://www.example.com')\n .wait(1000)
\nHere are some useful recipes. I hope they come in handy!
\nconst scrollToElement = (selector) => {\n return nightmare.evaluate(selector => {\n document.getElementById(selector).getBoundingClientRect()\n var rect = document.getElementById(selector).getBoundingClientRect();\n var y = ((rect.bottom - rect.top) / 2) + rect.top;\n var x = ((rect.right - rect.left) / 2) + rect.left;\n window.scrollTo(x, y)\n }, selector)\n}\n// Use\nnightmare.goto('http://www.example.com')\n .scrollToElement('#some_element')\n .then(() => nightmare.end())
\nfunction zoom(n) {\n return nightmare\n .evaluate(n => {\n return document.body.style.zoom = `${n * 100}%`\n }, n)\n}\nawait nightmare.zoom(0.5)
","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfmi2nmvs01670pgebekn/nightmare.png"},{"id":"cjeqgfkqennn60199707yp1fb","title":"Why You Should Create Your Next React Web App With Firebase","slug":"firebase-react-tutorial","published_at":"2018-05-01T16:00:00","created_at":"2018-03-14T02:16:31","excerpt":"Firebase is the jam. No, really. Full disclosure: I'm not a developer and I was able to setup and c","image":{"id":"e9q7ffedfbvm3m21kc5pxvf9h","url":"static/cjeqgfkqennn60199707yp1fb/firebase.jpg"},"posts_tags":[{"tag":{"id":"z8dz890np7oyvt3d7vz2","name":"Firebase"}}],"date":"May 1, 2018","html":"Firebase is the jam. No, really. Full disclosure: I'm not a developer and I was able to setup and create a simple web app with Firebase in less than an hour. Firebase really is that easy to use.
\nIn this tutorial, I'll save you some headaches by mentioning a few tips that aren't covered in the Firebase docs.
\nI make the following assumptions in this tutorial:
\nAre you familiar with the MCV (model-view-controller) software design pattern?
\nReactJs advertises itself as just the view layer. So where do we put the app logic? Where do the model and controller layers belong?
\nFor starters, the architects of ReactJs were being deliberately humble. ReactJs is much more than a view layer; components are incredibly powerful.
\nNevertheless, if you want to create a ReactJs app that doesn't just reset every page refresh, then you need a database.
\nFirebase is a backend-as-a-service (BAAS). Firebase provides the backend (database and user authentication) for your app so that you can focus on the frontend aspects. This is particularly advantageous in my case because I wouldn't even know how to setup a backend for a ReactJs app.
\nFirebase stores your app's data as key-value pairs that are accessible with a simple API call.
\nLet's build a quick-and-dirty React app that uses Firebase as a backend.
\nWhat's the bare minimum that's needed to integrate firebase into your React app?
\nRather just fiddle with a sample React Firebase project than fiddle with a tutorial? Clone this repo.
\nAdd these lines to your component:
\nimport firebase from 'firebase';\nimport reactfire from 'reactfire';
\nAnd make sure that Firebase and Reactfire are installed:
\nnpm install firebase reactfire --save
\nChances are you have a gmail account, so you don't even need to sign up for anything. Just go to firebase.google.com and click sign up.
\n\nYou'll be prompted to create a new project. Click \"Add Firebase to your web app\" and Firebase will provide you with API keys.
\n\nGreat, we have our apiKey, authDomain, and databaseURL. Now what?
\nAdd this config object to your ReactJs component. Now our component looks something like:
\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport firebase from 'firebase';\nimport reactfire from 'reactfire';\n const config = {\n apiKey: \"YOUR_API_KEY\",\n authDomain: \"YOUR_AUTH_DOMAIN\",\n databaseURL: \"YOUR_DATABASE_URL\"\n};\nfirebase.initializeApp(config);\nclass App extends React.Component { \n render() {\n return (\n <H1>Happy coding!</H1>\n );\n }\n}
\nNote: Google will tell you to add the Reactfire and FireBase scripts to your html document when you first get your API keys. This obviously isn't necessary if you're importing these libraries at the beginning of your react component with import firebase from 'firebase'.
\nI omitted the storageBucket and messagingSenderId because we won't need them for this example.
\nWhat we've done so far:
\nnpm install firebase reactfire --save
)Pushing data to Firebase is super simple.
\nThe code that interacts with Firebase will live in the React lifecycle component componentWillMount. Unremarkably, this function is called whenever our component will mount.
\nAdd the following to your React component:
\ncomponentWillMount() { \n this.firebaseRef = firebase.database().ref(\"moaningmyrtle/items\");\n}\ncomponentWillUnmount() {\n this.firebaseRef.off();\n};
\nThis code identifies the database we want to use for our app by name (\"moaningmyrtle/items\"). I just made this string up and Firebase automatically structured my database according to it. Here's what I mean:
\n\nOnce we've setup our lifecycle components, we can push data to Firebase as follows:
\npushToFirebase() {\n this.firebaseRef.push({entry: this.state.text});\n}
\nIn this situation, imagine we stored some data like a string in this.state.text. Then we called pushToFirebase() when we were ready to add data to our database.
\nLet's test everything out by rendering a controlled input field. We'll enter text and watch it be pushed to Firebase.
\nHere's what the working component will look like:
\n\nWhat's going on here?
\nevent.preventDefault();
to prevent submitting an empty input field. On line 37, we reset our input field value. The function pushToFirebase(event)
is called with an onClick
event.You can navigate to the Firebase console to confirm that the data was populated.
\nWhat we covered in this section:
\ncomponentWillMount()
lifecycle hook.this.firebaseRef.push({entry: this.state.text}).
We pushed data to firebase, now how do we retrieve it?
\nLet's rewrite our componentWillMount() lifecycle function as follows:
\ncomponentWillMount() {\n this.firebaseRef = firebase.database().ref(\"moaningmyrtle/items\");\n this.firebaseRef.on('value', function(dataSnapshot) {\n var items = [];\n dataSnapshot.forEach(function(childSnapshot) {\n var item = childSnapshot.val();\n item['.key'] = childSnapshot.key;\n items.push(item);\n }.bind(this));\n this.setState({\n items: items\n });\n }.bind(this));\n };
\nWith dataSnapshot.forEach we loop through the the javascript objects in our database. You can read more about forEach() in the Firebase docs.
\nNext, we set each entry in our database to var item as follows:
\nvar item = childSnapshot.val();
\nIn addition to having a value, each childSnapshot has a key [\".key\"]. We include the childSnapshot keys in our objects with:
\nitem['.key'] = childSnapshot.key
\nWe can use these firebase database keys to avoid that pesky Reactjs error message:
\nWarning: Each child in an array or iterator should have a unique \"key\" prop.\n
Finally, we push the item object into an arbitrarily named array called items.
\nItems will now be an array of objects resembling the following:
\nvar items = [{\n KawNwox9GH8HQmcMg45: {text: 'some text'}\n}, {\n KawNxf670d_hgcwSUZ3: {text: 'some more text'}\n}];
\nWe can display data from Firebase with map.
\n// we stored our data from Firebase in this.state.items\nconst items = this.state.items;\n// iterate through the array of objects\nconst records = items.map((items) => \n <p key={items.key}>{items.text}</p>\n);
\nSee this github repo.
","avatar":"https://s3.amazonaws.com/contentkit/static/cjeqgfkqennn60199707yp1fb/firebase.jpg"}]}}}