diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa53f765..5c9b047f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,7 +34,21 @@ Using the following categories, list your changes in this order:
 
 ## [Unreleased]
 
--   Nothing (yet)!
+### Changed
+
+-   New syntax for `use_query` and `use_mutation` hooks. Here's a quick comparison of the changes:
+
+    ```python
+    query = use_query(QueryOptions(thread_sensitive=True), get_items, value=123456, foo="bar") # Old
+    query = use_query(get_items, {"value":12356, "foo":"bar"}, thread_sensitive=True) # New
+
+    mutation = use_mutation(MutationOptions(thread_sensitive=True), remove_item) # Old
+    mutation = use_mutation(remove_item, thread_sensitive=True) # New
+    ```
+
+### Removed
+
+-   `QueryOptions` and `MutationOptions` have been removed. Their values are now passed direct into the hook.
 
 ## [3.8.1] - 2024-05-07
 
diff --git a/docs/examples/python/django-query-postprocessor.py b/docs/examples/python/django-query-postprocessor.py
index ff7419b5..da33c362 100644
--- a/docs/examples/python/django-query-postprocessor.py
+++ b/docs/examples/python/django-query-postprocessor.py
@@ -1,7 +1,6 @@
 from example.models import TodoItem
 from reactpy import component
 from reactpy_django.hooks import use_query
-from reactpy_django.types import QueryOptions
 from reactpy_django.utils import django_query_postprocessor
 
 
@@ -11,13 +10,11 @@ def get_items():
 
 @component
 def todo_list():
-    # These `QueryOptions` are functionally equivalent to ReactPy-Django's default values
+    # These postprocessor options are functionally equivalent to ReactPy-Django's default values
     item_query = use_query(
-        QueryOptions(
-            postprocessor=django_query_postprocessor,
-            postprocessor_kwargs={"many_to_many": True, "many_to_one": True},
-        ),
         get_items,
+        postprocessor=django_query_postprocessor,
+        postprocessor_kwargs={"many_to_many": True, "many_to_one": True},
     )
 
     return item_query.data
diff --git a/docs/examples/python/use-mutation-query-refetch.py b/docs/examples/python/use-mutation-query-refetch.py
index b0817713..227ab1a7 100644
--- a/docs/examples/python/use-mutation-query-refetch.py
+++ b/docs/examples/python/use-mutation-query-refetch.py
@@ -26,7 +26,9 @@ def submit_event(event):
     elif item_query.error or not item_query.data:
         rendered_items = html.h2("Error when loading!")
     else:
-        rendered_items = html.ul(html.li(item, key=item.pk) for item in item_query.data)
+        rendered_items = html.ul(
+            html.li(item.text, key=item.pk) for item in item_query.data
+        )
 
     # Handle all possible mutation states
     if item_mutation.loading:
diff --git a/docs/examples/python/use-mutation-thread-sensitive.py b/docs/examples/python/use-mutation-thread-sensitive.py
index 0242e41b..85046dc0 100644
--- a/docs/examples/python/use-mutation-thread-sensitive.py
+++ b/docs/examples/python/use-mutation-thread-sensitive.py
@@ -1,6 +1,5 @@
 from reactpy import component, html
 from reactpy_django.hooks import use_mutation
-from reactpy_django.types import MutationOptions
 
 
 def execute_thread_safe_mutation(text):
@@ -11,8 +10,8 @@ def execute_thread_safe_mutation(text):
 @component
 def my_component():
     item_mutation = use_mutation(
-        MutationOptions(thread_sensitive=False),
         execute_thread_safe_mutation,
+        thread_sensitive=False,
     )
 
     def submit_event(event):
diff --git a/docs/examples/python/use-query-args.py b/docs/examples/python/use-query-args.py
index 60e8851a..8deb549a 100644
--- a/docs/examples/python/use-query-args.py
+++ b/docs/examples/python/use-query-args.py
@@ -2,16 +2,11 @@
 from reactpy_django.hooks import use_query
 
 
-def example_query(value: int, other_value: bool = False):
-    ...
+def example_query(value: int, other_value: bool = False): ...
 
 
 @component
 def my_component():
-    query = use_query(
-        example_query,
-        123,
-        other_value=True,
-    )
+    query = use_query(example_query, {"value": 123, "other_value": True})
 
     return str(query.data)
diff --git a/docs/examples/python/use-query-postprocessor-change.py b/docs/examples/python/use-query-postprocessor-change.py
index c331285e..5685956d 100644
--- a/docs/examples/python/use-query-postprocessor-change.py
+++ b/docs/examples/python/use-query-postprocessor-change.py
@@ -1,6 +1,5 @@
 from reactpy import component
 from reactpy_django.hooks import use_query
-from reactpy_django.types import QueryOptions
 
 
 def my_postprocessor(data, example_kwarg=True):
@@ -18,11 +17,9 @@ def execute_io_intensive_operation():
 @component
 def my_component():
     query = use_query(
-        QueryOptions(
-            postprocessor=my_postprocessor,
-            postprocessor_kwargs={"example_kwarg": False},
-        ),
         execute_io_intensive_operation,
+        postprocessor=my_postprocessor,
+        postprocessor_kwargs={"example_kwarg": False},
     )
 
     if query.loading or query.error:
