{"record":{"id":"f849a4403eddabc5","repo":"wavetermdev/waveterm","slug":"component-function-argument-must-be-map-string-any","errorCode":null,"errorMessage":"Component function argument must be map[string]any, struct, or any","messagePattern":"Component function argument must be map\\[string\\]any, struct, or any","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tsunami/engine/rootelem.go","lineNumber":263,"sourceCode":"\tif rtype.NumIn() != 1 {\n\t\treturn fmt.Errorf(\"Component function must take exactly 1 argument\")\n\t}\n\tif rtype.NumOut() != 1 {\n\t\treturn fmt.Errorf(\"Component function must return exactly 1 value\")\n\t}\n\t// first argument can be a map[string]any, or a struct, or ptr to struct (we'll reflect the value into it)\n\targ1Type := rtype.In(0)\n\tif arg1Type.Kind() == reflect.Ptr {\n\t\targ1Type = arg1Type.Elem()\n\t}\n\tif arg1Type.Kind() == reflect.Map {\n\t\tif arg1Type.Key().Kind() != reflect.String ||\n\t\t\t!(arg1Type.Elem().Kind() == reflect.Interface && arg1Type.Elem().NumMethod() == 0) {\n\t\t\treturn fmt.Errorf(\"Map argument must be map[string]any\")\n\t\t}\n\t} else if arg1Type.Kind() != reflect.Struct &&\n\t\t!(arg1Type.Kind() == reflect.Interface && arg1Type.NumMethod() == 0) {\n\t\treturn fmt.Errorf(\"Component function argument must be map[string]any, struct, or any\")\n\t}\n\treturn nil\n}\n\nfunc (r *RootElem) RegisterComponent(name string, cfunc any) error {\n\tif err := validateCFunc(cfunc); err != nil {\n\t\treturn err\n\t}\n\tr.CFuncs[name] = cfunc\n\treturn nil\n}\n\nfunc callVDomFn(fnVal any, data vdom.VDomEvent) {\n\tif fnVal == nil {\n\t\treturn\n\t}\n\tfn := fnVal\n\tif vdf, ok := fnVal.(*vdom.VDomFunc); ok {","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/tsunami/engine/rootelem.go#L245-L281","documentation":"The component function's single argument must be one of: map[string]any, a struct, a pointer to struct, or any (empty interface). Anything else — slices, primitives like string/int, channels, funcs — fails this final check with \"Component function argument must be map[string]any, struct, or any\".","triggerScenarios":"RegisterComponent with func(props []string) any; func(count int) any; func(cb func() string) any; any single-parameter function whose parameter is not a (pointer to) struct, map[string]any, or empty interface.","commonSituations":"Registering helper functions that take primitive options; expecting the engine to marshal arbitrary JSON types into slices; aliasing a parameter type that resolves to a slice or primitive.","solutions":["Change the parameter to map[string]any, a struct, *struct, or any","Encode list-like props as fields inside a struct or as values in a map[string]any","Wrap the existing function in a closure with an accepted parameter type"],"exampleFix":"// before\nroot.RegisterComponent(\"list\", func(items []string) any { ... }) // slice not allowed\n// after\ntype listProps struct { Items []string `json:\"items\"` }\nroot.RegisterComponent(\"list\", func(p *listProps) any { return renderList(p.Items) })","handlingStrategy":"validation","validationCode":"t := reflect.TypeOf(cfunc)\nif t != nil && t.Kind() == reflect.Func && t.NumIn() == 1 {\n    at := t.In(0)\n    if at.Kind() == reflect.Ptr { at = at.Elem() }\n    ok := at.Kind() == reflect.Map || at.Kind() == reflect.Struct ||\n        (at.Kind() == reflect.Interface && at.NumMethod() == 0)\n    if !ok { return fmt.Errorf(\"param type %v not allowed\", t.In(0)) }\n}","typeGuard":"func hasValidComponentParam(v any) bool {\n    t := reflect.TypeOf(v)\n    if t == nil || t.Kind() != reflect.Func || t.NumIn() != 1 { return false }\n    at := t.In(0)\n    if at.Kind() == reflect.Ptr { at = at.Elem() }\n    if at.Kind() == reflect.Map { return at.Key().Kind() == reflect.String && at.Elem().Kind() == reflect.Interface }\n    return at.Kind() == reflect.Struct || (at.Kind() == reflect.Interface && at.NumMethod() == 0)\n}","tryCatchPattern":"if err := root.RegisterComponent(name, fn); err != nil {\n    return fmt.Errorf(\"component %s param must be map[string]any/struct/any: %w\", name, err)\n}","preventionTips":["Accept only map[string]any, struct, *struct, or any as the props parameter","Move slices/primitives into struct fields or map entries","Validate all component signatures in a startup test to catch regressions early"],"tags":["go","reflection","signature","parameter-types"],"backgroundTag":"callback-signature-mismatch","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}