Skip to main content

Isograph

The UI framework for teams that move fast —
without breaking things

A tale as old as time

Your engineering team is growing.
Are you ready?

As your team grows, effective coordination and communication will become harder. No one will understand the system end-to-end, and no one will be able to guarantee that innocent-looking changes are safe to land. Your app will be chronically unstable.

You might combat this by adding process and manual testing, sacrificing developer velocity.

Or, you might restructure your app to make parts less interdependent. Each view will fetch its own data, leading to data being loaded redundantly and a chaotic loading experience. Or your query declarations will be append-only. Your servers will be hammered and your app will slow to a crawl.

Either way, your competitors will eat your lunch.

That's why we built Isograph

Isograph is an opinionated framework for building interactive, data-driven apps. It makes heavy use of its compiler and of generated code to enable developers to quickly and confidently build stable and performant apps, while providing an amazing developer experience.

Developers define components and the data they need. Isograph takes care of the rest.

With Isograph, there's no tradeoff between
velocity, stability and performance

Local reasoning

Developers can make changes to individual files without reasoning about the rest of the app. If you alter a component, there's no need to update query declarations or modify other files to ensure that the data makes it to the component — the compiler does that for you.

We lean into Isograph's compiler and into generated files so that you can focus on what matters — shipping features and iterating.

Unparalleled performance

On every save, the Isograph compiler generates queries that provide exactly the data needed by a view. Say goodbye to under- and over-fetching.

So as engineers add features or refactor components, no work is required to keep the app performant.

Build with confidence

Isograph components are truly independent. Changes to one don't affect the data that other components receive, and there are no common files (such as loaders or query declarations) where all changes must be coordinated. This is what makes apps built with Isograph stable, even as the apps evolve over time.

But Isograph goes further: it ensures that after mutations, all needed fields are refetched, meaning your app remains in a consistent state.

Watch the talk at GraphQL Conf 2024

I'm ready to learn more

Defining Isograph components

Isograph components are defined using an iso literal, in which we tell the Isograph compiler the name of the component (PetList) and the type on which that component can be accessed (the GraphQL type Query).

We also specify the data that that component requires (e.g. pets and id). Dependencies on other components, such as PetProfile, are declared in the same way!

To this iso literal, we pass the actual component that will be rendered. This is just a plain ol' React component, and all of the React features you would expect (e.g. state, context and suspense) work here.

Subcomponents

A typical Isograph app will be composed of many layers of Isograph components, before it bottoms out at server fields that are defined in a GraphQL schema.

In this example, PetProfile is another Isograph component. In PetList, we render it directly, and we don't pass anything down to it — even though the PetProfile component itself requires data (name and species)!

import {iso} from '@iso';

export const PetList = iso(`
field Query.PetList @component {
pets {
id
PetProfile
}
}
`)(function({ data }) {
return (<>
<h1>Pet Hotel Guest List</h1>
<p>{data.pets.length} pets checked in.</p>
{data.pets.map(pet => (
<pet.PetProfile key={pet.id} />
))}
</>);
});

export const PetProfile = iso(`
field Pet.PetProfile @component {
name
species
}
`)(function({ data }) {
return (<Box>
{data.name}, a {data.species}
</Box>);
});

Fetching data

Those components aren't doing much on their own. Somehow, we need to make a network request for the data and render the PetList component.

We start by writing iso(`entrypoint Query.PetList`). This instructs the Isograph compiler to generate a query for all of the data needed by Query.PetList component, or any of its subcomponents.

Next, we pass this to useLazyReference, which makes the network request when the PetListRoute component is rendered.

This gives us an opaque query reference. We get the result of that request by calling useResult, and render that.

import {iso} from '@iso';

export default function PetListRoute() {
const {fragmentReference} = useLazyReference(
iso(`entrypoint Query.PetList`),
{}
);

const PetList = useResult(fragmentReference);

return <PetList />;
}
Ask your doctor

Is Isograph right for me?

Interested in giving Isograph a try?

Join the Discord. Follow the official Twitter account. Check out the open issues on GitHub.

If you want to make substantial contributions on a fast-moving, ambitious project that is pushing the boundaries of what is possible with web development, Isograph is the project for you.