diff --git a/docs/examples/python/use-query-postprocessor-disable.py b/docs/examples/python/use-query-postprocessor-disable.py
index 32e981f1..e9541924 100644
--- a/docs/examples/python/use-query-postprocessor-disable.py
+++ b/docs/examples/python/use-query-postprocessor-disable.py
@@ -1,6 +1,5 @@
 from reactpy import component
 from reactpy_django.hooks import use_query
-from reactpy_django.types import QueryOptions
 
 
 def execute_io_intensive_operation():
@@ -11,8 +10,8 @@ def execute_io_intensive_operation():
 @component
 def my_component():
     query = use_query(
-        QueryOptions(postprocessor=None),
         execute_io_intensive_operation,
+        postprocessor=None,
     )
 
     if query.loading or query.error:
diff --git a/docs/examples/python/use-query-postprocessor-kwargs.py b/docs/examples/python/use-query-postprocessor-kwargs.py
index b11eb2b5..4ed108af 100644
--- a/docs/examples/python/use-query-postprocessor-kwargs.py
+++ b/docs/examples/python/use-query-postprocessor-kwargs.py
@@ -1,7 +1,6 @@
 from example.models import TodoItem
 from reactpy import component
 from reactpy_django.hooks import use_query
-from reactpy_django.types import QueryOptions
 
 
 def get_model_with_relationships():
@@ -17,10 +16,8 @@ def get_model_with_relationships():
 @component
 def my_component():
     query = use_query(
-        QueryOptions(
-            postprocessor_kwargs={"many_to_many": False, "many_to_one": False}
-        ),
         get_model_with_relationships,
+        postprocessor_kwargs={"many_to_many": False, "many_to_one": False},
     )
 
     if query.loading or query.error or not query.data:
diff --git a/docs/examples/python/use-query-thread-sensitive.py b/docs/examples/python/use-query-thread-sensitive.py
index 9af19f2b..d657be6b 100644
--- a/docs/examples/python/use-query-thread-sensitive.py
+++ b/docs/examples/python/use-query-thread-sensitive.py
@@ -1,6 +1,5 @@
 from reactpy import component
 from reactpy_django.hooks import use_query
-from reactpy_django.types import QueryOptions
 
 
 def execute_thread_safe_operation():
@@ -10,10 +9,7 @@ def execute_thread_safe_operation():
 
 @component
 def my_component():
-    query = use_query(
-        QueryOptions(thread_sensitive=False),
-        execute_thread_safe_operation,
-    )
+    query = use_query(execute_thread_safe_operation, thread_sensitive=False)
 
     if query.loading or query.error:
         return None
diff --git a/docs/examples/python/use-query.py b/docs/examples/python/use-query.py
index cd8d171b..5688765b 100644
--- a/docs/examples/python/use-query.py
+++ b/docs/examples/python/use-query.py
@@ -18,7 +18,7 @@ def todo_list():
         rendered_items = html.h2("Error when loading!")
     else:
         rendered_items = html.ul(
-            [html.li(item, key=item.pk) for item in item_query.data]
+            [html.li(item.text, key=item.pk) for item in item_query.data]
         )
 
     return html.div("Rendered items: ", rendered_items)
diff --git a/docs/includes/orm.md b/docs/includes/orm.md
index 58f45477..a2a18dfe 100644
--- a/docs/includes/orm.md
+++ b/docs/includes/orm.md
@@ -8,6 +8,6 @@ These `#!python SynchronousOnlyOperation` exceptions may be removed in a future
 
 <!--orm-fetch-start-->
 
-By default, automatic recursive fetching of `#!python ManyToMany` or `#!python ForeignKey` fields is enabled within the `django_query_postprocessor`. This is needed to prevent `#!python SynchronousOnlyOperation` exceptions when accessing these fields within your ReactPy components.
+By default, automatic recursive fetching of `#!python ManyToMany` or `#!python ForeignKey` fields is enabled within the `#!python django_query_postprocessor`. This is needed to prevent `#!python SynchronousOnlyOperation` exceptions when accessing these fields within your ReactPy components.
 
 <!--orm-fetch-end-->
diff --git a/docs/src/reference/hooks.md b/docs/src/reference/hooks.md
index b3ec7d6e..6d7da90f 100644
--- a/docs/src/reference/hooks.md
+++ b/docs/src/reference/hooks.md
@@ -44,20 +44,21 @@ Query functions can be sync or async.
 
     | Name | Type | Description | Default |
     | --- | --- | --- | --- |
-    | `#!python options` | `#!python QueryOptions | None` | An optional `#!python QueryOptions` object that can modify how the query is executed. | `#!python None` |
-    | `#!python query` | `#!python Callable[_Params, _Result | None]` | A callable that returns a Django `#!python Model` or `#!python QuerySet`. | N/A |
-    | `#!python *args` | `#!python _Params.args` | Positional arguments to pass into `#!python query`. | N/A |
-    | `#!python **kwargs` | `#!python _Params.kwargs` | Keyword arguments to pass into `#!python query`. | N/A |
+    | `#!python query` | `#!python Callable[FuncParams, Awaitable[Inferred]] | Callable[FuncParams, Inferred]` | A function that executes a query and returns some data. | N/A |
+    | `#!python kwargs` | `#!python dict[str, Any] | None` | Keyword arguments to passed into the `#!python query` function. | `#!python None` |
+    | `#!python thread_sensitive` | `#!python bool` | Whether to run your query function in thread sensitive mode. This mode only applies to sync query functions, and is turned on by default due to Django ORM limitations. | `#!python True` |
+    | `#!python postprocessor` | `#!python AsyncPostprocessor | SyncPostprocessor | None` | A callable that processes the query `#!python data` before it is returned. The first argument of postprocessor function must be the query `#!python data`. All proceeding arguments are optional `#!python postprocessor_kwargs`. This postprocessor function must return the modified `#!python data`. | `#!python None` |
+    | `#!python postprocessor_kwargs` | `#!python dict[str, Any] | None` | Keyworded arguments passed into the `#!python postprocessor` function. | `#!python None` |
 
     <font size="4">**Returns**</font>
 
     | Type | Description |
     | --- | --- |
