fetch

package
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 14, 2026 License: MIT Imports: 0 Imported by: 0

README

Fetch Package

Location: /fetch

GoWebComponents/
├── dom/
├── hooks/
├── state/
├── render/
├── router/
├── fetch/            ← YOU ARE HERE
│   ├── doc.go
│   └── fetch.go
├── internal/
├── examples/
└── ...

Overview

The fetch package provides utilities for making HTTP requests from WebAssembly applications. It offers both declarative (hook-based) and imperative APIs for data fetching, with built-in loading states, error handling, and automatic re-fetching.

Core APIs

UseFetch(url string, options ...FetchOptions) (func() FetchState, func())

Declarative hook for data fetching within components. Automatically manages loading, error, and data states.

import "github.com/monstercameron/GoWebComponents/fetch"

func UserProfile(props dom.Attrs) *dom.Element {
    // Returns: (stateGetter, refetch)
    getState, refetch := fetch.UseFetch("https://api.example.com/user/1")
    state := getState()

    if state.Loading {
        return dom.P(nil, dom.Text("Loading..."))
    }

    if state.Error != "" {
        return dom.Div(nil,
            dom.P(nil, dom.Text("Error: "+state.Error)),
            dom.Button(dom.Attrs{
                "onclick": hooks.GoUseFunc(func(e dom.GoEvent) {
                    refetch()  // Retry the request
                }),
            }, dom.Text("Retry")),
        )
    }

    user := state.Data.(map[string]interface{})
    return dom.Div(nil,
        dom.H1(nil, dom.Text(user["name"].(string))),
        dom.P(nil, dom.Text(user["email"].(string))),
    )
}
GoFetch(url string, options FetchOptions) <-chan FetchResult

Imperative API for making HTTP requests from anywhere (event handlers, goroutines, etc.).

import "github.com/monstercameron/GoWebComponents/fetch"

func CreateUser(userData map[string]interface{}) {
    resultChan := fetch.GoFetch("https://api.example.com/users", fetch.FetchOptions{
        Method:  "POST",
        Headers: map[string]interface{}{
            "Content-Type": "application/json",
        },
        Body: userData,
    })

    go func() {
        result := <-resultChan
        fetch.ReturnFetchChannel(resultChan)  // IMPORTANT: Prevent memory leaks

        if result.Err != nil {
            fmt.Println("Error:", result.Err)
            return
        }

        fmt.Println("Success:", result.Data)
    }()
}

Data Types

FetchState
type FetchState struct {
    Data    interface{}  // Response data (parsed JSON)
    Loading bool         // Is request in progress?
    Error   string       // Error message if request failed
}
FetchResult
type FetchResult struct {
    Data interface{}  // Response data
    Err  error        // Error if request failed
}
FetchOptions
type FetchOptions struct {
    Method  string                 // HTTP method: GET, POST, PUT, DELETE, etc.
    Headers map[string]interface{} // Request headers
    Body    interface{}            // Request body (auto-JSON encoded if struct/map)
}

Usage Patterns

