wavetermdev/waveterm · error

Component function must be a function

Error message

Component function must be a function

What it means

RegisterComponent only accepts component functions, but validateCFunc uses reflect to check that cfunc's reflect.Kind is Func. Passing any non-function value (string, int, struct, map) triggers "Component function must be a function". This guards the deferred reflect.Call path from panicking later.

Source

Thrown at tsunami/engine/rootelem.go:242

		return fmt.Errorf("atom %q not found", name)
	}
	return atom.SetVal(val)
}

func (r *RootElem) RemoveAtom(name string) {
	r.atomLock.Lock()
	defer r.atomLock.Unlock()

	delete(r.Atoms, name)
}

func validateCFunc(cfunc any) error {
	if cfunc == nil {
		return fmt.Errorf("Component function cannot b nil")
	}
	rval := reflect.ValueOf(cfunc)
	if rval.Kind() != reflect.Func {
		return fmt.Errorf("Component function must be a function")
	}
	rtype := rval.Type()
	if rtype.NumIn() != 1 {
		return fmt.Errorf("Component function must take exactly 1 argument")
	}
	if rtype.NumOut() != 1 {
		return fmt.Errorf("Component function must return exactly 1 value")
	}
	// first argument can be a map[string]any, or a struct, or ptr to struct (we'll reflect the value into it)
	arg1Type := rtype.In(0)
	if arg1Type.Kind() == reflect.Ptr {
		arg1Type = arg1Type.Elem()
	}
	if arg1Type.Kind() == reflect.Map {
		if arg1Type.Key().Kind() != reflect.String ||
			!(arg1Type.Elem().Kind() == reflect.Interface && arg1Type.Elem().NumMethod() == 0) {
			return fmt.Errorf("Map argument must be map[string]any")
		}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Pass an actual function value to RegisterComponent
  2. Verify the variable's concrete type before registering (reflect.TypeOf or an IDE type check)
  3. If the handler comes from a map/registry, assert it is a func before registration

Example fix

// before
root.RegisterComponent("panel", PanelConfig{}) // struct, not a func
// after
root.RegisterComponent("panel", func(props map[string]any) any {
    return renderPanel(props)
})
Defensive patterns

Strategy: type-guard

Validate before calling

if reflect.ValueOf(cfunc).Kind() != reflect.Func {
    return fmt.Errorf("component %q: value is %T, need a func", name, cfunc)
}

Type guard

func isFunc(v any) bool { return v != nil && reflect.ValueOf(v).Kind() == reflect.Func }

Try / catch

if err := root.RegisterComponent("panel", v); err != nil {
    return fmt.Errorf("bad component value for panel: %w", err)
}

Prevention

When it happens

Trigger: RegisterComponent("name", someStringOrStruct); passing a variable of type any that actually holds a non-func value; passing a method value wrapper that was reassigned to data.

Common situations: Confusing RegisterComponent with an API that takes component state/data objects; accidentally passing the result of a lookup that returned data instead of a handler; refactoring that changed the registered value from a func to a struct.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/da3f851559667d0d. Report an issue: GitHub.