-    | `#!python Query[_Result | None]` | An object containing `#!python loading`/`#!python error` states, your `#!python data` (if the query has successfully executed), and a `#!python refetch` callable that can be used to re-run the query. |
+    | `#!python Query[Inferred]` | An object containing `#!python loading`/`#!python error` states, your `#!python data` (if the query has successfully executed), and a `#!python refetch` callable that can be used to re-run the query. |
 
 ??? question "How can I provide arguments to my query function?"
 
-    `#!python *args` and `#!python **kwargs` can be provided to your query function via `#!python use_query` parameters.
+    `#!python kwargs` can be provided to your query function via the `#!python kwargs=...` parameter.
 
     === "components.py"
 
@@ -67,15 +68,15 @@ Query functions can be sync or async.
 
 ??? question "How can I customize this hook's behavior?"
 
-    This hook accepts a `#!python options: QueryOptions` parameter that can be used to customize behavior.
+    This hook has several parameters that can be used to customize behavior.
 
-    Below are the settings that can be modified via these `#!python QueryOptions`.
+    Below are examples of values that can be modified.
 
     ---
 
     <font size="4">**`#!python thread_sensitive`**</font>
 
-    Whether to run your synchronous query function in thread-sensitive mode. Thread-sensitive mode is turned on by default due to Django ORM limitations. See Django's [`sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.
+    Whether to run your synchronous query function in thread sensitive mode. Thread sensitive mode is turned on by default due to Django ORM limitations. See Django's [`sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.
 
     This setting only applies to sync query functions, and will be ignored for async functions.
 
@@ -96,7 +97,7 @@ Query functions can be sync or async.
     1. Want to use this hook to defer IO intensive tasks to be computed in the background
     2. Want to to utilize `#!python use_query` with a different ORM
 
-    ... then you can either set a custom `#!python postprocessor`, or disable all postprocessing behavior by modifying the `#!python QueryOptions.postprocessor` parameter. In the example below, we will set the `#!python postprocessor` to `#!python None` to disable postprocessing behavior.
+    ... then you can either set a custom `#!python postprocessor`, or disable all postprocessing behavior by modifying the `#!python postprocessor=...` parameter. In the example below, we will set the `#!python postprocessor` to `#!python None` to disable postprocessing behavior.
 
     === "components.py"
 
@@ -104,11 +105,7 @@ Query functions can be sync or async.
         {% include "../../examples/python/use-query-postprocessor-disable.py" %}
         ```
 
-    If you wish to create a custom `#!python postprocessor`, you will need to create a callable.
-
-    The first argument of `#!python postprocessor` must be the query `#!python data`. All proceeding arguments
-    are optional `#!python postprocessor_kwargs` (see below). This `#!python postprocessor` must return
-    the modified `#!python data`.
+    If you wish to create a custom `#!python postprocessor`, you will need to create a function where the first must be the query `#!python data`. All proceeding arguments are optional `#!python postprocessor_kwargs` (see below). This `#!python postprocessor` function must return the modified `#!python data`.
 
     === "components.py"
 
@@ -124,7 +121,7 @@ Query functions can be sync or async.
 
     However, if you have deep nested trees of relational data, this may not be a desirable behavior. In these scenarios, you may prefer to manually fetch these relational fields using a second `#!python use_query` hook.
 
-    You can disable the prefetching behavior of the default `#!python postprocessor` (located at `#!python reactpy_django.utils.django_query_postprocessor`) via the `#!python QueryOptions.postprocessor_kwargs` parameter.
+    You can disable the prefetching behavior of the default `#!python postprocessor` (located at `#!python reactpy_django.utils.django_query_postprocessor`) via the `#!python postprocessor_kwargs=...` parameter.
 
     === "components.py"
 
@@ -140,7 +137,7 @@ Query functions can be sync or async.
 
 ??? question "Can I make a failed query try again?"
 
-    Yes, a `#!python use_mutation` can be re-performed by calling `#!python reset()` on your `#!python use_mutation` instance.
+    Yes, `#!python use_mutation` can be re-executed by calling `#!python reset()` on your `#!python use_mutation` instance.
 
     For example, take a look at `#!python reset_event` below.
 
@@ -190,14 +187,15 @@ Mutation functions can be sync or async.
 
     | Name | Type | Description | Default |
     | --- | --- | --- | --- |
