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

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Fetch

func Fetch(url string, options Options) <-chan Result

Fetch performs an asynchronous HTTP fetch operation and returns a channel for the result. This uses the browser Fetch API from WASM; it currently returns the response body as text.

func ReturnChannel

func ReturnChannel(ch <-chan Result)

ReturnChannel returns a fetch result channel to the pool for reuse. With the new implementation channels are one-shot, so this is a no-op kept for API compatibility.

Types

type Options

type Options struct {
	Method  string
	Headers map[string]interface{}
	Body    interface{}
}

Options represents configuration for HTTP fetch operations.

type Resource

type Resource struct {
	// contains filtered or unexported fields
}

func UseFetch

func UseFetch(url string, options ...Options) Resource

UseFetch is a hook that simplifies data fetching within a component. It uses the new hooks.UseFetch entry point internally.

Example
package main

import (
	"fmt"

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

func main() {
	// Note: Hooks can only be used inside a component function

	// Fetch data from an API
	resource := fetch.UseFetch("https://api.example.com/data")

	state := resource.Get()

	if state.Loading {
		fmt.Println("Loading...")
		return
	}

	if state.Error != "" {
		fmt.Printf("Error: %s\n", state.Error)
		return
	}

	// Use the data
	fmt.Printf("Data: %v\n", state.Data)

	// Manually trigger a refetch
	resource.Refetch()
}

func (Resource) Get

func (r Resource) Get() State

func (Resource) Refetch

func (r Resource) Refetch()

type Result

type Result struct {
	Data interface{}
	Err  error
}

Result represents the result of a manual Fetch operation.

type State

type State = runtime.FetchState

State represents the state of a fetch operation managed by UseFetch.

Jump to

Keyboard shortcuts

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