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
- Pass an actual function value to RegisterComponent
- Verify the variable's concrete type before registering (reflect.TypeOf or an IDE type check)
- 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
- Register only literal func values, not data/config objects
- Keep a single registration helper that validates types once
- Let the compiler check types by avoiding any-typed handler variables
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
- error setting field %s: %w
- cannot set value of type %v to field of type %v
- Component function must take exactly 1 argument
- error setting field %s: %w
- cannot set value of type %v to field of type %v
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/da3f851559667d0d.
Report an issue: GitHub.