-    | `#!python mutation` | `#!python Callable[_Params, bool | None]` | A callable that performs Django ORM create, update, or delete functionality. If this function returns `#!python False`, then your `#!python refetch` function will not be used. | N/A |
-    | `#!python refetch` | `#!python Callable[..., Any] | Sequence[Callable[..., Any]] | None` | A query function (the function you provide to your `#!python use_query` hook) or a sequence of query functions that need a `refetch` if the mutation succeeds. This is useful for refreshing data after a mutation has been performed. | `#!python None` |
+    | `#!python mutation` | `#!python Callable[FuncParams, bool | None] | Callable[FuncParams, Awaitable[bool | None]]` | A callable that performs Django ORM create, update, or delete functionality. If this function returns `#!python False`, then your `#!python refetch` function will not be used. | N/A |
+    | `#!python thread_sensitive` | `#!python bool` | Whether to run the mutation in thread sensitive mode. This mode only applies to sync mutation functions, and is turned on by default due to Django ORM limitations. | `#!python True` |
+    | `#!python refetch` | `#!python Callable[..., Any] | Sequence[Callable[..., Any]] | None` | A query function (the function you provide to your `#!python use_query` hook) or a sequence of query functions that need a `#!python refetch` if the mutation succeeds. This is useful for refreshing data after a mutation has been performed. | `#!python None` |
 
     <font size="4">**Returns**</font>
 
     | Type | Description |
     | --- | --- |
-    | `#!python Mutation[_Params]` | An object containing `#!python loading`/`#!python error` states, a `#!python reset` callable that will set `#!python loading`/`#!python error` states to defaults, and a `#!python execute` callable that will run the query. |
+    | `#!python Mutation[FuncParams]` | An object containing `#!python loading`/`#!python error` states, and a `#!python reset` callable that will set `#!python loading`/`#!python error` states to defaults. This object can be called to run the query. |
 
 ??? question "How can I provide arguments to my mutation function?"
 
@@ -211,15 +209,15 @@ Mutation functions can be sync or async.
 
 ??? question "How can I customize this hook's behavior?"
 
-    This hook accepts a `#!python options: MutationOptions` parameter that can be used to customize behavior.
+    This hook has several parameters that can be used to customize behavior.
 
-    Below are the settings that can be modified via these `#!python MutationOptions`.
+    Below are examples of values that can be modified.
 
     ---
 
     <font size="4">**`#!python thread_sensitive`**</font>
 
-    Whether to run your synchronous mutation function in thread-sensitive mode. Thread-sensitive mode is turned on by default due to Django ORM limitations. See Django's [`sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.
+    Whether to run your synchronous mutation function in thread sensitive mode. Thread sensitive mode is turned on by default due to Django ORM limitations. See Django's [`sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.
 
     This setting only applies to sync query functions, and will be ignored for async functions.
 
@@ -235,7 +233,7 @@ Mutation functions can be sync or async.
 
 ??? question "Can I make a failed mutation try again?"
 
-    Yes, a `#!python use_mutation` can be re-performed by calling `#!python reset()` on your `#!python use_mutation` instance.
+    Yes, `#!python use_mutation` can be re-executed by calling `#!python reset()` on your `#!python use_mutation` instance.
 
     For example, take a look at `#!python reset_event` below.
 
@@ -257,7 +255,7 @@ Mutation functions can be sync or async.
 
     The example below is a merge of the `#!python use_query` and `#!python use_mutation` examples above with the addition of a `#!python use_mutation(refetch=...)` argument.
 
-    Please note that `refetch` will cause all `#!python use_query` hooks that use `#!python get_items` in the current component tree will be refetched.
+    Please note that `#!python refetch` will cause all `#!python use_query` hooks that use `#!python get_items` in the current component tree will be refetched.
 
     === "components.py"
 
diff --git a/src/reactpy_django/hooks.py b/src/reactpy_django/hooks.py
index 8015a4ab..e13655c2 100644
--- a/src/reactpy_django/hooks.py
+++ b/src/reactpy_django/hooks.py
@@ -11,7 +11,6 @@
     Sequence,
     Union,
     cast,
-    overload,
 )
 from uuid import uuid4
 
@@ -29,16 +28,16 @@
 from reactpy_django.types import (
     AsyncMessageReceiver,
     AsyncMessageSender,
+    AsyncPostprocessor,
     ConnectionType,
     FuncParams,
     Inferred,
     Mutation,
-    MutationOptions,
     Query,
-    QueryOptions,
+    SyncPostprocessor,
     UserData,
 )
-from reactpy_django.utils import generate_obj_name, get_pk
+from reactpy_django.utils import django_query_postprocessor, generate_obj_name, get_pk
 
 if TYPE_CHECKING:
     from channels_redis.core import RedisChannelLayer
@@ -51,7 +50,6 @@
 )
 
 
-# TODO: Deprecate this once the equivalent hook gets moved to reactpy.hooks.*
 def use_location() -> Location:
     """Get the current route as a `Location` object"""
     return _use_location()
@@ -85,7 +83,6 @@ def use_origin() -> str | None:
     return None
 
 
-# TODO: Deprecate this once the equivalent hook gets moved to reactpy.hooks.*
 def use_scope() -> dict[str, Any]:
     """Get the current ASGI scope dictionary"""
     scope = _use_scope()
@@ -96,55 +93,55 @@ def use_scope() -> dict[str, Any]:
     raise TypeError(f"Expected scope to be a dict, got {type(scope)}")
 
 
-# TODO: Deprecate this once the equivalent hook gets moved to reactpy.hooks.*
 def use_connection() -> ConnectionType:
     """Get the current `Connection` object"""
     return _use_connection()
 
 
