Persistence

noun: firm or obstinate continuance in a course of action in spite of difficulty or opposition

We estimate that 80% of the pain points in web development are the direct result of maintaining state on the client. Even without considering the complexity of frameworks like React, how much time have you lost to fretting about model validation, stale data, and DOM readiness over your career?

Sockpuppet applications don't have a client state.*

* This is at least 98% true.

Imagine if you could focus almost all of your time and attention on the fun parts of web development again. Exploring the best way to implement features instead of worrying about data serialization and forgotten user flows. Smaller teams working smarter and faster, then going home on time.

Designing applications in the Sockpuppet mindset is far simpler than what we're used to, and we don't have to give up responsive client functionality to see our productivity shoot through the roof. It does, however, require some unlearning of old habits. You're about to rethink how you approach persisting the state of your application. This can be jarring at first! Even positive changes feel like work.

The life of a Reflex

When you access a page in a Sockpuppet application, you see the current state of your user interface for that URL. There is no mounting process and no fetching of JSON from an API. Your request goes through the URL router to your Django view where it renders the template and sends HTML to the browser. This is Django in all its server-rendered glory.

Only once the HTML page is displayed in your browser, the JavaScript library StimulusReflex wakes up. First, it opens a websocket connection and waits for messages. Then it scans your DOM for elements with data-reflex attributes. Those attributes become event handlers that map to methods in Stimulus controllers. The controllers connect events in your browser to methods in your Reflex classes on the server.

In a Reflex method, you can call the Django ORM, access data from Redis or sessions, and set instance variables that get picked up in your view. After the Reflex method is complete, the Django view is executed and any instance variables set on the Reflex will be passed onto the view's context.

We find that people learn how to work with Sockpuppet quickly when they are pushed in the right direction. The order of operations can seem fuzzy until the light bulb flicks on.

This document is here to get you to the light bulb moment quickly.

Sockpuppet only works with class-based views and expects the method get_context_data to exist on the view.

As Sockpuppet re-renders the view during the reflex phase, it's important to consider caching the view correctly. Otherwise queries in the view will be executed again, which will decrease performance.

Instance Variables

One of the most common patterns in Sockpuppet is to pass instance variables from the Reflex method to the view, which then get rendered in the template. Sockpuppet will do this for you automatically as long as it can call get_context_data (you don't need to implement it unless you desire special behavior, however; the class-based views you'll be using include ContextMixin).

def updateValue
  self.value = self.element.attributes['value']
def get_context_data(self, *args, **kwargs):
    context = super().get_context_data(*args, **kwargs)
    context['value'] = 0
    return context
<div data-controller="example">
  <input type="text" data-reflex-permanent
    data-reflex="input->ExampleReflex#updateValue">
  <p>The value is: {{ value }}.</p>
</div>

When you access the index page, the value will initially be set to 0. If the user changes the value of the text input, the value is updated to reflect whatever has been typed. This is possible because reflex data takes precedence over view data.

Sockpuppet doesn't need to go through Django routing. This means updates are processed much faster than requests that come from typing in a URL or refreshing the page.

Of course, instance variables are aptly named; they only exist for the duration of a single request, regardless of whether that request is initiated by accessing a URL or clicking a button managed by StimulusReflex.

The stimulus_reflex context variable

When Sockpuppet calls your Django view, it passes any active instance variables along with a special context variable called stimulus_reflex, which is set to true. You can use this context variable to create an if/else block in your template or view that behaves differently depending on whether it's being called within the context of a Reflex update or not.

def get_context_data(self, *args, **kwargs):
    context = super().get_context_data(*args, **kwargs)
    if not context.get('stimulus_reflex'):
        self.request.session['balls_left'] = 3
    return context

In this example, the user is given three new balls every time they refresh the page in their browser, effectively restarting the game. If the page state is updated via the Sockpuppet Reflex, no new balls are allocated.

Since the stimulus_reflex variable is only available during the reflex phase and not when executing the view normally you'll have to use context.get, otherwise you'll get an error.

This also means that self.request.session['balls_left'] will be set to 3 before the initial HTML page has been rendered and transmitted.

The first time the view action executes is your opportunity to set up the state that Sockpuppet will later modify.

The Django session object

The session object will persist across multiple requests; indeed, you can open multiple browser tabs and they will all share the same session.session_key value on the server. See for yourself: you can create a new session using Incognito Mode or using a second web browser.

We can update our earlier example to use the session object, and it will now persist across multiple browser tabs and refreshes:

def update_value(self):
    self.request.session['value'] = self.element['value']
def get(self, *args, **kwargs):
    context = self.get_context_data()
    context['value'] = self.request.session['value'] = 0
    return render_to_response(...)
<div>
  <input type="text" data-reflex-permanent
    data-reflex="input->ExampleReflex#updateValue">
  <p>The value is: {{ value }}.</p>
</div>

Last updated