swagger-api/swagger-ui · error · TypeError

wrapSelector needs to return a function that returns a new f

Error message

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

What it means

Plugins extend selectors via statePlugins.<plugin>.wrapSelectors.<selectorName>, and each wrapper must be curried — (originalSelector, getSystem) => (stateSlice, ...args) => newValue — because the system invokes it with the plugin's Immutable state slice (getState().getIn([pluginName])) plus any caller args. This TypeError is raised in getWrappedAndBoundSelectors (src/core/system.js:207) during system construction when the rebuilt selector is not callable. As with wrapActions, a wrapper that forgets the inner arrow more often appears later as a 'TypeError: ... is not a function' at selector invocation.

Source

Thrown at src/core/system.js:207

      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) {
                return selector
              }

              if(!Array.isArray(wrap)) {
                wrap = [wrap]
              }
              return wrap.reduce((acc, fn) => {
                let wrappedSelector = (...args) => {
                  return fn(acc, this.getSystem())(getState().getIn(stateName), ...args)
                }
                if(!isFn(wrappedSelector)) {
                  throw new TypeError("wrapSelector needs to return a function that returns a new function (ie the wrapped action)")
                }
                return wrappedSelector
              }, selector || Function.prototype)
            })
          }
        return selectors
      })
  }

  getStates(state) {
    return Object.keys(this.system.statePlugins).reduce((obj, key) => {
      obj[key] = state.get(key)
      return obj
    }, {})
  }

  getStateThunks(getState) {
    return Object.keys(this.system.statePlugins).reduce((obj, key) => {

View on GitHub (pinned to 3d9d0916d4)

Solutions

  1. Make every wrapSelectors entry a double arrow: (ori, system) => (state, ...args) => { return ori(state, ...args) }.
  2. Confirm the selector key matches an existing selector in that statePlugin.
  3. Remember the inner function's first argument is the plugin's Immutable state slice, not the whole Redux store.

Example fix

// before
wrapSelectors: {
  isOAS3: (ori, system) => true
}

// after
wrapSelectors: {
  isOAS3: (ori, system) => (state, ...args) => {
    return ori(state, ...args)
  }
}
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

// Narrows a wrapSelector entry to the curried (ori, system) => fn form.
const isWrapSelector = (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 (/wrapSelector needs to return a function/.test(e.message)) {
    console.error("A wrapSelectors entry is not curried (needs two arrow functions):", e)
  } else {
    throw e
  }
}

Prevention

When it happens

Trigger: Defining wrapSelectors with a single arrow returning a value, e.g. wrapSelectors: { isOAS3: (ori, system) => true } instead of (ori, system) => (state, ...args) => true; returning a non-function from the outer arrow; copying a wrapActions-shaped signature into a wrapSelectors slot; getting the argument order wrong so the outer call yields a value.

Common situations: Writing a plugin that augments auth or spec selectors and flattening the wrapper to one arrow; copying an OAS31 wrapSelector example but dropping one layer; selector key not matching an existing selector name (silently no-ops).

Related errors


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