GET Request (Declarative)
func PostsList(props dom.Attrs) *dom.Element {
    getState, _ := fetch.UseFetch("https://jsonplaceholder.typicode.com/posts")
    state := getState()

    if state.Loading {
        return dom.P(nil, dom.Text("Loading posts..."))
    }

    if state.Error != "" {
        return dom.P(nil, dom.Text("Failed to load posts"))
    }

    posts := state.Data.([]interface{})
    items := make([]interface{}, len(posts))

    for i, post := range posts {
        p := post.(map[string]interface{})
        items[i] = dom.Li(dom.Attrs{"key": p["id"]},
            dom.Text(p["title"].(string)))
    }

    return dom.Ul(nil, items...)
}
POST Request (Imperative)
func CreatePostForm(props dom.Attrs) *dom.Element {
    title, setTitle := hooks.UseState("")
    body, setBody := hooks.UseState("")
    status, setStatus := hooks.UseState("")

    handleSubmit := hooks.GoUseFunc(func(e dom.GoEvent) {
        e.PreventDefault()
        setStatus("Creating...")

        postData := map[string]interface{}{
            "title": title(),
            "body":  body(),
            "userId": 1,
        }

        resultChan := fetch.GoFetch("https://jsonplaceholder.typicode.com/posts",
            fetch.FetchOptions{
                Method: "POST",
                Headers: map[string]interface{}{
                    "Content-Type": "application/json",
                },
                Body: postData,
            })

        go func() {
            result := <-resultChan
            fetch.ReturnFetchChannel(resultChan)

            if result.Err != nil {
                setStatus("Error: " + result.Err.Error())
            } else {
                setStatus("Post created successfully!")
                setTitle("")
                setBody("")
            }
        }()
    })

    return dom.Form(dom.Attrs{"onsubmit": handleSubmit},
        dom.Input(dom.Attrs{
            "value": title(),
            "oninput": hooks.GoUseFunc(func(e dom.GoEvent) {
                setTitle(e.GetValue())
            }),
            "placeholder": "Title",
        }),
        dom.Textarea(dom.Attrs{
            "value": body(),
            "oninput": hooks.GoUseFunc(func(e dom.GoEvent) {
                setBody(e.GetValue())
            }),
            "placeholder": "Body",
        }),
        dom.Button(nil, dom.Text("Create Post")),
        dom.P(nil, dom.Text(status())),
    )
}
PUT/PATCH Request
func UpdateUser(userID int, updates map[string]interface{}) {
    url := fmt.Sprintf("https://api.example.com/users/%d", userID)

    resultChan := fetch.GoFetch(url, fetch.FetchOptions{
        Method: "PUT",
        Headers: map[string]interface{}{
            "Content-Type": "application/json",
        },
        Body: updates,
    })

    go func() {
        result := <-resultChan
        fetch.ReturnFetchChannel(resultChan)

        if result.Err != nil {
            fmt.Println("Update failed:", result.Err)
        } else {
            fmt.Println("User updated:", result.Data)
        }
    }()
}
DELETE Request
func DeletePost(postID int) {
    url := fmt.Sprintf("https://api.example.com/posts/%d", postID)

    resultChan := fetch.GoFetch(url, fetch.FetchOptions{
        Method: "DELETE",
    })

    go func() {
        result := <-resultChan
        fetch.ReturnFetchChannel(resultChan)

        if result.Err != nil {
            fmt.Println("Delete failed:", result.Err)
        } else {
            fmt.Println("Post deleted successfully")
        }
    }()
}
Custom Headers & Authentication
func AuthenticatedRequest() {
    token := "your-jwt-token"

    resultChan := fetch.GoFetch("https://api.example.com/protected",
        fetch.FetchOptions{
            Method: "GET",
            Headers: map[string]interface{}{
                "Authorization": "Bearer " + token,
                "Accept":        "application/json",
            },
        })

    go func() {
        result := <-resultChan
        fetch.ReturnFetchChannel(resultChan)

        // Handle result
    }()
}

Advanced Patterns

Conditional Fetching
func UserPosts(props dom.Attrs) *dom.Element {
    userID, _ := state.UseAtom("selected-user-id", 0)
    currentUserID := userID()

    // Only fetch if userID is set
    if currentUserID == 0 {
        return dom.P(nil, dom.Text("Select a user"))
    }

    url := fmt.Sprintf("https://api.example.com/users/%d/posts", currentUserID)
    getState, _ := fetch.UseFetch(url)
    state := getState()

    // ... render posts
}
Polling/Auto-Refresh
func LiveData(props dom.Attrs) *dom.Element {
    getState, refetch := fetch.UseFetch("https://api.example.com/live-data")

    // Poll every 5 seconds
    hooks.UseEffect(func() {
        ticker := time.NewTicker(5 * time.Second)
        done := make(chan bool)

        go func() {
            for {
                select {
                case <-ticker.C:
                    refetch()
                case <-done:
                    ticker.Stop()
                    return
                }
            }
        }()

        // Cleanup
        return func() {
            done <- true
        }
    }, nil)

    state := getState()
    // ... render data
}
Parallel Requests
func Dashboard(props dom.Attrs) *dom.Element {
    getUsers, _ := fetch.UseFetch("https://api.example.com/users")
    getPosts, _ := fetch.UseFetch("https://api.example.com/posts")
    getComments, _ := fetch.UseFetch("https://api.example.com/comments")

    usersState := getUsers()
    postsState := getPosts()
    commentsState := getComments()

    if usersState.Loading || postsState.Loading || commentsState.Loading {
        return dom.P(nil, dom.Text("Loading dashboard..."))
    }

    // All three requests complete, render dashboard
}

Error Handling

Retry Logic
func RobustFetch(props dom.Attrs) *dom.Element {
    getState, refetch := fetch.UseFetch("https://api.example.com/data")
    retryCount, setRetryCount := hooks.UseState(0)

    state := getState()

    if state.Error != "" {
        return dom.Div(nil,
            dom.P(nil, dom.Text("Error: "+state.Error)),
            dom.P(nil, dom.Text(fmt.Sprintf("Retries: %d", retryCount()))),
            dom.Button(dom.Attrs{
                "onclick": hooks.GoUseFunc(func(e dom.GoEvent) {
                    setRetryCount(retryCount() + 1)
                    refetch()
                }),
            }, dom.Text("Retry")),
        )
    }

    // ... render data
}
Timeout Handling
func FetchWithTimeout(url string, timeout time.Duration) <-chan FetchResult {
    resultChan := fetch.GoFetch(url, fetch.FetchOptions{Method: "GET"})
    timeoutChan := make(chan FetchResult, 1)

    go func() {
        select {
        case result := <-resultChan:
            fetch.ReturnFetchChannel(resultChan)
            timeoutChan <- result
        case <-time.After(timeout):
            fetch.ReturnFetchChannel(resultChan)
            timeoutChan <- FetchResult{
                Err: fmt.Errorf("request timeout after %v", timeout),
            }
        }
    }()

    return timeoutChan
}

Best Practices

1. Always Return Channels
// ✅ Good
result := <-resultChan
fetch.ReturnFetchChannel(resultChan)

// ❌ Bad - memory leak
result := <-resultChan
// Channel not returned!
2. Use Declarative API in Components
// ✅ Good - automatic state management
func Component(props dom.Attrs) *dom.Element {
    getState, _ := fetch.UseFetch(url)
    state := getState()
    // ...
}

// ❌ Less ideal - manual state management
func Component(props dom.Attrs) *dom.Element {
    data, setData := hooks.UseState(nil)
    loading, setLoading := hooks.UseState(true)

    hooks.UseEffect(func() {
        // Manual fetch with GoFetch...
    }, nil)
}
3. Handle All States
func GoodComponent(props dom.Attrs) *dom.Element {
    getState, _ := fetch.UseFetch(url)
    state := getState()

    // Always handle all three states
    if state.Loading {
        return LoadingSpinner()
    }

    if state.Error != "" {
        return ErrorDisplay(state.Error)
    }

    return DataDisplay(state.Data)
}

Examples

See fetching in action:

Documentation

See doc.go for the official Go package documentation.

Documentation

Overview

Package fetch provides declarative data fetching utilities for GoWebComponents.

This package simplifies asynchronous HTTP requests in WASM applications with handle-based hooks that manage loading, error, and data states.

Basic usage:

import "github.com/monstercameron/GoWebComponents/fetch"

func UserList() ui.Node {
    resource := fetch.UseFetch("/api/users")
    state := resource.Get()

    if state.Loading {
        return html.Div(html.Props{}, html.P(html.Props{}, html.Text("Loading...")))
    }

    if state.Error != "" {
        return html.Div(html.Props{}, html.P(html.Props{}, html.Text("Error: "+state.Error)))
    }

    users := state.Data.(string) // Parse JSON or use as needed
    refresh := ui.UseEvent(func() {
        resource.Refetch()
    })
    return html.Div(html.Props{},
        html.H1(html.Props{}, html.Text("Users")),
        html.Pre(html.Props{}, html.Text(users)),
        html.Button(html.Props{OnClick: refresh}, html.Text("Refresh")),
    )
}

Available functions:

  • UseFetch: Hook for manual fetch state management via a typed handle
  • Fetch: Low-level fetch function returning a channel for manual control

The UseFetch hook:

  • Manages loading, error, and data states
  • Returns a handle with Get and Refetch methods
  • Leaves fetch timing under caller control

For more control, use the Fetch function directly:

ch := fetch.Fetch("/api/data", fetch.Options{
    Method: "POST",
    Headers: map[string]interface{}{"Content-Type": "application/json"},
    Body: `{"key": "value"}`,
})
result := <-ch
if result.Err != nil {
    // Handle error
}
// Use result.Data

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL