ruby-hyperloop / hyper-react

The project has moved to Hyperstack!!
https://hyperstack.org/
MIT License
285 stars 14 forks source link

Stateless Components #104

Open sollycatprint opened 8 years ago

sollycatprint commented 8 years ago

From @catmando on December 15, 2015 17:8

React now has this notion of a "stateless" component, by which they mean a JS function that takes the "props" as it param, and generates an element.

Its stateless because there is no react state of any sort.

Just wondering if we should bother and what it would look like?

I guess there might be some value? Consider this:

class Foo < React::Component::Base

   def search_tag(url, name)
      a(href: "https://www.google.com/#q=#{name}") { name }
   end

   def render
      div { search_tag("Joe") }
   end
end

and now this:

class SearchTag < React::Component::Base
  param :name, type: String 
  def render
     a(href: "https://www.google.com/#q=#{params.name}") { params.name }
  end
end

class Foo < React::Component::Base
  ... assume Foo has some parameters and/or state
   def render
        ... assume Foo is rendering some other stuff too
          div { SearchTag("Joe") }
   end
end

In the second case, we will never re-render SearchTag, because its param never changes, and it has no state. In the first case everytime Foo is rendered so is the anchor tag.

So I see the value, but I am not sure how to ruby-ize this...

Copied from original issue: zetachang/react.rb#104

sollycatprint commented 8 years ago

From @derikson on February 7, 2016 10:47

Just as in JavaScript, stateless components could be created in Ruby as functions. Consider:

def SearchTag(name:)
  a(href: "https://www.google.com/#q=#{name}") { name }
end

# optional
React::Component.param_types(:SearchTag) do
  param :name, type: String
end
sollycatprint commented 8 years ago

From @catmando on February 8, 2016 22:31

@derikson - good point. The difficulty I think is more "syntactic" as in how to get this to all work nicely within the DSL, such that its easy to create stateless components.

A lot of the DSL mechanism comes along with being inside of a React::Component::Base class, so its not clear how to declare a component without declaring the class wrapper, and a render method.

Some important background: In 0.8 (or 0.9 maybe) we will be merging the "define_state" and "export_state" macros to a single "state" macro and making it mandantory (currently its optional for non exported states.)

Also in 0.8 you will have to prefix params and state with the "params" and "state" methods.

Given this:

One approach would be to have a class React::Component::StatelessBase that simply removes the state macro, and has no state instance method. An exception would be raised if the component accessed any external state.

class SearchTag < React::Component::Stateless
  param: name, type: String
  def render
    a(href: "https://www.google.com/#q=#{params.name}") { params.name }
  end
end

its a lot of boiler plate.... how about

React::Component.define(:SearchTag) do 
   param :name, type: String
   a(href: "https://www.google.com/#q=#{params.name}") { params.name }
end

I think it would be possible to implement this...

catmando commented 7 years ago

I'm closing this... Its easy enough in ruby to just have an include file with your stateless methods.

catmando commented 7 years ago

after thinking about this I realize that a stateless method is not the same thing as a stateless component. A stateless method will get re-rerendered even if its params do not change.

How about this syntax (using kwargs)

class SearchTag < React::Component::Stateless
  def render(name:)
    a(href: "https://www.google.com/#q=#{name}") { name }
  end
end

This could also be written in some other class or module as

React::Component.define(:SearchTag) do |:name|
   a(href: "https://www.google.com/#q=#{name}") { name }
end

and even just

define(:SearchTag) { |:name| a(href: "https://www.google.com/#q=#{name}") { name } }

within a React::Component

zetachang commented 7 years ago

Not sure about the first one, the method signature is different than other API we got, so it might be confusing to users.

Hyalite using API like this to implement stateless component,

FunctionComponent =  Hyalite.fn {|prrops| div({className: 'actual'}, props[:value]) }
render do
    FunctionComponent.el(value: 'value')
 end

https://github.com/youchan/hyalite/blob/master/spec/render_spec.rb#L61