module Hyperstack module Internal module Component # # React assumes all components should update, unless a component explicitly overrides # the shouldComponentUpdate method. Reactrb does an explicit check doing a shallow # compare of params, and using a timestamp to determine if state has changed. # If needed components can provide their own #needs_update? method which will be # passed the next params and state opal hashes. # Attached to these hashes is a #changed? method that returns whether the hash contains # changes as calculated by the base mechanism. This way implementations of #needs_update? # can use the base comparison mechanism as needed. # For example # def needs_update?(next_params, next_state) # # use a special comparison method # return false if next_state.changed? || next_params.changed? # # do some other special checks # end # Note that beginning in 0.9 we will use standard ruby compare on all params further reducing # the need for needs_update? # module ShouldComponentUpdate def should_component_update?(next_props, next_state) observing do # rubocop:disable Style/DoubleNegation # we must return true/false to js land if respond_to?(:needs_update?) !!call_needs_update(next_props, next_state) else props_changed?(next_props) || native_state_changed?(next_state) end # rubocop:enable Style/DoubleNegation end end # create opal hashes for next params and state, and attach # the changed? method to each hash def call_needs_update(next_params, next_state) component = self next_params.define_singleton_method(:changed?) do component.props_changed?(self) end next_state.define_singleton_method(:changed?) do component.native_state_changed?(next_state) end needs_update?(next_params, next_state) end # Whenever state changes, reactrb updates a timestamp on the state object. # We can rapidly check for state changes comparing the incoming state time_stamp # with the current time stamp. # we receive a Opal Ruby Hash here, always, so the Hash is either empty or filled # Hash is converted to native object # if the Hash was empty, the Object has no keys # Different versions of react treat empty state differently, so we first # convert anything that looks like an empty state to "false" for consistency. # Then we test if one state is empty and the other is not, then we return false. # Then we test if both states are empty we return true. # If either state does not have a time stamp then we have to assume a change. # Otherwise we check time stamps # rubocop:disable Metrics/MethodLength # for effeciency we want this to be one method def native_state_changed?(next_state_hash) # next_state = next_state_hash.to_n # %x{ # var current_state = #{@__hyperstack_component_native}.state # var normalized_next_state = # !next_state || Object.keys(next_state).length === 0 ? false : next_state # var normalized_current_state = # !current_state || Object.keys(current_state).length === 0 ? false : current_state # if (!normalized_current_state != !normalized_next_state) return(true) # if (!normalized_current_state && !normalized_next_state) return(false) # if (!normalized_current_state['***_state_updated_at-***'] && # !normalized_next_state['***_state_updated_at-***']) return(false) # if (!normalized_current_state['***_state_updated_at-***'] || # !normalized_next_state['***_state_updated_at-***']) return(true) # return (normalized_current_state['***_state_updated_at-***'] != # normalized_next_state['***_state_updated_at-***']) # } state_hash = Hash.new(`#{@__hyperstack_component_native}.state`) next_state_hash != state_hash end # rubocop:enable Metrics/MethodLength # Do a shallow compare on the two hashes. Starting in 0.9 we will do a deep compare. ??? def props_changed?(next_props) props = Hash.new(`#{@__hyperstack_component_native}.props`) next_props != props end end end end end