|
| 1 | +module React |
| 2 | + module Component |
| 3 | + # |
| 4 | + # React assumes all components should update, unless a component explicitly overrides |
| 5 | + # the shouldComponentUpdate method. Reactrb does an explicit check doing a shallow |
| 6 | + # compare of params, and using a timestamp to determine if state has changed. |
| 7 | + |
| 8 | + # If needed components can provide their own #needs_update? method which will be |
| 9 | + # passed the next params and state opal hashes. |
| 10 | + |
| 11 | + # Attached to these hashes is a #changed? method that returns whether the hash contains |
| 12 | + # changes as calculated by the base mechanism. This way implementations of #needs_update? |
| 13 | + # can use the base comparison mechanism as needed. |
| 14 | + |
| 15 | + # For example |
| 16 | + # def needs_update?(next_params, next_state) |
| 17 | + # # use a special comparison method |
| 18 | + # return false if next_state.changed? || next_params.changed? |
| 19 | + # # do some other special checks |
| 20 | + # end |
| 21 | + |
| 22 | + # Note that beginning in 0.9 we will use standard ruby compare on all params further reducing |
| 23 | + # the need for needs_update? |
| 24 | + # |
| 25 | + module ShouldComponentUpdate |
| 26 | + def should_component_update?(native_next_props, native_next_state) |
| 27 | + State.set_state_context_to(self) do |
| 28 | + next_params = Hash.new(native_next_props) |
| 29 | + # rubocop:disable Style/DoubleNegation # we must return true/false to js land |
| 30 | + if respond_to?(:needs_update?) |
| 31 | + !!call_needs_update(next_params, native_next_state) |
| 32 | + else |
| 33 | + !!(props_changed?(next_params) || native_state_changed?(native_next_state)) |
| 34 | + end |
| 35 | + # rubocop:enable Style/DoubleNegation |
| 36 | + end |
| 37 | + end |
| 38 | + |
| 39 | + # create opal hashes for next params and state, and attach |
| 40 | + # the changed? method to each hash |
| 41 | + |
| 42 | + def call_needs_update(next_params, native_next_state) |
| 43 | + component = self |
| 44 | + next_params.define_singleton_method(:changed?) do |
| 45 | + component.props_changed?(self) |
| 46 | + end |
| 47 | + next_state = Hash.new(native_next_state) |
| 48 | + next_state.define_singleton_method(:changed?) do |
| 49 | + component.native_state_changed?(native_next_state) |
| 50 | + end |
| 51 | + needs_update?(next_params, next_state) |
| 52 | + end |
| 53 | + |
| 54 | + # Whenever state changes, reactrb updates a timestamp on the state object. |
| 55 | + # We can rapidly check for state changes comparing the incoming state time_stamp |
| 56 | + # with the current time stamp. |
| 57 | + |
| 58 | + # Different versions of react treat empty state differently, so we first |
| 59 | + # convert anything that looks like an empty state to "false" for consistency. |
| 60 | + |
| 61 | + # Then we test if one state is empty and the other is not, then we return false. |
| 62 | + # Then we test if both states are empty we return true. |
| 63 | + # If either state does not have a time stamp then we have to assume a change. |
| 64 | + # Otherwise we check time stamps |
| 65 | + |
| 66 | + # rubocop:disable Metrics/MethodLength # for effeciency we want this to be one method |
| 67 | + def native_state_changed?(next_state) |
| 68 | + %x{ |
| 69 | + var current_state = #{@native}.state |
| 70 | + var normalized_next_state = |
| 71 | + !#{next_state} || Object.keys(#{next_state}).length === 0 || #{nil} == #{next_state} ? |
| 72 | + false : #{next_state} |
| 73 | + var normalized_current_state = |
| 74 | + !current_state || Object.keys(current_state).length === 0 || #{nil} == current_state ? |
| 75 | + false : current_state |
| 76 | + if (!normalized_current_state != !normalized_next_state) return(true) |
| 77 | + if (!normalized_current_state && !normalized_next_state) return(false) |
| 78 | + if (!normalized_current_state['***_state_updated_at-***'] || |
| 79 | + !normalized_next_state['***_state_updated_at-***']) return(true) |
| 80 | + return (normalized_current_state['***_state_updated_at-***'] != |
| 81 | + normalized_next_state['***_state_updated_at-***']) |
| 82 | + } |
| 83 | + end |
| 84 | + # rubocop:enable Metrics/MethodLength |
| 85 | + |
| 86 | + # Do a shallow compare on the two hashes. Starting in 0.9 we will do a deep compare. |
| 87 | + |
| 88 | + def props_changed?(next_params) |
| 89 | + Component.deprecation_warning( |
| 90 | + "Using shallow incoming params comparison.\n"\ |
| 91 | + 'Do a require "reactrb/deep-compare, to get 0.9 behavior' |
| 92 | + ) |
| 93 | + (props.keys.sort != next_params.keys.sort) || |
| 94 | + next_params.detect { |k, v| `#{v} != #{@native}.props[#{k}]` } |
| 95 | + end |
| 96 | + end |
| 97 | + end |
| 98 | +end |
0 commit comments