view_component_reflex 3.1.11 → 3.1.12

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: 447c5a4a1ce73910929aa5aca37f8e09ae0469fc80aa042057dbf91e50d8b756
4
- data.tar.gz: a6a47cb68cc385fd1d4de4b06a895b94a86ef7f624c796bc883cbb2c43716d46
3
+ metadata.gz: 041ef93f09303abacb6ef4385394ede9d98bc8e9491a1083eb6478af27a8ce39
4
+ data.tar.gz: d13347dac95081f9d3d79e3fdfee881bf51909c7a0c8fcc64f677f4d8454e6be
5
5
  SHA512:
6
- metadata.gz: 00e76451e67b57889f752384a1abc06251cdd62265af723a2474a25542390a7572355a3de90db866cdde2fbdb5921501acfbc335629134995cb191d8f5c75560
7
- data.tar.gz: bdb1220f50503c1d83811545a5694f50eff9fb9013de619138c9ff1c26e9906d275ff98fb65b7b7dc0a8a4f4d6fe963baf535da40e87c907091cec497985d18c
6
+ metadata.gz: c90db4e5c4a2b9190b1cf3952569d261fdf83a373f94ec9821205f30c0b108c38f57bd352e8bd978f2183a365feeb6c2ece4f5ad8dbc25083e4a5814d14cf7ee
7
+ data.tar.gz: 27be6c9f4c2b1920c7b36135cb4cc3920e1ccc78cf89c39be5ac9a823f5c6d9e18e847c466dee97032031f3ae03182967e6f4c8b00e09f85ace274f5a3be0526
data/README.md CHANGED
@@ -1,412 +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
- ## API
59
-
60
- ### permit_parameter?(initial_param, new_params)
61
- If a new parameter is passed to the component during rendering, it is used instead of what's in state.
62
- If you're storing instances in state, you can use this to properly compare them.
63
-
64
- ```ruby
65
- def permit_parameter?(initial_param, new_param)
66
- if new_param.instance_of? MyModel
67
- new_param.id == @my_model.id
68
- else
69
- super
70
- end
71
- end
72
- ```
73
-
74
- ### omitted_from_state
75
- Return an array of instance variables you want to omit from state. Only really useful if you're using the session state
76
- adapter, and you have an instance variable that can't be serialized.
77
-
78
- ```ruby
79
- def omitted_from_state
80
- [:@form]
81
- end
82
- ```
83
-
84
- ### reflex_tag(reflex, name, content_or_options_with_block = nil, options = nil, escape = true, &block)
85
- This shares the same definition as `content_tag`, except it accepts a reflex as the first parameter.
86
-
87
- ```erb
88
- <%= reflex_tag :increment, :button, "Click me!" %>
89
- ```
90
-
91
- Would add a click handler to the `increment` method on your component.
92
-
93
- To use a non-click event, specific that with `->` notation
94
-
95
- ```erb
96
- <%= reflex_tag "mouseenter->increment", :button, "Click me!" %>
97
- ```
98
-
99
- ### reflex_data_attributes(reflex)
100
-
101
- This helper will give you the data attributes used in the reflex_tag above if you want to build your own elements.
102
-
103
- Build your own tag:
104
-
105
- ```erb
106
- <%= link_to (image_tag photo.image.url(:medium)), data: reflex_data_attributes(:increment) %>
107
- ```
108
-
109
- Render a ViewComponent
110
-
111
- ```erb
112
- <%= render ButtonComponent.new(data: reflex_data_attributes("mouseenter->increment")) %>
113
- ```
114
-
115
- Make sure that you assign the reflex_data_attributes to the correct element in your component.
116
-
117
- ### collection_key
118
- 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.
119
- This is used to reconcile state in the background.
120
-
121
- ```ruby
122
- def initialize
123
- @my_model = MyModel.new
124
- end
125
-
126
- def collection_key
127
- @my_model.id
128
- end
129
- ```
130
-
131
- ### stimulate(target, data)
132
- Stimulate another reflex from within your component. This typically requires the key of the component you're stimulating
133
- which can be passed in via parameters.
134
-
135
- ```ruby
136
- def initialize(parent_key)
137
- @parent_key = parent_key
138
- end
139
-
140
- def stimulate_other
141
- stimulate("OtherComponent#method", { key: @parent_key })
142
- end
143
- ```
144
-
145
- ### refresh!(selectors)
146
- Refresh a specific element on the page. Using this will implicitly run `prevent_render!`.
147
- 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
148
-
149
- ```
150
- def my_method
151
- refresh! '#my-special-element', selector
152
- end
153
- ```
154
-
155
- ### selector
156
- Returns the unique selector for this component. Useful to pass to `refresh!` when refreshing custom elements.
157
-
158
- ### prevent_refresh!
159
- By default, VCR will re-render your component after it executes your method. `prevent_refresh!` prevents this from happening.
160
-
161
- ```ruby
162
- def my_method
163
- prevent_refresh!
164
- @foo = :bar
165
- end # the rendered page will not reflect this change
166
- ```
167
-
168
- ### refresh_all!
169
- Refresh the entire body of the page
170
-
171
- ```ruby
172
- def do_some_global_action
173
- prevent_refresh!
174
- session[:model] = MyModel.new
175
- refresh_all!
176
- end
177
- ```
178
-
179
- ### stream_to(channel)
180
- Stream to a custom channel, rather than the default stimulus reflex one
181
-
182
- ```ruby
183
- def do_something
184
- stream_to MyChannel
185
-
186
- @foo = :bar
187
- end
188
- ```
189
-
190
- ### key
191
- 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
192
-
193
- ```erb
194
- <button type="button" data-reflex="click->MyComponent#do_something" data-key="<%= key %>">Click me!</button>
195
- ```
196
-
197
- ### component_controller(options = {}, &blk)
198
- This is a view helper to properly connect VCR to the component. It outputs `<div data-controller="my-controller" key=<%= key %></div>`
199
- You *must* wrap your component in this for everything to work properly.
200
-
201
- ```erb
202
- <%= component_controller do %>
203
- <p><%= @count %></p
204
- <% end %>
205
- ```
206
-
207
- ### after_state_initialized(parameters_changed)
208
-
209
- This is called after the state has been inserted in the component. You can use this to run conditional functions after
210
- some parameter has superseeded whatever's in state
211
-
212
- ```
213
- def after_state_initialized(parameters_changed)
214
- if parameters_changed.include?(:@filter)
215
- calculate_visible_rows
216
- end
217
- end
218
- ```
219
-
220
- ## Custom reflex base class
221
- 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.
222
- The parent class must inherit ViewComponentReflex::Reflex, and will throw an error if it does not.
223
-
224
- ```ruby
225
- class ApplicationReflex < ViewComponentReflex::Reflex
226
-
227
- end
228
-
229
-
230
- class MyComponent < ViewComponentReflex::Component
231
- MyComponent.reflex_base_class = ApplicationReflex
232
- end
233
- ```
234
-
235
- ## Common patterns
236
- A lot of the time, you only need to update specific components when changing instance variables. For example, changing `@loading` might only need
237
- 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
238
-
239
- ```ruby
240
- def initialize
241
- @loading = false
242
- end
243
-
244
- def loading=(new_value)
245
- @loading = new_value
246
- refresh! '#loader'
247
- end
248
-
249
- def do_expensive_action
250
- prevent_refresh!
251
-
252
- self.loading = true
253
- execute_it
254
- self.loading = false
255
- end
256
- ```
257
-
258
- ```erb
259
- <%= component_controller do %>
260
- <div id="loader">
261
- <% if @loading %>
262
- <p>Loading...</p>
263
- <% end %>
264
- </div>
265
-
266
- <button type="button" data-reflex="click->MyComponent#do_expensive_action" data-key="<%= key %>">Click me!</button>
267
- <% end
268
- ```
269
-
270
- ## State
271
-
272
- By default (since version `2.3.2`), view_component_reflex stores component state in session. You can optionally set the state adapter
273
- to use the memory by changing `config.state_adapter` to `ViewComponentReflex::StateAdapter::Memory`.
274
-
275
- ## Custom State Adapters
276
-
277
- ViewComponentReflex uses session for its state by default. To change this, add
278
- an initializer to `config/initializers/view_component_reflex.rb`.
279
-
280
- ```ruby
281
- ViewComponentReflex::Engine.configure do |config|
282
- config.state_adapter = YourAdapter
283
- end
284
- ```
285
-
286
-
287
- ## Existing Fast Redis based State Adapter
288
-
289
- This adapter uses hmset and hgetall to reduce the number of operations.
290
- This is the recommended adapter if you are using AnyCable.
291
-
292
- ```ruby
293
- ViewComponentReflex::Engine.configure do |config|
294
- config.state_adapter = ViewComponentReflex::StateAdapter::Redis.new(
295
- redis_opts: {
296
- url: "redis://localhost:6379/1", driver: :hiredis
297
- },
298
- ttl: 3600)
299
- end
300
- ```
301
-
302
- `YourAdapter` should implement
303
-
304
- ```ruby
305
- class YourAdapter
306
- ##
307
- # request - a rails request object
308
- # key - a unique string that identifies the component instance
309
- def self.state(request, key)
310
- # Return state for a given key
311
- end
312
-
313
- ##
314
- # set_state is used to modify the state.
315
- #
316
- # request - a rails request object
317
- # controller - the current controller
318
- # key - a unique string that identifies the component
319
- # new_state - the new state to set
320
- def self.set_state(request, controller, key, new_state)
321
- # update the state
322
- end
323
-
324
-
325
- ##
326
- # store_state is used to replace the state entirely. It only accepts
327
- # a request object, rather than a reflex because it's called from the component's
328
- # side with the component's instance variables.
329
- #
330
- # request - a rails request object
331
- # key - a unique string that identifies the component instance
332
- # new_state - a hash containing the component state
333
- def self.store_state(request, key, new_state = {})
334
- # replace the state
335
- end
336
- end
337
- ```
338
-
339
-
340
- ## Installation
341
- Add this line to your application's Gemfile:
342
-
343
- ```ruby
344
- gem 'view_component_reflex'
345
- ```
346
-
347
- And then execute:
348
- ```bash
349
- $ bundle
350
- ```
351
-
352
- Or install it yourself as:
353
- ```bash
354
- $ gem install view_component_reflex
355
- ```
356
-
357
- # Common problems
358
-
359
- ## Uninitialized constants \<component\>Reflex
360
- A component needs to be wrapped in `<%= component_controller do %>` in order to properly initialize, otherwise the Reflex class won't get created.
361
-
362
- ## Session is an empty hash
363
- StimulusReflex 3.3 introduced _selector morphs_, allowing you to render arbitrary strings via `ApplicationController.render`, for example:
364
-
365
- ```rb
366
- def test_selector
367
- morph '#some-container', ApplicationController.render(MyComponent.new(some: :param))
368
- end
369
- ```
370
-
371
- StimulusReflex 3.4 introduced a fix that merges the current `request.env` and provides the CSRF token to fetch the session.
372
-
373
- ## Help, my instance variables do not persist into the session
374
-
375
- These instance variable names are not working and unsafe:
376
-
377
- ```rb
378
- def unsafe_instance_variables
379
- [
380
- :@view_context, :@lookup_context, :@view_renderer, :@view_flow,
381
- :@virtual_path, :@variant, :@current_template, :@output_buffer, :@key,
382
- :@helpers, :@controller, :@request, :@tag_builder, :@initialized_state
383
- ]
384
- end
385
- ```
386
- Please use a different name to be able to save them to the session.
387
-
388
- ## Anycable
389
-
390
- @sebyx07 provided a solution to use anycable (https://github.com/joshleblanc/view_component_reflex/issues/23#issue-721786338)
391
-
392
- Leaving this, might help others:
393
-
394
- I tried this with any cable and I had to add this to development.rb
395
- Otherwise @instance_variables were nil after a reflex
396
-
397
- ```ruby
398
- config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/1", driver: :hiredis }
399
- config.session_store :cache_store
400
- ```
401
-
402
- ## License
403
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
404
-
405
- ## Caveats
406
-
407
- 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`.
408
-
409
- ## Support me
410
-
411
- <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>
412
-
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
+