-@overload
 def use_query(
-    options: QueryOptions,
-    /,
     query: Callable[FuncParams, Awaitable[Inferred]] | Callable[FuncParams, Inferred],
-    *args: FuncParams.args,
-    **kwargs: FuncParams.kwargs,
-) -> Query[Inferred]: ...
-
-
-@overload
-def use_query(
-    query: Callable[FuncParams, Awaitable[Inferred]] | Callable[FuncParams, Inferred],
-    *args: FuncParams.args,
-    **kwargs: FuncParams.kwargs,
-) -> Query[Inferred]: ...
-
-
-def use_query(*args, **kwargs) -> Query[Inferred]:
+    kwargs: dict[str, Any] | None = None,
+    *,
+    thread_sensitive: bool = True,
+    postprocessor: (
+        AsyncPostprocessor | SyncPostprocessor | None
+    ) = django_query_postprocessor,
+    postprocessor_kwargs: dict[str, Any] | None = None,
+) -> Query[Inferred]:
     """This hook is used to execute functions in the background and return the result, \
         typically to read data the Django ORM.
 
     Args:
-        options: An optional `QueryOptions` object that can modify how the query is executed.
-        query: A callable that returns a Django `Model` or `QuerySet`.
-        *args: Positional arguments to pass into `query`.
+        query: A function that executes a query and returns some data.
 
-    Keyword Args:
-        **kwargs: Keyword arguments to pass into `query`."""
+    Kwargs:
+        kwargs: Keyword arguments to passed into the `query` function.
+        thread_sensitive: Whether to run the query in thread sensitive mode. \
+            This mode only applies to sync query functions, and is turned on by default \
+            due to Django ORM limitations.
+        postprocessor: A callable that processes the query `data` before it is returned. \
+            The first argument of postprocessor function must be the query `data`. All \
+            proceeding arguments are optional `postprocessor_kwargs`. This postprocessor \
+            function must return the modified `data`. \
+            \
+            If unset, `REACTPY_DEFAULT_QUERY_POSTPROCESSOR` is used. By default, this \
+            is used to prevent Django's lazy query execution and supports `many_to_many` \
+            and `many_to_one` as `postprocessor_kwargs`.
+        postprocessor_kwargs: Keyworded arguments passed into the `postprocessor` function.
+
+    Returns:
+         An object containing `loading`/`#!python error` states, your `data` (if the query \
+         has successfully executed), and a `refetch` callable that can be used to re-run the query.
+    """
 
     should_execute, set_should_execute = use_state(True)
     data, set_data = use_state(cast(Inferred, None))
     loading, set_loading = use_state(True)
     error, set_error = use_state(cast(Union[Exception, None], None))
-    if isinstance(args[0], QueryOptions):
-        query_options, query, query_args, query_kwargs = _use_query_args_1(
-            *args, **kwargs
-        )
-    else:
-        query_options, query, query_args, query_kwargs = _use_query_args_2(
-            *args, **kwargs
-        )
     query_ref = use_ref(query)
+    kwargs = kwargs or {}
+    postprocessor_kwargs = postprocessor_kwargs or {}
+
     if query_ref.current is not query:
         raise ValueError(f"Query function changed from {query_ref.current} to {query}.")
 
@@ -153,31 +150,27 @@ async def execute_query() -> None:
         try:
             # Run the query
             if asyncio.iscoroutinefunction(query):
-                new_data = await query(*query_args, **query_kwargs)
+                new_data = await query(**kwargs)
             else:
                 new_data = await database_sync_to_async(
-                    query,
-                    thread_sensitive=query_options.thread_sensitive,
-                )(*query_args, **query_kwargs)
+                    query, thread_sensitive=thread_sensitive
+                )(**kwargs)
 
             # Run the postprocessor
-            if query_options.postprocessor:
-                if asyncio.iscoroutinefunction(query_options.postprocessor):
-                    new_data = await query_options.postprocessor(
-                        new_data, **query_options.postprocessor_kwargs
-                    )
+            if postprocessor:
+                if asyncio.iscoroutinefunction(postprocessor):
+                    new_data = await postprocessor(new_data, **postprocessor_kwargs)
                 else:
                     new_data = await database_sync_to_async(
-                        query_options.postprocessor,
-                        thread_sensitive=query_options.thread_sensitive,
-                    )(new_data, **query_options.postprocessor_kwargs)
+                        postprocessor, thread_sensitive=thread_sensitive
+                    )(new_data, **postprocessor_kwargs)
 
         # Log any errors and set the error state
         except Exception as e:
             set_data(cast(Inferred, None))
             set_loading(False)
             set_error(e)
-            _logger.exception(f"Failed to execute query: {generate_obj_name(query)}")
+            _logger.exception("Failed to execute query: %s", generate_obj_name(query))
             return
 
         # Query was successful
@@ -212,30 +205,18 @@ def register_refetch_callback() -> Callable[[], None]:
         _REFETCH_CALLBACKS[query].add(refetch)
         return lambda: _REFETCH_CALLBACKS[query].remove(refetch)
 
-    # The query's user API
+    # Return Query user API
     return Query(data, loading, error, refetch)
 
 
-@overload
-def use_mutation(
-    options: MutationOptions,
-    mutation: (
-        Callable[FuncParams, bool | None] | Callable[FuncParams, Awaitable[bool | None]]
-    ),
-    refetch: Callable[..., Any] | Sequence[Callable[..., Any]] | None = None,
-) -> Mutation[FuncParams]: ...
-
-
-@overload
 def use_mutation(
     mutation: (
         Callable[FuncParams, bool | None] | Callable[FuncParams, Awaitable[bool | None]]
     ),
+    *,
+    thread_sensitive: bool = True,
     refetch: Callable[..., Any] | Sequence[Callable[..., Any]] | None = None,
-) -> Mutation[FuncParams]: ...
-
-
-def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
+) -> Mutation[FuncParams]:
     """This hook is used to modify data in the background, typically to create/update/delete \
     data from the Django ORM.
         
@@ -246,18 +227,24 @@ def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
         mutation: A callable that performs Django ORM create, update, or delete \
             functionality. If this function returns `False`, then your `refetch` \
             function will not be used.
+
+    Kwargs:
+        thread_sensitive: Whether to run the mutation in thread sensitive mode. \
+            This mode only applies to sync mutation functions, and is turned on by default \
+            due to Django ORM limitations.
         refetch:  A query function (the function you provide to your `use_query` \
             hook) or a sequence of query functions that need a `refetch` if the \
             mutation succeeds. This is useful for refreshing data after a mutation \
             has been performed.
+
+    Returns:
+        An object containing `#!python loading`/`#!python error` states, and a \
+        `#!python reset` callable that will set `#!python loading`/`#!python error` \
+        states to defaults. This object can be called to run the query.
     """
 
     loading, set_loading = use_state(False)
     error, set_error = use_state(cast(Union[Exception, None], None))
-    if isinstance(args[0], MutationOptions):
-        mutation_options, mutation, refetch = _use_mutation_args_1(*args, **kwargs)
-    else:
-        mutation_options, mutation, refetch = _use_mutation_args_2(*args, **kwargs)
 
     # The main "running" function for `use_mutation`
     async def execute_mutation(exec_args, exec_kwargs) -> None:
@@ -267,7 +254,7 @@ async def execute_mutation(exec_args, exec_kwargs) -> None:
                 should_refetch = await mutation(*exec_args, **exec_kwargs)
             else:
                 should_refetch = await database_sync_to_async(
-                    mutation, thread_sensitive=mutation_options.thread_sensitive
+                    mutation, thread_sensitive=thread_sensitive
                 )(*exec_args, **exec_kwargs)
 
         # Log any errors and set the error state
@@ -275,7 +262,7 @@ async def execute_mutation(exec_args, exec_kwargs) -> None:
             set_loading(False)
             set_error(e)
             _logger.exception(
-                f"Failed to execute mutation: {generate_obj_name(mutation)}"
+                "Failed to execute mutation: %s", generate_obj_name(mutation)
             )
 
         # Mutation was successful
@@ -311,7 +298,7 @@ def reset() -> None:
         set_loading(False)
         set_error(None)
 
-    # The mutation's user API
+    # Return mutation user API
     return Mutation(schedule_mutation, loading, error, reset)
 
 
@@ -355,11 +342,13 @@ async def _set_user_data(data: dict):
         await model.asave()
 
     query: Query[dict | None] = use_query(
-        QueryOptions(postprocessor=None),
         _get_user_data,
-        user=user,
-        default_data=default_data,
-        save_default_data=save_default_data,
+        kwargs={
+            "user": user,
+            "default_data": default_data,
+            "save_default_data": save_default_data,
+        },
+        postprocessor=None,
     )
     mutation = use_mutation(_set_user_data, refetch=_get_user_data)
 
@@ -444,22 +433,6 @@ def use_root_id() -> str:
     return scope["reactpy"]["id"]
 
 
-def _use_query_args_1(options: QueryOptions, /, query: Query, *args, **kwargs):
-    return options, query, args, kwargs
-
-
-def _use_query_args_2(query: Query, *args, **kwargs):
-    return QueryOptions(), query, args, kwargs
-
-
-def _use_mutation_args_1(options: MutationOptions, mutation: Mutation, refetch=None):
-    return options, mutation, refetch
-
-
-def _use_mutation_args_2(mutation, refetch=None):
-    return MutationOptions(), mutation, refetch
-
-
 async def _get_user_data(
     user: AbstractUser, default_data: None | dict, save_default_data: bool
 ) -> dict | None:
diff --git a/src/reactpy_django/types.py b/src/reactpy_django/types.py
index 8efda72f..91ffc319 100644
--- a/src/reactpy_django/types.py
+++ b/src/reactpy_django/types.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from dataclasses import dataclass, field
+from dataclasses import dataclass
 from typing import (
     TYPE_CHECKING,
     Any,
@@ -52,49 +52,11 @@ def __call__(self, *args: FuncParams.args, **kwargs: FuncParams.kwargs) -> None:
 
 
 class AsyncPostprocessor(Protocol):
-    async def __call__(self, data: Any) -> Any:
-        ...
+    async def __call__(self, data: Any) -> Any: ...
 
 
 class SyncPostprocessor(Protocol):
-    def __call__(self, data: Any) -> Any:
-        ...
-
-
-@dataclass
-class QueryOptions:
-    """Configuration options that can be provided to `use_query`."""
-
-    from reactpy_django.config import REACTPY_DEFAULT_QUERY_POSTPROCESSOR
-
-    postprocessor: AsyncPostprocessor | SyncPostprocessor | None = (
-        REACTPY_DEFAULT_QUERY_POSTPROCESSOR
-    )
-    """A callable that can modify the query `data` after the query has been executed.
-
-    The first argument of postprocessor must be the query `data`. All proceeding arguments
-    are optional `postprocessor_kwargs` (see below). This postprocessor function must return
-    the modified `data`.
-
-    If unset, REACTPY_DEFAULT_QUERY_POSTPROCESSOR is used.
-
-    ReactPy's default django_query_postprocessor prevents Django's lazy query execution, and
-    additionally can be configured via `postprocessor_kwargs` to recursively fetch
-    `many_to_many` and `many_to_one` fields."""
-
-    postprocessor_kwargs: MutableMapping[str, Any] = field(default_factory=lambda: {})
-    """Keyworded arguments directly passed into the `postprocessor` for configuration."""
-
-    thread_sensitive: bool = True
-    """Whether to run the query in thread-sensitive mode. This setting only applies to sync query functions."""
-
-
-@dataclass
-class MutationOptions:
-    """Configuration options that can be provided to `use_mutation`."""
-
-    thread_sensitive: bool = True
-    """Whether to run the mutation in thread-sensitive mode. This setting only applies to sync mutation functions."""
+    def __call__(self, data: Any) -> Any: ...
 
 
 @dataclass
@@ -112,10 +74,8 @@ class UserData(NamedTuple):
 
 
 class AsyncMessageReceiver(Protocol):
-    async def __call__(self, message: dict) -> None:
-        ...
+    async def __call__(self, message: dict) -> None: ...
 
 
 class AsyncMessageSender(Protocol):
-    async def __call__(self, message: dict) -> None:
-        ...
+    async def __call__(self, message: dict) -> None: ...
diff --git a/src/reactpy_django/utils.py b/src/reactpy_django/utils.py
index 457caf47..73538ad7 100644
--- a/src/reactpy_django/utils.py
+++ b/src/reactpy_django/utils.py
@@ -29,19 +29,19 @@
 )
 
 _logger = logging.getLogger(__name__)
-_component_tag = r"(?P<tag>component)"
-_component_path = r"""(?P<path>"[^"'\s]+"|'[^"'\s]+')"""
-_component_offline_kwarg = (
-    rf"""(\s*offline\s*=\s*{_component_path.replace(r"<path>", r"<offline_path>")})"""
+_TAG_PATTERN = r"(?P<tag>component)"
+_PATH_PATTERN = r"""(?P<path>"[^"'\s]+"|'[^"'\s]+')"""
+_OFFLINE_KWARG_PATTERN = (
+    rf"""(\s*offline\s*=\s*{_PATH_PATTERN.replace(r"<path>", r"<offline_path>")})"""
 )
-_component_generic_kwarg = r"""(\s*.*?)"""
+_GENERIC_KWARG_PATTERN = r"""(\s*.*?)"""
 COMMENT_REGEX = re.compile(r"<!--[\s\S]*?-->")
 COMPONENT_REGEX = re.compile(
     r"{%\s*"
-    + _component_tag
+    + _TAG_PATTERN
     + r"\s*"
-    + _component_path
-    + rf"({_component_offline_kwarg}|{_component_generic_kwarg})*?"
+    + _PATH_PATTERN
+    + rf"({_OFFLINE_KWARG_PATTERN}|{_GENERIC_KWARG_PATTERN})*?"
     + r"\s*%}"
 )
 
@@ -262,7 +262,7 @@ def django_query_postprocessor(
 ) -> QuerySet | Model:
     """Recursively fetch all fields within a `Model` or `QuerySet` to ensure they are not performed lazily.
 
-    Behaviors can be modified through `QueryOptions` within your `use_query` hook.
+    Behavior can be modified through `postprocessor_kwargs` within your `use_query` hook.
 
     Args:
         data: The `Model` or `QuerySet` to recursively fetch fields from.
@@ -275,8 +275,7 @@ def django_query_postprocessor(
         The `Model` or `QuerySet` with all fields fetched.
     """
 
-    # `QuerySet`, which is an iterable of `Model`/`QuerySet` instances
-    # https://github.com/typeddjango/django-stubs/issues/704
+    # `QuerySet`, which is an iterable containing `Model`/`QuerySet` objects.
     if isinstance(data, QuerySet):
         for model in data:
             django_query_postprocessor(
@@ -314,7 +313,7 @@ def django_query_postprocessor(
             "One of the following may have occurred:\n"
             "  - You are using a non-Django ORM.\n"
             "  - You are attempting to use `use_query` to fetch non-ORM data.\n\n"
-            "If these situations seem correct, you may want to consider disabling the postprocessor via `QueryOptions`."
+            "If these situations seem correct, you may want to consider disabling the postprocessor."
         )
 
     return data
@@ -381,4 +380,4 @@ def strtobool(val):
     elif val in ("n", "no", "f", "false", "off", "0"):
         return 0
     else:
-        raise ValueError("invalid truth value %r" % (val,))
+        raise ValueError(f"invalid truth value {val}")
diff --git a/src/reactpy_django/websocket/consumer.py b/src/reactpy_django/websocket/consumer.py
index 0d4c62d1..345f399e 100644
--- a/src/reactpy_django/websocket/consumer.py
+++ b/src/reactpy_django/websocket/consumer.py
@@ -29,6 +29,8 @@
 if TYPE_CHECKING:
     from django.contrib.auth.models import AbstractUser
 
+    from reactpy_django import models
+
 _logger = logging.getLogger(__name__)
 BACKHAUL_LOOP = asyncio.new_event_loop()
 
@@ -47,9 +49,18 @@ def start_backhaul_loop():
 class ReactpyAsyncWebsocketConsumer(AsyncJsonWebsocketConsumer):
     """Communicates with the browser to perform actions on-demand."""
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        # New WebsocketConsumer attributes created by ReactPy
+        self.dispatcher: Future | asyncio.Task
+        self.threaded: bool
+        self.recv_queue: asyncio.Queue
+        self.dotted_path: str
+        self.component_session: "models.ComponentSession" | None = None
+
     async def connect(self) -> None:
         """The browser has connected."""
-        from reactpy_django import models
         from reactpy_django.config import (
             REACTPY_AUTH_BACKEND,
             REACTPY_AUTO_RELOGIN,
@@ -79,9 +90,7 @@ async def connect(self) -> None:
                 )
 
         # Start the component dispatcher
-        self.dispatcher: Future | asyncio.Task
         self.threaded = REACTPY_BACKHAUL_THREAD
-        self.component_session: models.ComponentSession | None = None
         if self.threaded:
             if not BACKHAUL_THREAD.is_alive():
                 await asyncio.to_thread(
@@ -149,14 +158,14 @@ async def run_dispatcher(self):
         )
 
         scope = self.scope
-        self.dotted_path = dotted_path = scope["url_route"]["kwargs"]["dotted_path"]
+        self.dotted_path = scope["url_route"]["kwargs"]["dotted_path"]
         uuid = scope["url_route"]["kwargs"].get("uuid")
         has_args = scope["url_route"]["kwargs"].get("has_args")
         scope["reactpy"] = {"id": str(uuid)}
         query_string = parse_qs(scope["query_string"].decode(), strict_parsing=True)
         http_pathname = query_string.get("http_pathname", [""])[0]
         http_search = query_string.get("http_search", [""])[0]
-        self.recv_queue: asyncio.Queue = asyncio.Queue()
+        self.recv_queue = asyncio.Queue()
         connection = Connection(  # For `use_connection`
             scope=scope,
             location=Location(pathname=http_pathname, search=http_search),
@@ -168,11 +177,11 @@ async def run_dispatcher(self):
 
         # Verify the component has already been registered
         try:
-            root_component_constructor = REACTPY_REGISTERED_COMPONENTS[dotted_path]
+            root_component_constructor = REACTPY_REGISTERED_COMPONENTS[self.dotted_path]
         except KeyError:
             await asyncio.to_thread(
                 _logger.warning,
-                f"Attempt to access invalid ReactPy component: {dotted_path!r}",
+                f"Attempt to access invalid ReactPy component: {self.dotted_path!r}",
             )
             return
 
@@ -194,7 +203,7 @@ async def run_dispatcher(self):
         except models.ComponentSession.DoesNotExist:
             await asyncio.to_thread(
                 _logger.warning,
-                f"Component session for '{dotted_path}:{uuid}' not found. The "
+                f"Component session for '{self.dotted_path}:{uuid}' not found. The "
                 "session may have already expired beyond REACTPY_SESSION_MAX_AGE. "
                 "If you are using a custom `host`, you may have forgotten to provide "
                 "args/kwargs.",
diff --git a/tests/test_app/components.py b/tests/test_app/components.py
index dbe9bd8f..fe6df2f0 100644
--- a/tests/test_app/components.py
+++ b/tests/test_app/components.py
@@ -10,7 +10,6 @@
 from django.shortcuts import render
 from reactpy import component, hooks, html, web
 from reactpy_django.components import view_to_component, view_to_iframe
-from reactpy_django.types import QueryOptions
 
 from test_app.models import (
     AsyncForiegnChild,
@@ -659,7 +658,7 @@ def custom_host(number=0):
 @component
 def broken_postprocessor_query():
     relational_parent = reactpy_django.hooks.use_query(
-        QueryOptions(postprocessor=None), get_relational_parent_query
+        get_relational_parent_query, postprocessor=None
     )
 
     if not relational_parent.data:
@@ -720,9 +719,9 @@ async def on_submit(event):
             "data-fetch-error": bool(user_data_query.error),
             "data-mutation-error": bool(user_data_mutation.error),
             "data-loading": user_data_query.loading or user_data_mutation.loading,
-            "data-username": "AnonymousUser"
-            if current_user.is_anonymous
-            else current_user.username,
+            "data-username": (
+                "AnonymousUser" if current_user.is_anonymous else current_user.username
+            ),
         },
         html.div("use_user_data"),
         html.button({"class": "login-1", "on_click": login_user1}, "Login 1"),
@@ -788,9 +787,9 @@ async def on_submit(event):
             "data-fetch-error": bool(user_data_query.error),
             "data-mutation-error": bool(user_data_mutation.error),
             "data-loading": user_data_query.loading or user_data_mutation.loading,
-            "data-username": "AnonymousUser"
-            if current_user.is_anonymous
-            else current_user.username,
+            "data-username": (
+                "AnonymousUser" if current_user.is_anonymous else current_user.username
+            ),
         },
         html.div("use_user_data_with_default"),
         html.button({"class": "login-3", "on_click": login_user3}, "Login 3"),