view_component_reflex 3.1.12 → 3.1.13.pre0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 041ef93f09303abacb6ef4385394ede9d98bc8e9491a1083eb6478af27a8ce39
4
- data.tar.gz: d13347dac95081f9d3d79e3fdfee881bf51909c7a0c8fcc64f677f4d8454e6be
3
+ metadata.gz: 22eba7675ae2b684251bd022d9c36251d46ab049e56cc2bd380f8ae722466ec7
4
+ data.tar.gz: 85330cbbbab5788a0b3d651bed97dad7f0f4ba02b0df0953d9fb60517f9ac94c
5
5
  SHA512:
6
- metadata.gz: c90db4e5c4a2b9190b1cf3952569d261fdf83a373f94ec9821205f30c0b108c38f57bd352e8bd978f2183a365feeb6c2ece4f5ad8dbc25083e4a5814d14cf7ee
7
- data.tar.gz: 27be6c9f4c2b1920c7b36135cb4cc3920e1ccc78cf89c39be5ac9a823f5c6d9e18e847c466dee97032031f3ae03182967e6f4c8b00e09f85ace274f5a3be0526
6
+ metadata.gz: eb9d2028157211899974076bfe005cfe5c0f6de177943ceb5f16166bf4eb22fe29bbcc338ad695078953a25be5fe003ea60fea4ac39b8772d2764a98c7ce4111
7
+ data.tar.gz: a42866037d8da661c22e6dca64f187b62dd653bf7ad17ee360c7e7f3a690863840cb41d4888e9a36bfaef212c5b1ca548159b5a1ccc8c1e5f6d5e3202adc7aa5
data/MIT-LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- Copyright 2020 Joshua LeBlanc
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Copyright 2020 Joshua LeBlanc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,453 +1,453 @@
1
- # ViewComponentReflex
2
-
3
- ViewComponentReflex allows you to write reflexes right in your view component code.
4
-
5
- It builds upon [stimulus_reflex](https://github.com/hopsoft/stimulus_reflex) and [view_component](https://github.com/github/view_component)
6
-
7
- ## Usage
8
-
9
- You can add reflexes to your component by inheriting from `ViewComponentReflex::Component`.
10
-
11
- This will act as if you created a reflex with the method `my_cool_stuff`. To call this reflex, add `data-reflex="click->MyComponentReflex#my_cool_reflex"`, just like you're
12
- using stimulus reflex.
13
-
14
- ViewComponentReflex will maintain your component's instance variables between renders. You need to include `data-key=<%= key %>` on your root element, as well
15
- as any element that stimulates a reflex. ViewComponent is inherently state-less, so the key is used to reconcile state to its respective component.
16
-
17
- ### Example
18
- ```ruby
19
- # counter_component.rb
20
- class CounterComponent < ViewComponentReflex::Component
21
- def initialize
22
- @count = 0
23
- end
24
-
25
- def increment
26
- @count += 1
27
- end
28
- end
29
- ```
30
-
31
- ```erb
32
- # counter_component.html.erb
33
- <%= component_controller do %>
34
- <p><%= @count %></p>
35
- <%= reflex_tag :increment, :button, "Click" %>
36
- <% end %>
37
- ```
38
-
39
- ## Collections
40
-
41
- In order to reconcile state to components in collections, you can specify a `collection_key` method that returns some
42
- value unique to that component.
43
-
44
- ```ruby
45
- class TodoComponent < ViewComponentReflex::Component
46
- def initialize(todo:)
47
- @todo = todo
48
- end
49
-
50
- def collection_key
51
- @todo.id
52
- end
53
- end
54
- #
55
- <%= render(TodoComponent.with_collection(Todo.all)) %>
56
- ```
57
-
58
- In case you're rendering a collection of empty models, use a UUID of some sort to address the correct component instance on your page:
59
-
60
- ```ruby
61
- class TodoComponent < ViewComponentReflex::Component
62
- def initialize(todo:)
63
- @todo = todo
64
- end
65
-
66
- def collection_key
67
- @todo.id || SecureRandom.hex(16)
68
- end
69
- end
70
- #
71
- <%= render(TodoComponent.with_collection((0..5).map { Todo.new })) %>
72
- ```
73
-
74
- ## API
75
-
76
- ### permit_parameter?(initial_param, new_params)
77
- If a new parameter is passed to the component during rendering, it is used instead of what's in state.
78
- If you're storing instances in state, you can use this to properly compare them.
79
-
80
- ```ruby
81
- def permit_parameter?(initial_param, new_param)
82
- if new_param.instance_of? MyModel
83
- new_param.id == @my_model.id
84
- else
85
- super
86
- end
87
- end
88
- ```
89
-
90
- ### omitted_from_state
91
- Return an array of instance variables you want to omit from state. Only really useful if you're using the session state
92
- adapter, and you have an instance variable that can't be serialized.
93
-
94
- ```ruby
95
- def omitted_from_state
96
- [:@form]
97
- end
98
- ```
99
-
100
- ### reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
101
- This shares the same definition as `content_tag`, except it accepts a reflex as the first parameter.
102
-
103
- ```erb
104
- <%= reflex_tag :increment, :button, "Click me!" %>
105
- ```
106
-
107
- Would add a click handler to the `increment` method on your component.
108
-
109
- To use a non-click event, specific that with `->` notation
110
-
111
- ```erb
112
- <%= reflex_tag "mouseenter->increment", :button, "Click me!" %>
113
- ```
114
-
115
- ### reflex_data_attributes(reflex)
116
-
117
- This helper will give you the data attributes used in the reflex_tag above if you want to build your own elements.
118
-
119
- Build your own tag:
120
-
121
- ```erb
122
- <%= link_to (image_tag photo.image.url(:medium)), data: reflex_data_attributes(:increment) %>
123
- ```
124
-
125
- Render a ViewComponent
126
-
127
- ```erb
128
- <%= render ButtonComponent.new(data: reflex_data_attributes("mouseenter->increment")) %>
129
- ```
130
-
131
- Make sure that you assign the reflex_data_attributes to the correct element in your component.
132
-
133
- ### collection_key
134
- If you're rendering a component as a collection with `MyComponent.with_collection(SomeCollection)`, you must define this method to return some unique value for the component.
135
- This is used to reconcile state in the background.
136
-
137
- ```ruby
138
- def initialize
139
- @my_model = MyModel.new
140
- end
141
-
142
- def collection_key
143
- @my_model.id
144
- end
145
- ```
146
-
147
- ### stimulate(target, data)
148
- Stimulate another reflex from within your component. This typically requires the key of the component you're stimulating
149
- which can be passed in via parameters.
150
-
151
- ```ruby
152
- def initialize(parent_key)
153
- @parent_key = parent_key
154
- end
155
-
156
- def stimulate_other
157
- stimulate("OtherComponent#method", { key: @parent_key })
158
- end
159
- ```
160
-
161
- ### refresh!(selectors)
162
- Refresh a specific element on the page. Using this will implicitly run `prevent_render!`.
163
- If you want to render a specific element, as well as the component, a common pattern would be to pass `selector` as one of the parameters
164
-
165
- ```
166
- def my_method
167
- refresh! '#my-special-element', selector
168
- end
169
- ```
170
-
171
- ### selector
172
- Returns the unique selector for this component. Useful to pass to `refresh!` when refreshing custom elements.
173
-
174
- ### prevent_refresh!
175
- By default, VCR will re-render your component after it executes your method. `prevent_refresh!` prevents this from happening.
176
-
177
- ```ruby
178
- def my_method
179
- prevent_refresh!
180
- @foo = :bar
181
- end # the rendered page will not reflect this change
182
- ```
183
-
184
- ### refresh_all!
185
- Refresh the entire body of the page
186
-
187
- ```ruby
188
- def do_some_global_action
189
- prevent_refresh!
190
- session[:model] = MyModel.new
191
- refresh_all!
192
- end
193
- ```
194
-
195
- ### stream_to(channel)
196
- Stream to a custom channel, rather than the default stimulus reflex one
197
-
198
- ```ruby
199
- def do_something
200
- stream_to MyChannel
201
-
202
- @foo = :bar
203
- end
204
- ```
205
-
206
- ### key
207
- This is a key unique to a particular component. It's used to reconcile state between renders, and should be passed as a data attribute whenever a reflex is called
208
-
209
- ```erb
210
- <button type="button" data-reflex="click->MyComponent#do_something" data-key="<%= key %>">Click me!</button>
211
- ```
212
-
213
- ### component_controller(options = {}, &blk)
214
- This is a view helper to properly connect VCR to the component. It outputs `<div data-controller="my-controller" key=<%= key %></div>`
215
- You *must* wrap your component in this for everything to work properly.
216
-
217
- ```erb
218
- <%= component_controller do %>
219
- <p><%= @count %></p
220
- <% end %>
221
- ```
222
-
223
- ### after_state_initialized(parameters_changed)
224
-
225
- This is called after the state has been inserted in the component. You can use this to run conditional functions after
226
- some parameter has superseeded whatever's in state
227
-
228
- ```
229
- def after_state_initialized(parameters_changed)
230
- if parameters_changed.include?(:@filter)
231
- calculate_visible_rows
232
- end
233
- end
234
- ```
235
-
236
- ## Custom reflex base class
237
- Reflexes typically inherit from a base ApplicationReflex. You can define the base class for a view_component_reflex by using the `reflex_base_class` accessor.
238
- The parent class must inherit ViewComponentReflex::Reflex, and will throw an error if it does not.
239
-
240
- ```ruby
241
- class ApplicationReflex < ViewComponentReflex::Reflex
242
-
243
- end
244
-
245
-
246
- class MyComponent < ViewComponentReflex::Component
247
- MyComponent.reflex_base_class = ApplicationReflex
248
- end
249
- ```
250
-
251
- ## Common patterns
252
- A lot of the time, you only need to update specific components when changing instance variables. For example, changing `@loading` might only need
253
- to display a spinner somewhere on the page. You can define setters to implicitly render the appropriate pieces of dom whenever that variable is set
254
-
255
- ```ruby
256
- def initialize
257
- @loading = false
258
- end
259
-
260
- def loading=(new_value)
261
- @loading = new_value
262
- refresh! '#loader'
263
- end
264
-
265
- def do_expensive_action
266
- prevent_refresh!
267
-
268
- self.loading = true
269
- execute_it
270
- self.loading = false
271
- end
272
- ```
273
-
274
- ```erb
275
- <%= component_controller do %>
276
- <div id="loader">
277
- <% if @loading %>
278
- <p>Loading...</p>
279
- <% end %>
280
- </div>
281
-
282
- <button type="button" data-reflex="click->MyComponent#do_expensive_action" data-key="<%= key %>">Click me!</button>
283
- <% end
284
- ```
285
-
286
- ## State
287
-
288
- By default (since version `2.3.2`), view_component_reflex stores component state in session. You can optionally set the state adapter
289
- to use the memory by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Memory`.
290
-
291
- ## Custom State Adapters
292
-
293
- ViewComponentReflex uses session for its state by default. To change this, add
294
- an initializer to `config/initializers/view_component_reflex.rb`.
295
-
296
- ```ruby
297
- ViewComponentReflex::Engine.configure do |config|
298
- config.state_adapter = YourAdapter
299
- end
300
- ```
301
-
302
-
303
- ## Existing Fast Redis based State Adapter
304
-
305
- This adapter uses hmset and hgetall to reduce the number of operations.
306
- This is the recommended adapter if you are using AnyCable.
307
-
308
- ```ruby
309
- ViewComponentReflex::Engine.configure do |config|
310
- config.state_adapter = ViewComponentReflex::StateAdapter::Redis.new(
311
- redis_opts: {
312
- url: "redis://localhost:6379/1", driver: :hiredis
313
- },
314
- ttl: 3600)
315
- end
316
- ```
317
-
318
- `YourAdapter` should implement
319
-
320
- ```ruby
321
- class YourAdapter
322
- ##
323
- # request - a rails request object
324
- # key - a unique string that identifies the component instance
325
- def self.state(request, key)
326
- # Return state for a given key
327
- end
328
-
329
- ##
330
- # set_state is used to modify the state.
331
- #
332
- # request - a rails request object
333
- # controller - the current controller
334
- # key - a unique string that identifies the component
335
- # new_state - the new state to set
336
- def self.set_state(request, controller, key, new_state)
337
- # update the state
338
- end
339
-
340
-
341
- ##
342
- # store_state is used to replace the state entirely. It only accepts
343
- # a request object, rather than a reflex because it's called from the component's
344
- # side with the component's instance variables.
345
- #
346
- # request - a rails request object
347
- # key - a unique string that identifies the component instance
348
- # new_state - a hash containing the component state
349
- def self.store_state(request, key, new_state = {})
350
- # replace the state
351
- end
352
- end
353
- ```
354
-
355
-
356
- ## Installation
357
- Add this line to your application's Gemfile:
358
-
359
- ```ruby
360
- gem 'view_component_reflex'
361
- ```
362
-
363
- And then execute:
364
- ```bash
365
- $ bundle
366
- ```
367
-
368
- Or install it yourself as:
369
- ```bash
370
- $ gem install view_component_reflex
371
- ```
372
-
373
- # Common problems
374
-
375
- ## Uninitialized constants \<component\>Reflex
376
- A component needs to be wrapped in `<%= component_controller do %>` in order to properly initialize, otherwise the Reflex class won't get created.
377
-
378
- ## Session is an empty hash
379
- StimulusReflex 3.3 introduced _selector morphs_, allowing you to render arbitrary strings via `ApplicationController.render`, for example:
380
-
381
- ```rb
382
- def test_selector
383
- morph '#some-container', ApplicationController.render(MyComponent.new(some: :param))
384
- end
385
- ```
386
-
387
- StimulusReflex 3.4 introduced a fix that merges the current `request.env` and provides the CSRF token to fetch the session.
388
-
389
- ## Help, my instance variables do not persist into the session
390
-
391
- These instance variable names are not working and unsafe:
392
-
393
- ```rb
394
- def unsafe_instance_variables
395
- [
396
- :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
397
- :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
398
- :@helpers, :@controller, :@request, :@tag_builder, :@initialized_state
399
- ]
400
- end
401
- ```
402
- Please use a different name to be able to save them to the session.
403
-
404
- ## Foo Can't Be Dumped
405
-
406
- If you are getting errors that e.g. MatchData, Singleton etc. can't be dumped, ensure that you do not set any instance variables in your components (or any class you inject into them, for that matter) that cannot be marshaled.
407
-
408
- This can be easily remedied though, by providing a list of unmarshalable instance variables and overwriting `marshal_dump` and `marshal_load` (from [https://stackoverflow.com/a/32877159/4341756](https://stackoverflow.com/a/32877159/4341756)):
409
-
410
- ```rb
411
- class MarshalTest
412
- UNMARSHALED_VARIABLES = [:@foo, :@bar]
413
-
414
- def marshal_dump
415
- instance_variables.reject{|m| UNMARSHALED_VARIABLES.include? m}.inject({}) do |vars, attr|
416
- vars[attr] = instance_variable_get(attr)
417
- vars
418
- end
419
- end
420
-
421
- def marshal_load(vars)
422
- vars.each do |attr, value|
423
- instance_variable_set(attr, value) unless UNMARSHALED_VARIABLES.include?(attr)
424
- end
425
- end
426
- end
427
- ```
428
-
429
- ## Anycable
430
-
431
- @sebyx07 provided a solution to use anycable (https://github.com/joshleblanc/view_component_reflex/issues/23#issue-721786338)
432
-
433
- Leaving this, might help others:
434
-
435
- I tried this with any cable and I had to add this to development.rb
436
- Otherwise @instance_variables were nil after a reflex
437
-
438
- ```ruby
439
- config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/1", driver: :hiredis }
440
- config.session_store :cache_store
441
- ```
442
-
443
- ## License
444
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
445
-
446
- ## Caveats
447
-
448
- State uses session to maintain state as of right now. It also assumes your component view is written with a file extension of either `.html.erb`, `.html.haml` or `.html.slim`.
449
-
450
- ## Support me
451
-
452
- <a href="https://www.buymeacoffee.com/jleblanc" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="40" ></a>
453
-
1
+ # ViewComponentReflex
2
+
3
+ ViewComponentReflex allows you to write reflexes right in your view component code.
4
+
5
+ It builds upon [stimulus_reflex](https://github.com/hopsoft/stimulus_reflex) and [view_component](https://github.com/github/view_component)
6
+
7
+ ## Usage
8
+
9
+ You can add reflexes to your component by inheriting from `ViewComponentReflex::Component`.
10
+
11
+ This will act as if you created a reflex with the method `my_cool_stuff`. To call this reflex, add `data-reflex="click->MyComponentReflex#my_cool_reflex"`, just like you're
12
+ using stimulus reflex.
13
+
14
+ ViewComponentReflex will maintain your component's instance variables between renders. You need to include `data-key=<%= key %>` on your root element, as well
15
+ as any element that stimulates a reflex. ViewComponent is inherently state-less, so the key is used to reconcile state to its respective component.
16
+
17
+ ### Example
18
+ ```ruby
19
+ # counter_component.rb
20
+ class CounterComponent < ViewComponentReflex::Component
21
+ def initialize
22
+ @count = 0
23
+ end
24
+
25
+ def increment
26
+ @count += 1
27
+ end
28
+ end
29
+ ```
30
+
31
+ ```erb
32
+ # counter_component.html.erb
33
+ <%= component_controller do %>
34
+ <p><%= @count %></p>
35
+ <%= reflex_tag :increment, :button, "Click" %>
36
+ <% end %>
37
+ ```
38
+
39
+ ## Collections
40
+
41
+ In order to reconcile state to components in collections, you can specify a `collection_key` method that returns some
42
+ value unique to that component.
43
+
44
+ ```ruby
45
+ class TodoComponent < ViewComponentReflex::Component
46
+ def initialize(todo:)
47
+ @todo = todo
48
+ end
49
+
50
+ def collection_key
51
+ @todo.id
52
+ end
53
+ end
54
+ #
55
+ <%= render(TodoComponent.with_collection(Todo.all)) %>
56
+ ```
57
+
58
+ In case you're rendering a collection of empty models, use a UUID of some sort to address the correct component instance on your page:
59
+
60
+ ```ruby
61
+ class TodoComponent < ViewComponentReflex::Component
62
+ def initialize(todo:)
63
+ @todo = todo
64
+ end
65
+
66
+ def collection_key
67
+ @todo.id || SecureRandom.hex(16)
68
+ end
69
+ end
70
+ #
71
+ <%= render(TodoComponent.with_collection((0..5).map { Todo.new })) %>
72
+ ```
73
+
74
+ ## API
75
+
76
+ ### permit_parameter?(initial_param, new_params)
77
+ If a new parameter is passed to the component during rendering, it is used instead of what's in state.
78
+ If you're storing instances in state, you can use this to properly compare them.
79
+
80
+ ```ruby
81
+ def permit_parameter?(initial_param, new_param)
82
+ if new_param.instance_of? MyModel
83
+ new_param.id == @my_model.id
84
+ else
85
+ super
86
+ end
87
+ end
88
+ ```
89
+
90
+ ### omitted_from_state
91
+ Return an array of instance variables you want to omit from state. Only really useful if you're using the session state
92
+ adapter, and you have an instance variable that can't be serialized.
93
+
94
+ ```ruby
95
+ def omitted_from_state
96
+ [:@form]
97
+ end
98
+ ```
99
+
100
+ ### reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
101
+ This shares the same definition as `content_tag`, except it accepts a reflex as the first parameter.
102
+
103
+ ```erb
104
+ <%= reflex_tag :increment, :button, "Click me!" %>
105
+ ```
106
+
107
+ Would add a click handler to the `increment` method on your component.
108
+
109
+ To use a non-click event, specific that with `->` notation
110
+
111
+ ```erb
112
+ <%= reflex_tag "mouseenter->increment", :button, "Click me!" %>
113
+ ```
114
+
115
+ ### reflex_data_attributes(reflex)
116
+
117
+ This helper will give you the data attributes used in the reflex_tag above if you want to build your own elements.
118
+
119
+ Build your own tag:
120
+
121
+ ```erb
122
+ <%= link_to (image_tag photo.image.url(:medium)), data: reflex_data_attributes(:increment) %>
123
+ ```
124
+
125
+ Render a ViewComponent
126
+
127
+ ```erb
128
+ <%= render ButtonComponent.new(data: reflex_data_attributes("mouseenter->increment")) %>
129
+ ```
130
+
131
+ Make sure that you assign the reflex_data_attributes to the correct element in your component.
132
+
133
+ ### collection_key
134
+ If you're rendering a component as a collection with `MyComponent.with_collection(SomeCollection)`, you must define this method to return some unique value for the component.
135
+ This is used to reconcile state in the background.
136
+
137
+ ```ruby
138
+ def initialize
139
+ @my_model = MyModel.new
140
+ end
141
+
142
+ def collection_key
143
+ @my_model.id
144
+ end
145
+ ```
146
+
147
+ ### stimulate(target, data)
148
+ Stimulate another reflex from within your component. This typically requires the key of the component you're stimulating
149
+ which can be passed in via parameters.
150
+
151
+ ```ruby
152
+ def initialize(parent_key)
153
+ @parent_key = parent_key
154
+ end
155
+
156
+ def stimulate_other
157
+ stimulate("OtherComponent#method", { key: @parent_key })
158
+ end
159
+ ```
160
+
161
+ ### refresh!(selectors)
162
+ Refresh a specific element on the page. Using this will implicitly run `prevent_render!`.
163
+ If you want to render a specific element, as well as the component, a common pattern would be to pass `selector` as one of the parameters
164
+
165
+ ```
166
+ def my_method
167
+ refresh! '#my-special-element', selector
168
+ end
169
+ ```
170
+
171
+ ### selector
172
+ Returns the unique selector for this component. Useful to pass to `refresh!` when refreshing custom elements.
173
+
174
+ ### prevent_refresh!
175
+ By default, VCR will re-render your component after it executes your method. `prevent_refresh!` prevents this from happening.
176
+
177
+ ```ruby
178
+ def my_method
179
+ prevent_refresh!
180
+ @foo = :bar
181
+ end # the rendered page will not reflect this change
182
+ ```
183
+
184
+ ### refresh_all!
185
+ Refresh the entire body of the page
186
+
187
+ ```ruby
188
+ def do_some_global_action
189
+ prevent_refresh!
190
+ session[:model] = MyModel.new
191
+ refresh_all!
192
+ end
193
+ ```
194
+
195
+ ### stream_to(channel)
196
+ Stream to a custom channel, rather than the default stimulus reflex one
197
+
198
+ ```ruby
199
+ def do_something
200
+ stream_to MyChannel
201
+
202
+ @foo = :bar
203
+ end
204
+ ```
205
+
206
+ ### key
207
+ This is a key unique to a particular component. It's used to reconcile state between renders, and should be passed as a data attribute whenever a reflex is called
208
+
209
+ ```erb
210
+ <button type="button" data-reflex="click->MyComponent#do_something" data-key="<%= key %>">Click me!</button>
211
+ ```
212
+
213
+ ### component_controller(options = {}, &blk)
214
+ This is a view helper to properly connect VCR to the component. It outputs `<div data-controller="my-controller" key=<%= key %></div>`
215
+ You *must* wrap your component in this for everything to work properly.
216
+
217
+ ```erb
218
+ <%= component_controller do %>
219
+ <p><%= @count %></p
220
+ <% end %>
221
+ ```
222
+
223
+ ### after_state_initialized(parameters_changed)
224
+
225
+ This is called after the state has been inserted in the component. You can use this to run conditional functions after
226
+ some parameter has superseeded whatever's in state
227
+
228
+ ```
229
+ def after_state_initialized(parameters_changed)
230
+ if parameters_changed.include?(:@filter)
231
+ calculate_visible_rows
232
+ end
233
+ end
234
+ ```
235
+
236
+ ## Custom reflex base class
237
+ Reflexes typically inherit from a base ApplicationReflex. You can define the base class for a view_component_reflex by using the `reflex_base_class` accessor.
238
+ The parent class must inherit ViewComponentReflex::Reflex, and will throw an error if it does not.
239
+
240
+ ```ruby
241
+ class ApplicationReflex < ViewComponentReflex::Reflex
242
+
243
+ end
244
+
245
+
246
+ class MyComponent < ViewComponentReflex::Component
247
+ MyComponent.reflex_base_class = ApplicationReflex
248
+ end
249
+ ```
250
+
251
+ ## Common patterns
252
+ A lot of the time, you only need to update specific components when changing instance variables. For example, changing `@loading` might only need
253
+ to display a spinner somewhere on the page. You can define setters to implicitly render the appropriate pieces of dom whenever that variable is set
254
+
255
+ ```ruby
256
+ def initialize
257
+ @loading = false
258
+ end
259
+
260
+ def loading=(new_value)
261
+ @loading = new_value
262
+ refresh! '#loader'
263
+ end
264
+
265
+ def do_expensive_action
266
+ prevent_refresh!
267
+
268
+ self.loading = true
269
+ execute_it
270
+ self.loading = false
271
+ end
272
+ ```
273
+
274
+ ```erb
275
+ <%= component_controller do %>
276
+ <div id="loader">
277
+ <% if @loading %>
278
+ <p>Loading...</p>
279
+ <% end %>
280
+ </div>
281
+
282
+ <button type="button" data-reflex="click->MyComponent#do_expensive_action" data-key="<%= key %>">Click me!</button>
283
+ <% end
284
+ ```
285
+
286
+ ## State
287
+
288
+ By default (since version `2.3.2`), view_component_reflex stores component state in session. You can optionally set the state adapter
289
+ to use the memory by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Memory`.
290
+
291
+ ## Custom State Adapters
292
+
293
+ ViewComponentReflex uses session for its state by default. To change this, add
294
+ an initializer to `config/initializers/view_component_reflex.rb`.
295
+
296
+ ```ruby
297
+ ViewComponentReflex::Engine.configure do |config|
298
+ config.state_adapter = YourAdapter
299
+ end
300
+ ```
301
+
302
+
303
+ ## Existing Fast Redis based State Adapter
304
+
305
+ This adapter uses hmset and hgetall to reduce the number of operations.
306
+ This is the recommended adapter if you are using AnyCable.
307
+
308
+ ```ruby
309
+ ViewComponentReflex::Engine.configure do |config|
310
+ config.state_adapter = ViewComponentReflex::StateAdapter::Redis.new(
311
+ redis_opts: {
312
+ url: "redis://localhost:6379/1", driver: :hiredis
313
+ },
314
+ ttl: 3600)
315
+ end
316
+ ```
317
+
318
+ `YourAdapter` should implement
319
+
320
+ ```ruby
321
+ class YourAdapter
322
+ ##
323
+ # request - a rails request object
324
+ # key - a unique string that identifies the component instance
325
+ def self.state(request, key)
326
+ # Return state for a given key
327
+ end
328
+
329
+ ##
330
+ # set_state is used to modify the state.
331
+ #
332
+ # request - a rails request object
333
+ # controller - the current controller
334
+ # key - a unique string that identifies the component
335
+ # new_state - the new state to set
336
+ def self.set_state(request, controller, key, new_state)
337
+ # update the state
338
+ end
339
+
340
+
341
+ ##
342
+ # store_state is used to replace the state entirely. It only accepts
343
+ # a request object, rather than a reflex because it's called from the component's
344
+ # side with the component's instance variables.
345
+ #
346
+ # request - a rails request object
347
+ # key - a unique string that identifies the component instance
348
+ # new_state - a hash containing the component state
349
+ def self.store_state(request, key, new_state = {})
350
+ # replace the state
351
+ end
352
+ end
353
+ ```
354
+
355
+
356
+ ## Installation
357
+ Add this line to your application's Gemfile:
358
+
359
+ ```ruby
360
+ gem 'view_component_reflex'
361
+ ```
362
+
363
+ And then execute:
364
+ ```bash
365
+ $ bundle
366
+ ```
367
+
368
+ Or install it yourself as:
369
+ ```bash
370
+ $ gem install view_component_reflex
371
+ ```
372
+
373
+ # Common problems
374
+
375
+ ## Uninitialized constants \<component\>Reflex
376
+ A component needs to be wrapped in `<%= component_controller do %>` in order to properly initialize, otherwise the Reflex class won't get created.
377
+
378
+ ## Session is an empty hash
379
+ StimulusReflex 3.3 introduced _selector morphs_, allowing you to render arbitrary strings via `ApplicationController.render`, for example:
380
+
381
+ ```rb
382
+ def test_selector
383
+ morph '#some-container', ApplicationController.render(MyComponent.new(some: :param))
384
+ end
385
+ ```
386
+
387
+ StimulusReflex 3.4 introduced a fix that merges the current `request.env` and provides the CSRF token to fetch the session.
388
+
389
+ ## Help, my instance variables do not persist into the session
390
+
391
+ These instance variable names are not working and unsafe:
392
+
393
+ ```rb
394
+ def unsafe_instance_variables
395
+ [
396
+ :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
397
+ :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
398
+ :@helpers, :@controller, :@request, :@tag_builder, :@initialized_state
399
+ ]
400
+ end
401
+ ```
402
+ Please use a different name to be able to save them to the session.
403
+
404
+ ## Foo Can't Be Dumped
405
+
406
+ If you are getting errors that e.g. MatchData, Singleton etc. can't be dumped, ensure that you do not set any instance variables in your components (or any class you inject into them, for that matter) that cannot be marshaled.
407
+
408
+ This can be easily remedied though, by providing a list of unmarshalable instance variables and overwriting `marshal_dump` and `marshal_load` (from [https://stackoverflow.com/a/32877159/4341756](https://stackoverflow.com/a/32877159/4341756)):
409
+
410
+ ```rb
411
+ class MarshalTest
412
+ UNMARSHALED_VARIABLES = [:@foo, :@bar]
413
+
414
+ def marshal_dump
415
+ instance_variables.reject{|m| UNMARSHALED_VARIABLES.include? m}.inject({}) do |vars, attr|
416
+ vars[attr] = instance_variable_get(attr)
417
+ vars
418
+ end
419
+ end
420
+
421
+ def marshal_load(vars)
422
+ vars.each do |attr, value|
423
+ instance_variable_set(attr, value) unless UNMARSHALED_VARIABLES.include?(attr)
424
+ end
425
+ end
426
+ end
427
+ ```
428
+
429
+ ## Anycable
430
+
431
+ @sebyx07 provided a solution to use anycable (https://github.com/joshleblanc/view_component_reflex/issues/23#issue-721786338)
432
+
433
+ Leaving this, might help others:
434
+
435
+ I tried this with any cable and I had to add this to development.rb
436
+ Otherwise @instance_variables were nil after a reflex
437
+
438
+ ```ruby
439
+ config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/1", driver: :hiredis }
440
+ config.session_store :cache_store
441
+ ```
442
+
443
+ ## License
444
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
445
+
446
+ ## Caveats
447
+
448
+ State uses session to maintain state as of right now. It also assumes your component view is written with a file extension of either `.html.erb`, `.html.haml` or `.html.slim`.
449
+
450
+ ## Support me
451
+
452
+ <a href="https://www.buymeacoffee.com/jleblanc" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="40" ></a>
453
+