lib/react/component/should_component_update.rb
module React
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?(native_next_props, native_next_state)
State.set_state_context_to(self, false) do
next_params = Hash.new(native_next_props)
# rubocop:disable Style/DoubleNegation # we must return true/false to js land
if respond_to?(:needs_update?)
!!call_needs_update(next_params, native_next_state)
else
!!(props_changed?(next_params) || native_state_changed?(native_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, native_next_state)
component = self
next_params.define_singleton_method(:changed?) do
component.props_changed?(self)
end
next_state = Hash.new(native_next_state)
next_state.define_singleton_method(:changed?) do
component.native_state_changed?(native_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.
# 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)
%x{
var current_state = #{@native}.state
var normalized_next_state =
!#{next_state} || Object.keys(#{next_state}).length === 0 || #{nil} == #{next_state} ?
false : #{next_state}
var normalized_current_state =
!current_state || Object.keys(current_state).length === 0 || #{nil} == current_state ?
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(true)
return (normalized_current_state['***_state_updated_at-***'] !=
normalized_next_state['***_state_updated_at-***'])
}
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_params)
(props.keys.sort != next_params.keys.sort) ||
next_params.detect { |k, v| `#{v} != #{@native}.props[#{k}]` }
end
end
end
end