npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-blade

v0.0.1

Published

> Inline GraphQL for the age of Suspense

Downloads

6

Readme

React-Blade

Inline GraphQL for the age of Suspense

NPM JavaScript Style Guide

Caution: This library is still being made. None of the below actually exists yet, this is just documentation-driven development.

Why another GraphQL client?

All GraphQL client API's to date have a double declaration problem. Here's a sample adapted from the urql example:

const Home = () => (
  <Connect query={query(TodoQuery)}>
    {({ data }) => {
      return (
        <div>
          <TodoList todos={data.todos} />
        </div>
      );
    }}
  </Connect>
);

const TodoQuery = `
query {
  todos {
    id
    text
  }
}
`;

Everything requested in the graphql query string is then repeated in the code. On top of the ease of creating malformed queries, it is difficult to keep the query and code in sync as data needs change. There has to be a better way.

Here's the proposed Blade API:

const Home = () => (
  <Connect>
    {({ query }) => {
      query.todos = ["id", "text"];
      return (
        <div>
          <TodoList todos={query.todos} />
        </div>
      );
    }}
  </Connect>
);

How this works: query is actually wrapped with ES6 Proxies that throw a Promise wrapping a graphql query when asked for properties it does not have. Once it resolves, React Suspense's behavior is to rerender and the query succeeds as it is stored in cache.


API Walkthrough

Setting up the Provider

Blade's provider API is exactly the same as urql. But since Blade relies on React Suspense to work, to use Blade at all you must be in React's new AsyncMode.

import React, { AsyncMode } from "react";
import { Provider, Client } from "react-blade";
import Home from "./home";

const client = new Client({
  url: "http://localhost:3001/graphql"
});

export const App = () => (
  <AsyncMode>
    <Provider client={client}>
      <Home />
    </Provider>
  </AsyncMode>
);

Querying

import { Connect } from "blade";
// Blade-style query with subfields that aren't directly used
const Home = () => (
  <Connect>
    {({ query }) => {
      query.todos = ["id", "text"]; // setting subfields that we also want in our response
      return (
        <div>
          <TodoList todos={query.todos} />
        </div>
      );
    }}
  </Connect>
);

Because of our usage of React Suspense, the fetched data is normalized in our cache "for free" based on our usage. Here's an example of a query with multiple fields:

// Blade-style query with multiple fields
const Home = () => (
  <Connect>
    {({ query }) => {
      return (
        <>
          <h3>{query.user.name}</h3>
          <TodoList todos={query.todos} />
        </>
      );
    }}
  </Connect>
);

React suspends twice and the cache stores both query.user and query.todos separately. In the future we will have batching algorithms to execute suspenders in parallel.

Query Variables

In GraphQL every query variable is usually named twice:

// normal graphql query variable syntax
const GetTodo = `
query($text: String!) {
  getTodoByText(text: $text) {
    id
    text
  }
}
`;

Here, what we really want is to pass in a string to getTodoByText's text field, but have to come up with awkward naming conventions like $text to pass it in due to the limitations of the spec. More duplication, more chances of error.

We can do better. In Blade, you can supply query variables inline without having to provide an intermediate query variable name:

// Blade-style inline graphql query variable
const Home = () => (
  <Connect>
    {({ query }) => {
      query.getTodoByText = {text: 'Todo Text Content'}
      return <TodoItem todo={query.getTodoByText} />
      );
    }}
  </Connect>
);

Mutations

Mutations take pretty much the same format. Here's a GraphQL mutation:

// normal graphql mutation syntax
const AddTodo = `
mutation($text: String!) {
  addTodo(text: $text) {
    id
    text
  }
}
`;

Here it is inline in Blade:

// Blade-style mutation, note this doesn't need to use react suspense at all
// but the result of the mutation MUST return the new/changed item
// so that we can update our related cache in query.todos
const Home = () => (
  <Connect>
    {({ query, mutation }) => {
      mutation.addTodo = { text: "New Todo Added" };
      return (
        <>
          <TodoList todos={query.todos} />
          <button onClick={mutation.addTodo}>Add Todo</button>
        </>
      );
    }}
  </Connect>
);

Other render args

We take similar render args as urql:

Misc API notes

HOC Form

If you are nostalgic for when HOC's were cool, no problem:

import { connect } from "react-blade";
export default connect(MyComponent);
// define MyComponent and use the queries and mutations inline like you would anyway

But I like Decorators

You're weird, but ok

import { connect } from "react-blade";
@connect
class MyComponent extends React.Component {
  // define MyComponent and use the queries and mutations inline like you would anyway
}

Prior Art

urql

This library wouldn't be possible without urql. Ken Wheeler and Team Formidable are an inspiration to us all. Specifically, watching Ken mess up repeatedly during a live coding session demonstrating Urql gave me the original inspiration for solving the double declaration problem. Enormous amounts of inspiration for this lib came from urql and its architecture.

HowToGraphQL/GraphCool

I learned GraphQL on the lap of Nick Burke's extensive HowToGraphQL.com tutorial and even made my own tutorial as a capstone project.

License

MIT © swyx