swagger-api/swagger-ui · error · TypeError

wrapActions needs to return a function that returns a new fu

Error message

wrapActions needs to return a function that returns a new function (ie the wrapped action)

What it means

Swagger UI's plugin system registers action wrappers under statePlugins.<plugin>.wrapActions.<actionName>. Each entry must be a curried higher-order function — (originalAction, getSystem) => (...args) => newValue — so the system can rebuild a callable action. This TypeError is raised in getWrappedAndBoundActions (src/core/system.js:177) during system construction when the rebuilt wrapper is not a function. In practice the guard inspects a locally-built closure, so the same mistake usually surfaces as a downstream 'TypeError: ... is not a function' when the wrapped action is dispatched.

Source

Thrown at src/core/system.js:177

    let actionGroups = this.getBoundActions(dispatch)
      return objMap(actionGroups, (actions, actionGroupName) => {
        let wrappers = this.system.statePlugins[actionGroupName.slice(0,-7)].wrapActions
          if(wrappers) {
            return objMap(actions, (action, actionName) => {
              let wrap = wrappers[actionName]
              if(!wrap) {
                return action
              }

              if(!Array.isArray(wrap)) {
                wrap = [wrap]
              }
              return wrap.reduce((acc, fn) => {
                let newAction = (...args) => {
                  return fn(acc, this.getSystem())(...args)
                }
                if(!isFn(newAction)) {
                  throw new TypeError("wrapActions needs to return a function that returns a new function (ie the wrapped action)")
                }
                return wrapWithTryCatch(newAction, this.getSystem)
              }, action || Function.prototype)
            })
          }
        return actions
      })
  }

  getWrappedAndBoundSelectors(getState, getSystem) {
    let selectorGroups = this.getBoundSelectors(getState, getSystem)
      return objMap(selectorGroups, (selectors, selectorGroupName) => {
        let stateName = [selectorGroupName.slice(0, -9)] // selectors = 9 chars
        let wrappers = this.system.statePlugins[stateName].wrapSelectors
          if(wrappers) {
            return objMap(selectors, (selector, selectorName) => {
              let wrap = wrappers[selectorName]
              if(!wrap) {

View on GitHub (pinned to 3d9d0916d4)

Solutions

  1. Reshape each wrapActions entry as two nested arrow functions: (oriAction, system) => (...args) => { ...; return ori(...args) }.
  2. Confirm the wrapper key exactly matches an existing action name in the same statePlugin (e.g. spec.updateSpec), otherwise the wrap never engages.
  3. Even if you only need the system and no args, still return (...args) => ori(...args) — the inner function is mandatory.
  4. Smoke-test the plugin by constructing SwaggerUI({ plugins: [yourPlugin], domNode }) and dispatching the wrapped action to confirm it stays callable.

Example fix

// before
wrapActions: {
  updateSpec: (ori, system) => {
    console.log("wrapping", system)
  }
}

// after
wrapActions: {
  updateSpec: (ori, system) => (...args) => {
    console.log("wrapping", system)
    return ori(...args)
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate a plugin's wrapActions before registering it.
function validateWrapActions(plugin) {
  const groups = (plugin && plugin.statePlugins) || {}
  for (const [pluginName, sp] of Object.entries(groups)) {
    for (const [name, wrap] of Object.entries(sp.wrapActions || {})) {
      const arr = Array.isArray(wrap) ? wrap : [wrap]
      arr.forEach((fn, i) => {
        if (typeof fn !== "function")
          throw new Error(`wrapActions[${pluginName}.${name}][${i}] is not a function`)
        const produced = fn(() => {}, {}) // (ori, system)
        if (typeof produced !== "function")
          throw new Error(`wrapActions[${pluginName}.${name}][${i}] must return a function; got ${typeof produced}`)
      })
    }
  }
  return plugin
}

Type guard

// Narrows a wrapAction entry to the curried (ori, system) => fn form.
const isWrapAction = (w) => {
  const fn = Array.isArray(w) ? w[0] : w
  return typeof fn === "function" && typeof fn(() => {}, {}) === "function"
}

Try / catch

try {
  SwaggerUI({ plugins: [myPlugin], domNode: "#swagger" })
} catch (e) {
  if (/wrapActions needs to return a function/.test(e.message)) {
    console.error("A wrapActions entry is not curried (needs two arrow functions):", e)
  } else {
    throw e
  }
}

Prevention

When it happens

Trigger: Defining a wrapActions entry with only one arrow that returns a value, e.g. wrapActions: { updateSpec: (ori, system) => { doSomething() } } (returns undefined) instead of (ori, system) => (...args) => {...}; returning a plain value/object from the outer arrow; assigning a wrapActions key to a function whose outer call does not yield a function.

Common situations: Writing a custom Swagger UI plugin and forgetting the second (inner) arrow function — the most common copy-paste mistake; migrating a plugin from another Redux app and assuming a simpler (ori) => result signature; assigning the wrapper to a key that does not match an existing action name (silently no-ops, leading devs to 'fix' it by changing the signature incorrectly).

Related errors


AI-assisted analysis of swagger-api/swagger-ui@3d9d0916d4 (2026-08-13). Data as JSON: /api/errors/c691d0be10836f27. Report an issue: GitHub.