Skip to content

Instantly share code, notes, and snippets.

@1gor
Forked from jgaskins/app.rb
Created February 16, 2019 13:35
Show Gist options
  • Save 1gor/a78e83fa1b3bc3c066b8c2c4c062ae04 to your computer and use it in GitHub Desktop.
Save 1gor/a78e83fa1b3bc3c066b8c2c4c062ae04 to your computer and use it in GitHub Desktop.
Compositional Components with Clearwater
require 'opal'
require 'clearwater'
require 'profile_page'
class Layout
include Clearwater::Component
def render
ProfilePage.new('yoxtb')
end
end
# Mount an app with a Layout component
Clearwater::Application.new(component: Layout.new).call
require 'clearwater/black_box_node'
require 'clearwater/application'
require 'bowser/http'
Fetch = Struct.new(:url, :content) do
# This mixin declares this component to be a black box.
# Clearwater will not try to update it. Instead, it will
# only notify the component to mount, update or clean up
# after itself.
# It is the only Clearwater component type to get notified
# about these events.
include Clearwater::BlackBoxNode
attr_reader :app
# This method is similar to componentWillMount (the DOM
# element is created, but it is not mounted into the DOM
# yet). We get the element
def mount element
# Because this is a black box, we either have to bring
# our own rendering or, in this case, we use another
# Clearwater app to take care of rendering for us.
@app = Clearwater::Application.new(component: content, element: element)
app.render
fetch
end
# This method is similar to React's componentDidReceiveProps, I think.
# It's not quite analogous because it's a whole new instance.
def update previous, element
fetch unless url == previous.url # Only get new data if the URL changed
# This is the tricky part. Since we get a new instance on every render,
# we copy over what we need from the previous instance. Not everything
# will need to be copied, usually just what was setup in the mount method.
@app = previous.app
app.render
end
# This is our last lifecycle hook, similar to React's componentWillUnmount.
def unmount element
end
def fetch
# We don't have setState, but the idea is that whatever component gets
# passed to this one will have setters for loading, data, and error.
content.loading = true
app.render
Bowser::HTTP.fetch(url)
.then(&:json)
.then do |data|
content.data = data
content.loading = false
app.render
end
.fail do |error|
content.error = 'Could not fetch remote data'
content.data = nil
content.loading = false
app.render
end
end
end
require 'clearwater/component'
require 'fetch'
require 'profile_view'
ProfilePage = Struct.new(:id) do
include Clearwater::Component
def render
Fetch.new(
"https://api.myjson.com/bins/#{id}",
ProfileView.new,
)
end
end
class ProfileView
include Clearwater::Component
attr_accessor :data, :error, :loading
def render
div({ class: 'some-view', dataset: { loading: loading } }, [
error && p({ style: { color: :red } }, error),
data && div({ class: 'profile' }, [
h1(data[:name]),
p(data[:bio]),
a({ href: data[:url], target: :_blank }, data[:urlName]),
]),
])
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment