diff --git a/CHANGELOG.md b/CHANGELOG.md
index 592d7050d6..6e4ef71908 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
 All notable changes to `dash` will be documented in this file.
 This project adheres to [Semantic Versioning](https://semver.org/).
 
+## [UNRELEASED]
+
+## Fixed
+- [#3395](https://github.com/plotly/dash/pull/3395) Fix Components added through set_props() cannot trigger related callback functions. Fix [#3316](https://github.com/plotly/dash/issues/3316)
+- [#3397](https://github.com/plotly/dash/pull/3397) Add optional callbacks, suppressing callback warning for missing component ids for a single callback.
+
 ## [3.2.0] - 2025-07-31
 
 ## Added
diff --git a/dash/_callback.py b/dash/_callback.py
index df3176ece1..663401e3f3 100644
--- a/dash/_callback.py
+++ b/dash/_callback.py
@@ -77,6 +77,7 @@ def callback(
     cache_args_to_ignore: Optional[list] = None,
     cache_ignore_triggered=True,
     on_error: Optional[Callable[[Exception], Any]] = None,
+    optional: Optional[bool] = False,
     **_kwargs,
 ) -> Callable[..., Any]:
     """
@@ -159,6 +160,8 @@ def callback(
             Function to call when the callback raises an exception. Receives the
             exception object as first argument. The callback_context can be used
             to access the original callback inputs, states and output.
+        :param optional:
+            Mark all dependencies as not required on the initial layout checks.
     """
 
     background_spec = None
@@ -213,6 +216,7 @@ def callback(
         manager=manager,
         running=running,
         on_error=on_error,
+        optional=optional,
     )
 
 
@@ -258,6 +262,7 @@ def insert_callback(
     running=None,
     dynamic_creator: Optional[bool] = False,
     no_output=False,
+    optional=False,
 ):
     if prevent_initial_call is None:
         prevent_initial_call = config_prevent_initial_callbacks
@@ -281,6 +286,7 @@ def insert_callback(
         },
         "dynamic_creator": dynamic_creator,
         "no_output": no_output,
+        "optional": optional,
     }
     if running:
         callback_spec["running"] = running
@@ -624,6 +630,7 @@ def register_callback(
         dynamic_creator=allow_dynamic_callbacks,
         running=running,
         no_output=not has_output,
+        optional=_kwargs.get("optional", False),
     )
 
     # pylint: disable=too-many-locals
diff --git a/dash/dash-renderer/package-lock.json b/dash/dash-renderer/package-lock.json
index 1612a851b0..4f0e413452 100644
--- a/dash/dash-renderer/package-lock.json
+++ b/dash/dash-renderer/package-lock.json
@@ -10065,30 +10065,12 @@
       }
     },
     "node_modules/tmp": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
-      "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
+      "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
       "dev": true,
-      "dependencies": {
-        "rimraf": "^3.0.0"
-      },
       "engines": {
-        "node": ">=8.17.0"
-      }
-    },
-    "node_modules/tmp/node_modules/rimraf": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
-      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-      "dev": true,
-      "dependencies": {
-        "glob": "^7.1.3"
-      },
-      "bin": {
-        "rimraf": "bin.js"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
+        "node": ">=14.14"
       }
     },
     "node_modules/to-buffer": {
@@ -18335,24 +18317,10 @@
       }
     },
     "tmp": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
-      "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
-      "dev": true,
-      "requires": {
-        "rimraf": "^3.0.0"
-      },
-      "dependencies": {
-        "rimraf": {
-          "version": "3.0.2",
-          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
-          "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-          "dev": true,
-          "requires": {
-            "glob": "^7.1.3"
-          }
-        }
-      }
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
+      "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
+      "dev": true
     },
     "to-buffer": {
       "version": "1.2.1",
diff --git a/dash/dash-renderer/src/actions/callbacks.ts b/dash/dash-renderer/src/actions/callbacks.ts
index e77bf803aa..78b2ad1550 100644
--- a/dash/dash-renderer/src/actions/callbacks.ts
+++ b/dash/dash-renderer/src/actions/callbacks.ts
@@ -36,13 +36,13 @@ import {
 } from '../types/callbacks';
 import {isMultiValued, stringifyId, isMultiOutputProp} from './dependencies';
 import {urlBase} from './utils';
-import {getCSRFHeader, dispatchError} from '.';
+import {getCSRFHeader, dispatchError, setPaths} from '.';
 import {createAction, Action} from 'redux-actions';
 import {addHttpHeaders} from '../actions';
 import {notifyObservers, updateProps} from './index';
 import {CallbackJobPayload} from '../reducers/callbackJobs';
 import {handlePatch, isPatch} from './patch';
-import {getPath} from './paths';
+import {computePaths, getPath} from './paths';
 
 import {requestDependencies} from './requestDependencies';
 
@@ -51,6 +51,7 @@ import {loadLibrary} from '../utils/libraries';
 import {parsePMCId} from './patternMatching';
 import {replacePMC} from './patternMatching';
 import {loaded, loading} from './loading';
+import {getComponentLayout} from '../wrapper/wrapping';
 
 export const addBlockedCallbacks = createAction<IBlockedCallback[]>(
     CallbackActionType.AddBlocked
@@ -409,7 +410,30 @@ function sideUpdate(outputs: SideUpdateOutput, cb: ICallbackPayload) {
                 return acc;
             }, [] as any[])
             .forEach(([id, idProps]) => {
+                const state = getState();
                 dispatch(updateComponent(id, idProps, cb));
+
+                const componentPath = getPath(state.paths, id);
+                if (!componentPath) {
+                    // Component doesn't exist, doesn't matter just allow the
+                    // callback to continue.
+                    return;
+                }
+                const oldComponent = getComponentLayout(componentPath, state);
+
+                dispatch(
+                    setPaths(
+                        computePaths(
+                            {
+                                ...oldComponent,
+                                props: {...oldComponent.props, ...idProps}
+                            },
+                            [...componentPath],
+                            state.paths,
+                            state.paths.events
+                        )
+                    )
+                );
             });
     };
 }
diff --git a/dash/dash-renderer/src/actions/dependencies.js b/dash/dash-renderer/src/actions/dependencies.js
index 63c9440fb2..657647da7e 100644
--- a/dash/dash-renderer/src/actions/dependencies.js
+++ b/dash/dash-renderer/src/actions/dependencies.js
@@ -570,12 +570,18 @@ export function validateCallbacksToLayout(state_, dispatchError) {
         for (const id in map) {
             const idProps = map[id];
             const fcb = flatten(values(idProps));
-            const optional = all(
-                ({allow_optional}) => allow_optional,
-                flatten(
-                    fcb.map(cb => concat(cb.outputs, cb.inputs, cb.states))
-                ).filter(dep => dep.id === id)
-            );
+            const optional = fcb.reduce((acc, cb) => {
+                if (acc === false || cb.optional) {
+                    return acc;
+                }
+                const deps = concat(cb.outputs, cb.inputs, cb.states).filter(
+                    dep => dep.id === id
+                );
+                return (
+                    !deps.length ||
+                    all(({allow_optional}) => allow_optional, deps)
+                );
+            }, true);
             if (optional) {
                 continue;
             }
diff --git a/dash/dash-renderer/src/utils/clientsideFunctions.ts b/dash/dash-renderer/src/utils/clientsideFunctions.ts
index 4859df41bf..f325e34b5c 100644
--- a/dash/dash-renderer/src/utils/clientsideFunctions.ts
+++ b/dash/dash-renderer/src/utils/clientsideFunctions.ts
@@ -1,5 +1,6 @@
-import {updateProps, notifyObservers} from '../actions/index';
-import {getPath} from '../actions/paths';
+import {updateProps, notifyObservers, setPaths} from '../actions/index';
+import {computePaths, getPath} from '../actions/paths';
+import {getComponentLayout} from '../wrapper/wrapping';
 import {getStores} from './stores';
 
 /**
@@ -16,9 +17,9 @@ function set_props(
     for (let y = 0; y < ds.length; y++) {
         const {dispatch, getState} = ds[y];
         let componentPath;
-        const {paths} = getState();
+        const state = getState();
         if (!Array.isArray(idOrPath)) {
-            componentPath = getPath(paths, idOrPath);
+            componentPath = getPath(state.paths, idOrPath);
         } else {
             componentPath = idOrPath;
         }
@@ -30,6 +31,21 @@ function set_props(
             })
         );
         dispatch(notifyObservers({id: idOrPath, props}));
+        const oldComponent = getComponentLayout(componentPath, state);
+
+        dispatch(
+            setPaths(
+                computePaths(
+                    {
+                        ...oldComponent,
+                        props: {...oldComponent.props, ...props}
+                    },
+                    [...componentPath],
+                    state.paths,
+                    state.paths.events
+                )
+            )
+        );
     }
 }
 
diff --git a/tests/integration/callbacks/test_arbitrary_callbacks.py b/tests/integration/callbacks/test_arbitrary_callbacks.py
index f07afc08be..d6038fd7e7 100644
--- a/tests/integration/callbacks/test_arbitrary_callbacks.py
+++ b/tests/integration/callbacks/test_arbitrary_callbacks.py
@@ -232,3 +232,44 @@ def test_arb007_clientside_no_output(dash_duo):
     dash_duo.wait_for_text_to_equal("#output", "start1")
     dash_duo.find_element("#start2").click()
     dash_duo.wait_for_text_to_equal("#output", "start2")
+
+
+def test_arb008_set_props_chain_cb(dash_duo):
+    app = Dash(suppress_callback_exceptions=True)
+
+    app.layout = html.Div(
+        [
+            html.Button("origin button", id="origin-button"),
+            html.Div(id="generated-button-container"),
+            html.Div("initial text", id="generated-button-output"),
+        ],
+        style={"padding": 50},
+    )
+
+    @app.callback(
+        Input("origin-button", "n_clicks"),
+    )
+    def generate_button(n_clicks):
+        set_props(
+            "generated-button-container",
+            {
+                "children": html.Button(
+                    "generated button", id="generated-button", n_clicks=0
+                )
+            },
+        )
+
+    @app.callback(
+        Output("generated-button-output", "children"),
+        Input("generated-button", "n_clicks", allow_optional=True),
+        prevent_initial_call=True,
+    )
+    def update_output(n_clicks):
+        return f"n_clicks: {n_clicks}"
+
+    dash_duo.start_server(app)
+
+    dash_duo.wait_for_element("#origin-button").click()
+    for i in range(1, 5):
+        dash_duo.wait_for_element("#generated-button").click()
+        dash_duo.wait_for_text_to_equal("#generated-button-output", f"n_clicks: {i}")
