Jeff Caldwell

/blog/using-echo-context-with-templ-components

Using Echo's Context with Templ components

How to use Echo's echo.Context as ctx values in Templ components.

When I was setting up the handlers and views for this site, I wanted to be able to pass a currentPath value to the site's Templ header component so I could use it to highlight the current page link. According to Templ's documentation, you can pass values to components using Go's context.Context. The process is simple enough if you're using standard context, and I thought I would be able to easily use Echo's echo.Context interchangeably.

I was very wrong.

The problem is that echo.Context and context.Context are completely different things. Echo's Context carries references to all sorts of useful things about the current http request, but it's not a standard Context type. It does, however, carry with it the standard Request.Context, which is. So, the trick to passing context to Templ components in Echo is to modify the request's context. We can do that with a middleware.

Modifying request.Context with middleware

While I was searching for a method to set a context.Context value, I came upon this answer on StackOverflow. It's a middleware that reimplements the Get and Set methods on echo.Context to update request.Context whenever they're called. It's relatively straightforward to add this middleware to your app, so let's put it together.

1. Extend echo.Context

First, we need to implement our own Get and Set methods so we can modify request.Context whenever we update echo.Context.

package custommiddleware
// extend echo.Context
type contextValue {
  echo.Context
}

func (c contextValue) Get(key string) interface{} {
  // grab value from echo.Context
  val := c.Context.Get(key)
  // if it's not nil, return it
  if val != nil {
    return val
  }
  // otherwise, return Request.Context
  return c.Request().Context().Value(key)
}

func (c contextValue) Set(key string, val interface{}) {
  // we're replacing the whole Request in echo.Context
  // with a copied request that has the updated context value
  c.SetRequest(
    c.Request().WithContext(
      context.WithValue(c.Request().Context(), key, value)
    )
  )
}

Okay, we have an extended context with custom Get and Set methods. Now we just need a middleware that will run everything on every request.

2. The middleware

Middlewares are pretty simple. They're functions that take a HandlerFunc as an argument and return a HandlerFunc. They modify the request and pass it along to the next handler. In Echo, these handlers carry the echo.Context with them. That means we can just swap out our new contextValue before moving on to the next handler.

package custommiddleware

func ContextValueMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
  // this is just an echo.HandlerFunc
	return func(c echo.Context) error {
    // instead of passing next(c) as you usually would,
    // you return it with the extended version
		return next(contextValue{c})
	}
}

3. Adding the new middleware

You use this middleware just like you would with any Echo app.

app := echo.New()
// ... routes, etc.
app.Use(custommiddleware.ContextValueMiddleware)
// ... more middleware, etc.

That's pretty much it! Now I can update the current path context value and use it in Templ components without the need for ugly prop drilling. Here's how I did it:

// make another middleware that sets `currentPath`
package custommiddleware

func SetCurrentPath(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		c.Set("currentPath", c.Request().URL.Path)
		return next(c)
	}
}
// my templ component can now access ctx.CurrentPath
package component

// GetCurrentPath ensures that we're always
// returning a string from `ctx`
func GetCurrentPath(c context.Context) string {
	currentPath := c.Value("currentPath")
	if currentPath != nil {
		return currentPath.(string)
	}
	return ""
}


templ Header() {
  <nav>
    <ul>
      <li>
        if GetCurrentPath(ctx) == "/" {
          <a href="/" class="current nav-link">Home</a>
        } else {
          <a href="/" class="nav-link">Home</a>
        }
      </li>
      <!-- etc., etc. -->
    </ul>
  </nav>
}

It's a super simple process that greatly simplifies passing values to my components. I hope it helps you out in your projects. Be sure to give eudore some props over on StackOverflow.