eddev 2.3.11-beta.8 → 2.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/entry/MetaTags.d.ts +1 -1
- package/dist/app/entry/MetaTags.d.ts.map +1 -1
- package/dist/app/entry/MetaTags.js +2 -0
- package/dist/app/entry/hydration-script.js +1 -1
- package/dist/app/lib/blocks/BlockPropMutator.d.ts +1 -1
- package/dist/app/lib/blocks/BlockPropMutator.d.ts.map +1 -1
- package/dist/app/lib/blocks/SlotBlocks.d.ts +1 -1
- package/dist/app/lib/blocks/SlotBlocks.d.ts.map +1 -1
- package/dist/app/lib/blocks/SlotBlocks.js +2 -2
- package/dist/app/lib/blocks/block-debugger.d.ts +3 -3
- package/dist/app/lib/blocks/block-debugger.d.ts.map +1 -1
- package/dist/app/lib/blocks/block-debugger.js +1 -1
- package/dist/app/lib/blocks/block-utils.d.ts +2 -2
- package/dist/app/lib/blocks/block-utils.d.ts.map +1 -1
- package/dist/app/lib/blocks/block-utils.js +10 -3
- package/dist/app/lib/blocks/builtin-blocks.js +1 -1
- package/dist/app/lib/blocks/defineBlock.js +2 -2
- package/dist/app/lib/blocks/editor/EditorHighlights.js +1 -1
- package/dist/app/lib/blocks/editor/create-block.d.ts +1 -1
- package/dist/app/lib/blocks/editor/create-block.d.ts.map +1 -1
- package/dist/app/lib/blocks/editor/root-blocks.d.ts.map +1 -1
- package/dist/app/lib/blocks/editor/root-blocks.js +4 -4
- package/dist/app/lib/devtools/components/DevMenu.js +4 -4
- package/dist/app/lib/devtools/components/DevUI.js +5 -5
- package/dist/app/lib/devtools/components/panels/AppDataPanel.js +4 -4
- package/dist/app/lib/devtools/components/panels/QueriesPanel.js +1 -1
- package/dist/app/lib/devtools/components/panels/RoutePanel.d.ts +1 -1
- package/dist/app/lib/devtools/components/panels/RoutePanel.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/panels/RoutePanel.js +8 -8
- package/dist/app/lib/devtools/components/panels/StructurePanel.js +8 -8
- package/dist/app/lib/devtools/components/panels/index.js +5 -5
- package/dist/app/lib/devtools/components/ui/JSONInspector.js +2 -2
- package/dist/app/lib/devtools/components/ui/Panel.js +1 -1
- package/dist/app/lib/devtools/components/ui/QueryMonitorDisplay.d.ts +1 -1
- package/dist/app/lib/devtools/components/ui/QueryMonitorDisplay.d.ts.map +1 -1
- package/dist/app/lib/devtools/components/ui/QueryMonitorDisplay.js +3 -3
- package/dist/app/lib/devtools/components/ui/TabBar.js +2 -2
- package/dist/app/lib/devtools/components/ui/Tabs.js +2 -2
- package/dist/app/lib/devtools/hooks/useTailwind.d.ts +5 -5
- package/dist/app/lib/dynamic/dynamic.js +1 -1
- package/dist/app/lib/hooks/query-hooks.d.ts +9 -9
- package/dist/app/lib/hooks/query-hooks.d.ts.map +1 -1
- package/dist/app/lib/hooks/query-hooks.js +2 -1
- package/dist/app/lib/integrations/gravityforms/createGravityFormComponent.d.ts +3 -3
- package/dist/app/lib/integrations/gravityforms/createGravityFormComponent.d.ts.map +1 -1
- package/dist/app/lib/integrations/gravityforms/createGravityFormComponent.js +1 -1
- package/dist/app/lib/integrations/gravityforms/field-types.d.ts +1 -1
- package/dist/app/lib/integrations/gravityforms/field-types.d.ts.map +1 -1
- package/dist/app/lib/legacy-stitches/createStitches.d.ts +1 -509
- package/dist/app/lib/legacy-stitches/createStitches.d.ts.map +1 -1
- package/dist/app/lib/routing/hooks/useRouteMeta.d.ts +1 -1
- package/dist/app/lib/routing/hooks/useRouteMeta.d.ts.map +1 -1
- package/dist/app/lib/runtime/apiConfig.d.ts +15 -0
- package/dist/app/lib/runtime/apiConfig.d.ts.map +1 -1
- package/dist/app/lib/runtime/errorHandling.d.ts +2 -2
- package/dist/app/lib/runtime/errorHandling.d.ts.map +1 -1
- package/dist/app/lib/runtime/index.d.ts +2 -2
- package/dist/app/lib/runtime/index.d.ts.map +1 -1
- package/dist/app/lib/runtime/index.js +2 -2
- package/dist/app/server/proxy-wp-admin.d.ts.map +1 -1
- package/dist/app/server/proxy-wp-admin.js +1 -0
- package/dist/app/server/rpc.d.ts +1 -1
- package/dist/app/server/rpc.d.ts.map +1 -1
- package/dist/app/server/server-context.d.ts +3 -3
- package/dist/app/server/server-context.d.ts.map +1 -1
- package/dist/app/server/server-context.js +5 -5
- package/dist/app/server/utils/content-security.d.ts +2 -2
- package/dist/app/server/utils/content-security.d.ts.map +1 -1
- package/dist/app/utils/trpc-client.d.ts.map +1 -1
- package/dist/app/utils/trpc-client.js +19 -3
- package/dist/app/utils/wp.d.ts +10 -10
- package/dist/app/utils/wp.d.ts.map +1 -1
- package/dist/node/cli/cli.js +3 -1
- package/dist/node/cli/version.d.ts +1 -1
- package/dist/node/cli/version.d.ts.map +1 -1
- package/dist/node/cli/version.js +1 -1
- package/dist/node/compiler/dev-server.js +1 -1
- package/dist/node/compiler/get-vite-config.d.ts.map +1 -1
- package/dist/node/compiler/get-vite-config.js +2 -0
- package/dist/node/compiler/vinxi-app.d.ts +1 -1
- package/dist/node/compiler/vinxi-app.d.ts.map +1 -1
- package/dist/node/compiler/vinxi-codegen.d.ts.map +1 -1
- package/dist/node/compiler/vinxi-codegen.js +2 -3
- package/dist/node/project/favicons.d.ts +1 -1
- package/dist/node/project/favicons.d.ts.map +1 -1
- package/dist/node/storybook/index.js +1 -1
- package/dist/node/utils/fetch-wp.d.ts.map +1 -1
- package/dist/node/utils/fetch-wp.js +2 -1
- package/dist/node/utils/fs.d.ts +22 -19
- package/dist/node/utils/fs.d.ts.map +1 -1
- package/package.json +2 -2
- package/skills/eddev/SKILL.md +156 -0
- package/skills/eddev/docs/acf/admin-panel-widgets.mdx +99 -0
- package/skills/eddev/docs/acf/custom-enums.mdx +75 -0
- package/skills/eddev/docs/acf/custom-fields.mdx +131 -0
- package/skills/eddev/docs/acf.mdx +31 -0
- package/skills/eddev/docs/blocks/block-definition.mdx +189 -0
- package/skills/eddev/docs/blocks/core-blocks.mdx +86 -0
- package/skills/eddev/docs/blocks/data-and-editing.mdx +219 -0
- package/skills/eddev/docs/blocks/editor-config.mdx +157 -0
- package/skills/eddev/docs/blocks/nested-blocks.mdx +129 -0
- package/skills/eddev/docs/blocks/overview.mdx +58 -0
- package/skills/eddev/docs/blocks/template-parts.mdx +131 -0
- package/skills/eddev/docs/config.mdx +200 -0
- package/skills/eddev/docs/design/color.mdx +185 -0
- package/skills/eddev/docs/design/favicons.mdx +103 -0
- package/skills/eddev/docs/design/grid.mdx +120 -0
- package/skills/eddev/docs/design/icons.mdx +197 -0
- package/skills/eddev/docs/design/responsive-scaling.mdx +312 -0
- package/skills/eddev/docs/design/type.mdx +125 -0
- package/skills/eddev/docs/devtool/cli.mdx +201 -0
- package/skills/eddev/docs/devtool/overlay.mdx +5 -0
- package/skills/eddev/docs/getting-started.mdx +53 -0
- package/skills/eddev/docs/graphql/extending.mdx +186 -0
- package/skills/eddev/docs/graphql/fragments.mdx +107 -0
- package/skills/eddev/docs/graphql/global-data.mdx +47 -0
- package/skills/eddev/docs/graphql/infinite-queries.mdx +95 -0
- package/skills/eddev/docs/graphql/mutation-hooks.mdx +111 -0
- package/skills/eddev/docs/graphql/query-hooks.mdx +122 -0
- package/skills/eddev/docs/graphql/tooling.mdx +50 -0
- package/skills/eddev/docs/graphql.mdx +97 -0
- package/skills/eddev/docs/guides/color-schemes.mdx +204 -0
- package/skills/eddev/docs/guides/integrations.mdx +3 -0
- package/skills/eddev/docs/guides/page-transitions.mdx +5 -0
- package/skills/eddev/docs/guides/seo.mdx +5 -0
- package/skills/eddev/docs/guides/state-management.mdx +5 -0
- package/skills/eddev/docs/infra/caching.mdx +9 -0
- package/skills/eddev/docs/infra/deployment.mdx +13 -0
- package/skills/eddev/docs/infra/local.mdx +5 -0
- package/skills/eddev/docs/infra/security.mdx +11 -0
- package/skills/eddev/docs/routing/api.mdx +731 -0
- package/skills/eddev/docs/routing/custom.mdx +123 -0
- package/skills/eddev/docs/routing/full-details.mdx +37 -0
- package/skills/eddev/docs/routing/wordpress.mdx +70 -0
- package/skills/eddev/docs/routing.mdx +18 -0
- package/skills/eddev/docs/serverless/functions.mdx +436 -0
- package/skills/eddev/docs/serverless.mdx +202 -0
- package/skills/eddev/docs/snippets/automated-block-layouts.mdx +97 -0
- package/skills/eddev/docs/snippets/custom-routes-and-urls.mdx +91 -0
- package/skills/eddev/docs/snippets/multiple-editable-zones.mdx +87 -0
- package/skills/eddev/docs/snippets/querying-specific-blocks.mdx +164 -0
- package/skills/eddev/docs/snippets/submitting-forms-to-rpc.mdx +91 -0
- package/skills/eddev/docs/snippets/svgs.mdx +38 -0
- package/skills/eddev/docs/snippets/type-safe-acf-dropdowns.mdx +72 -0
- package/skills/eddev/docs/snippets.mdx +19 -0
- package/skills/eddev/docs/software.mdx +19 -0
- package/skills/eddev/docs/stack/how-it-works.mdx +50 -0
- package/skills/eddev/docs/stack/overview.mdx +56 -0
- package/skills/eddev/docs/stack/spa-vs-ssr.mdx +52 -0
- package/skills/eddev/docs/views/app-view.mdx +97 -0
- package/skills/eddev/docs/views/page-templates.mdx +82 -0
- package/skills/eddev/docs/views/queries.mdx +116 -0
- package/skills/eddev/docs/views.mdx +63 -0
- package/skills/eddev/index.mdx +79 -0
- package/tsconfig.app.json +2 -2
- package/tsconfig.node.json +6 -3
- package/types.env.d.ts +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Getting Started (/docs/getting-started)
|
|
2
|
+
|
|
3
|
+
**Set up a new eddev project from the starter theme.**
|
|
4
|
+
|
|
5
|
+
## Initial Setup [#initial-setup]
|
|
6
|
+
|
|
7
|
+
All new sites should use the following process when setting up a new site:
|
|
8
|
+
|
|
9
|
+
1. [Create a new repo on Github](https://github.com/new)
|
|
10
|
+
1. Set `Owner` to `ed-digital`, and name your repo like `some-client-name`.
|
|
11
|
+
2. Set `Repository Template` to `ed-digital/eddev-starter-theme`
|
|
12
|
+
3. Hit `Create repository`
|
|
13
|
+
2. Create a new site in [Local](/docs/infra/local), on your computer.
|
|
14
|
+
1. Use the default settings
|
|
15
|
+
2. Be sure to use `ed_admin` as the username, and generate a secure password using 1Password or similar, and the email address should be `web@ed.com.au`
|
|
16
|
+
3. Save the username and password to 1Pass.
|
|
17
|
+
3. Install the following plugins:
|
|
18
|
+
1. Advanced Custom Fields Pro ([download](https://www.advancedcustomfields.com/my-account/view-licenses/)) (login details in 1Password)
|
|
19
|
+
2. Add the licence key as the key for ACF Pro under 'Custom Fields > Updates'. The licence key is also in 1Password for convenience.
|
|
20
|
+
3. Slim SEO — install from plugins dashboard
|
|
21
|
+
4. Nested Pages — install from plugins dashboard
|
|
22
|
+
4. Under Settings -> General, set Timezone to Sydney (or the appropriate timezone).
|
|
23
|
+
5. Make sure that all the installed plugins have been activated.
|
|
24
|
+
6. Clone your newly created theme to `wp-content/themes/`, making sure that the repo name and the theme folder name are identical.
|
|
25
|
+
7. Run the following commands inside the theme folder
|
|
26
|
+
1. `composer update`
|
|
27
|
+
2. `yarn`
|
|
28
|
+
3. `yarn add --dev eddev@latest`
|
|
29
|
+
4. `yarn setup`
|
|
30
|
+
8. Activate the theme under Appearance > Themes
|
|
31
|
+
9. Run `yarn dev`
|
|
32
|
+
10. Add, Commit and Push all code changes back to Github.
|
|
33
|
+
|
|
34
|
+
## Initial WP Engine Setup [#initial-wp-engine-setup]
|
|
35
|
+
|
|
36
|
+
After you've followed the steps above, and the site is running correctly locally, you can create a site on WP Engine:
|
|
37
|
+
|
|
38
|
+
1. [Log into WP Engine](https://my.wpengine.com/sites)
|
|
39
|
+
2. Navigate through **Add Site → Build a new site → Get Started**
|
|
40
|
+
3. Ensure 'ED.' is selected under the account picker.
|
|
41
|
+
4. Select 'I will own it'
|
|
42
|
+
5. Once complete, restart Local, run `yarn build` once, and do an initial push to the new site, with all files and the database.
|
|
43
|
+
|
|
44
|
+
## Initial Vercel Setup [#initial-vercel-setup]
|
|
45
|
+
|
|
46
|
+
Once the WP Engine site is up and running, you can create the Vercel project:
|
|
47
|
+
|
|
48
|
+
1. Log into [Vercel](https://vercel.com/)
|
|
49
|
+
2. On the 'Overview' tab, navigate to **Add New\... → Project**
|
|
50
|
+
3. Select our team from the dropdown, and choose the site repo, then hit 'Import'.
|
|
51
|
+
4. Ensure the project name looks nice
|
|
52
|
+
5. Add an Environment Variable with `Key: SITE_URL`, `Value: https://{staging-site-url}`
|
|
53
|
+
6. Hit `Deploy`
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Extending the GraphQL Schema (/docs/graphql/extending)
|
|
2
|
+
|
|
3
|
+
**Extend WPGraphQL when frontend data needs custom schema fields.**
|
|
4
|
+
|
|
5
|
+
Most projects only need fields that come from WordPress, ACF, registered post
|
|
6
|
+
types, and blocks. When the frontend needs a value that does not naturally exist
|
|
7
|
+
in the schema, extend WPGraphQL in the WordPress theme or plugin code.
|
|
8
|
+
|
|
9
|
+
Use the GraphiQL IDE while writing schema extensions. It is the quickest way to
|
|
10
|
+
check the type names you are extending, the fields you have registered, and the
|
|
11
|
+
shape returned by your resolver.
|
|
12
|
+
|
|
13
|
+
## Add A Field [#add-a-field]
|
|
14
|
+
|
|
15
|
+
Use `register_graphql_field` when you need to add a computed field to an
|
|
16
|
+
existing schema type.
|
|
17
|
+
|
|
18
|
+
```php
|
|
19
|
+
add_action('graphql_register_types', function () {
|
|
20
|
+
register_graphql_field('Film', 'totalSessions', [
|
|
21
|
+
'type' => 'Int',
|
|
22
|
+
'description' => 'The number of sessions for this film.',
|
|
23
|
+
'resolve' => function ($root) {
|
|
24
|
+
$sessions = get_post_meta($root->ID, 'total_sessions', true);
|
|
25
|
+
return $sessions ? (int)$sessions : null;
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The resolver receives the current GraphQL root object first. For post types this
|
|
32
|
+
usually gives you access to `$root->ID`, which can be used with `get_post_meta`,
|
|
33
|
+
ACF, or project model helpers.
|
|
34
|
+
|
|
35
|
+
## Return Posts [#return-posts]
|
|
36
|
+
|
|
37
|
+
When a resolver returns a WordPress post and you want GraphQL subfields to keep
|
|
38
|
+
working, wrap the post in `WPGraphQL\Model\Post`.
|
|
39
|
+
|
|
40
|
+
```php
|
|
41
|
+
add_action('graphql_register_types', function () {
|
|
42
|
+
register_graphql_field('RootQuery', 'currentProgram', [
|
|
43
|
+
'type' => 'Program',
|
|
44
|
+
'resolve' => function () {
|
|
45
|
+
$program = get_field('current_program', 'option');
|
|
46
|
+
|
|
47
|
+
if ($program) {
|
|
48
|
+
return new WPGraphQL\Model\Post($program);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The same applies to lists of posts:
|
|
58
|
+
|
|
59
|
+
```php
|
|
60
|
+
register_graphql_field('Film', 'guests', [
|
|
61
|
+
'type' => ['list_of' => 'Guest'],
|
|
62
|
+
'resolve' => function ($root) {
|
|
63
|
+
$guests = Film::fromPost($root->ID)->getGuests();
|
|
64
|
+
|
|
65
|
+
return array_map(function ($id) {
|
|
66
|
+
return new WPGraphQL\Model\Post(get_post($id));
|
|
67
|
+
}, $guests);
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Add A Root Query [#add-a-root-query]
|
|
73
|
+
|
|
74
|
+
Root fields are useful when the frontend needs a named project-level entry
|
|
75
|
+
point, such as the current programme, search results, or a custom data system.
|
|
76
|
+
|
|
77
|
+
```php
|
|
78
|
+
add_action('graphql_register_types', function () {
|
|
79
|
+
register_graphql_field('RootQuery', 'siteSearch', [
|
|
80
|
+
'type' => ['list_of' => 'ContentNode'],
|
|
81
|
+
'args' => [
|
|
82
|
+
'search' => ['type' => 'String'],
|
|
83
|
+
],
|
|
84
|
+
'resolve' => function ($root, $args) {
|
|
85
|
+
$query = new WP_Query([
|
|
86
|
+
's' => $args['search'] ?? '',
|
|
87
|
+
'post_type' => ['page', 'post'],
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
return array_map(function ($post) {
|
|
91
|
+
return new WPGraphQL\Model\Post($post);
|
|
92
|
+
}, $query->posts);
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Keep root fields narrow and intentional. If a field starts to behave like a
|
|
99
|
+
feature API, consider whether it should be a query hook, a serverless function,
|
|
100
|
+
or a dedicated WordPress endpoint instead.
|
|
101
|
+
|
|
102
|
+
## Add Object Types [#add-object-types]
|
|
103
|
+
|
|
104
|
+
Use `register_graphql_object_type` when the field returns structured data that
|
|
105
|
+
does not already have a GraphQL type.
|
|
106
|
+
|
|
107
|
+
```php
|
|
108
|
+
class FilmAssetProgram {
|
|
109
|
+
public function __construct(public WP_Post $program) {}
|
|
110
|
+
|
|
111
|
+
static function registerType() {
|
|
112
|
+
register_graphql_object_type('FilmAssetProgram', [
|
|
113
|
+
'fields' => [
|
|
114
|
+
'program' => [
|
|
115
|
+
'type' => 'Program',
|
|
116
|
+
'resolve' => function (FilmAssetProgram $program) {
|
|
117
|
+
return new WPGraphQL\Model\Post($program->program);
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
'buckets' => [
|
|
121
|
+
'type' => ['list_of' => 'FilmAssetBucket'],
|
|
122
|
+
'resolve' => function (FilmAssetProgram $program) {
|
|
123
|
+
return $program->getBuckets();
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
],
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Then return that object type from a field:
|
|
133
|
+
|
|
134
|
+
```php
|
|
135
|
+
add_action('graphql_register_types', function () {
|
|
136
|
+
FilmAssetProgram::registerType();
|
|
137
|
+
FilmAssetBucket::registerType();
|
|
138
|
+
|
|
139
|
+
register_graphql_field('RootQuery', 'assetBuckets', [
|
|
140
|
+
'type' => 'FilmAssetProgram',
|
|
141
|
+
'args' => [
|
|
142
|
+
'programId' => ['type' => 'ID'],
|
|
143
|
+
],
|
|
144
|
+
'resolve' => function ($root, $args) {
|
|
145
|
+
$program = get_post($args['programId']);
|
|
146
|
+
|
|
147
|
+
if (!$program) {
|
|
148
|
+
throw new \GraphQL\Error\UserError('Program not found');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return new FilmAssetProgram($program);
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Add Enums [#add-enums]
|
|
158
|
+
|
|
159
|
+
Enums are useful when a field has a small, known set of values.
|
|
160
|
+
|
|
161
|
+
```php
|
|
162
|
+
enum EventType: string {
|
|
163
|
+
case Film = 'Film';
|
|
164
|
+
case Event = 'Event';
|
|
165
|
+
case Short = 'Short';
|
|
166
|
+
case Subscription = 'Subscription';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
add_action('graphql_register_types', function () {
|
|
170
|
+
register_graphql_enum_type('EventType', [
|
|
171
|
+
'description' => 'Standard event types',
|
|
172
|
+
'values' => php_enum_cases_to_graphql_cases(EventType::cases()),
|
|
173
|
+
]);
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Enums make the generated TypeScript types more useful than a loose `string`.
|
|
178
|
+
|
|
179
|
+
## Keep Extensions Small [#keep-extensions-small]
|
|
180
|
+
|
|
181
|
+
Schema extensions are easiest to maintain when they sit close to the model they
|
|
182
|
+
describe. Prefer small fields on the relevant type over one large root field
|
|
183
|
+
that returns unrelated data.
|
|
184
|
+
|
|
185
|
+
When a resolver is expensive, cache the underlying data in WordPress or expose a
|
|
186
|
+
runtime query with an explicit `# ttl` comment in the `.graphql` file.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# GraphQL Fragments (/docs/graphql/fragments)
|
|
2
|
+
|
|
3
|
+
**Share GraphQL selections and generated fragment types across queries.**
|
|
4
|
+
|
|
5
|
+
[GraphQL fragments](https://graphql.org/learn/queries/#fragments) are reusable selections. Use them when the same object shape appears in multiple view, block, or runtime queries.
|
|
6
|
+
|
|
7
|
+
Fragments live in `queries/fragments/**/*.graphql`.
|
|
8
|
+
|
|
9
|
+
```graphql filename="queries/fragments/ResponsiveImage.graphql"
|
|
10
|
+
fragment ResponsiveImage on MediaItem {
|
|
11
|
+
mediaItemUrl
|
|
12
|
+
caption
|
|
13
|
+
srcSet
|
|
14
|
+
altText
|
|
15
|
+
mediaDetails {
|
|
16
|
+
width
|
|
17
|
+
height
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then spread the fragment anywhere the schema returns that type:
|
|
23
|
+
|
|
24
|
+
```graphql filename="views/single.graphql"
|
|
25
|
+
query Single($postId: ID!) {
|
|
26
|
+
post(id: $postId, idType: DATABASE_ID) {
|
|
27
|
+
title
|
|
28
|
+
featuredImage {
|
|
29
|
+
node {
|
|
30
|
+
...ResponsiveImage
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
eddev automatically includes fragment files during codegen, so the fragment type is available in the generated `/types.graphql.ts` file.
|
|
38
|
+
|
|
39
|
+
## Fragment Types [#fragment-types]
|
|
40
|
+
|
|
41
|
+
Import generated fragment types from `@generated-types`. This is a project alias for the generated `/types.graphql.ts` file.
|
|
42
|
+
|
|
43
|
+
```tsx filename="components/atoms/ResponsiveImage.tsx"
|
|
44
|
+
import { ResponsiveImageFragment } from "@generated-types"
|
|
45
|
+
|
|
46
|
+
type Props = Partial<ResponsiveImageFragment>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Tiles And Repeated Shapes [#tiles-and-repeated-shapes]
|
|
50
|
+
|
|
51
|
+
Fragments are especially useful for cards, tiles, and reusable listing components.
|
|
52
|
+
|
|
53
|
+
```graphql filename="queries/fragments/CaseStudyTile.graphql"
|
|
54
|
+
fragment CaseStudyTile on CaseStudy {
|
|
55
|
+
uri
|
|
56
|
+
title
|
|
57
|
+
subtitle
|
|
58
|
+
info {
|
|
59
|
+
heroImage {
|
|
60
|
+
...ResponsiveImage
|
|
61
|
+
}
|
|
62
|
+
categories {
|
|
63
|
+
name
|
|
64
|
+
slug
|
|
65
|
+
uri
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then an archive view can select a list of tiles without repeating the fields:
|
|
72
|
+
|
|
73
|
+
```graphql filename="views/archive-case-study.graphql"
|
|
74
|
+
query ArchiveCaseStudies {
|
|
75
|
+
caseStudies(first: 100) {
|
|
76
|
+
nodes {
|
|
77
|
+
...CaseStudyTile
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The matching component can use the generated fragment type as its prop shape:
|
|
84
|
+
|
|
85
|
+
```tsx filename="components/tiles/CaseStudyTile.tsx"
|
|
86
|
+
import { CaseStudyTileFragment } from "@generated-types"
|
|
87
|
+
import { Link } from "eddev/routing"
|
|
88
|
+
|
|
89
|
+
type Props = {
|
|
90
|
+
caseStudy: CaseStudyTileFragment
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function CaseStudyTile(props: Props) {
|
|
94
|
+
const { caseStudy } = props
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<article>
|
|
98
|
+
<h2>
|
|
99
|
+
<Link href={caseStudy.uri!}>{caseStudy.title}</Link>
|
|
100
|
+
</h2>
|
|
101
|
+
{caseStudy.subtitle && <p>{caseStudy.subtitle}</p>}
|
|
102
|
+
</article>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Prefer one fragment per reusable display shape, not one fragment per database table. A `CaseStudyTile` fragment is more useful than a generic fragment that tries to include every case-study field.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Global Data (/docs/graphql/global-data)
|
|
2
|
+
|
|
3
|
+
**Fetch shared site shell data through views/_app.graphql.**
|
|
4
|
+
|
|
5
|
+
`views/_app.graphql` fetches data needed by the site shell and shared systems. Typical examples are menus, settings, trackers, global callouts, and template parts.
|
|
6
|
+
|
|
7
|
+
```graphql filename="views/_app.graphql"
|
|
8
|
+
query CommonData {
|
|
9
|
+
menus {
|
|
10
|
+
nodes {
|
|
11
|
+
locations
|
|
12
|
+
slug
|
|
13
|
+
name
|
|
14
|
+
menuItems {
|
|
15
|
+
nodes {
|
|
16
|
+
label
|
|
17
|
+
title
|
|
18
|
+
url
|
|
19
|
+
parentId
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
templateParts {
|
|
25
|
+
siteFooter {
|
|
26
|
+
contentBlocks
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The result is available to `views/_app.tsx` as props and anywhere in React through `useAppData()`.
|
|
33
|
+
|
|
34
|
+
```tsx filename="features/site/Footer.tsx"
|
|
35
|
+
import { ContentBlocks } from "eddev/blocks"
|
|
36
|
+
import { useAppData } from "eddev/hooks"
|
|
37
|
+
|
|
38
|
+
export function Footer() {
|
|
39
|
+
const footer = useAppData((data) => data.templateParts?.siteFooter)
|
|
40
|
+
|
|
41
|
+
return <ContentBlocks blocks={footer?.contentBlocks} />
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
App data is loaded with the initial route payload and reused during normal client-side navigation. Do not put per-page content in `_app.graphql`; use a paired [view query](/docs/views/queries) instead.
|
|
46
|
+
|
|
47
|
+
For the layout side of `_app.tsx`, see [`_app.tsx` Layout](/docs/views/app-view).
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Infinite Queries (/docs/graphql/infinite-queries)
|
|
2
|
+
|
|
3
|
+
**Build load-more interfaces with generated infinite query hooks.**
|
|
4
|
+
|
|
5
|
+
Infinite queries are runtime query hooks for "load more" interfaces. They build on normal [Query Hooks](/docs/graphql/query-hooks), but codegen adds pagination handling around a WPGraphQL connection.
|
|
6
|
+
|
|
7
|
+
<Callout type="info">
|
|
8
|
+
WPGraphQL uses cursor pagination. Instead of asking for page 2, you ask for the next items after the last `endCursor`.
|
|
9
|
+
</Callout>
|
|
10
|
+
|
|
11
|
+
## Codegen Requirements [#codegen-requirements]
|
|
12
|
+
|
|
13
|
+
Codegen treats a query as infinite when the operation name contains `Infinite`, for example `UseInfinitePosts`.
|
|
14
|
+
|
|
15
|
+
The query must:
|
|
16
|
+
|
|
17
|
+
* live under `queries/`
|
|
18
|
+
* follow the normal `Use*` filename and operation-name rules
|
|
19
|
+
* define `$limit: Int` with a default value
|
|
20
|
+
* define `$cursor: String` with no default value
|
|
21
|
+
* use `first: $limit` and `after: $cursor` on the connection
|
|
22
|
+
* select one `pageInfo` object
|
|
23
|
+
* select `pageInfo { endCursor hasNextPage }`
|
|
24
|
+
* select `nodes` next to that `pageInfo`
|
|
25
|
+
|
|
26
|
+
```graphql filename="queries/UseInfinitePosts.graphql"
|
|
27
|
+
query UseInfinitePosts($limit: Int = 6, $cursor: String) {
|
|
28
|
+
posts(first: $limit, after: $cursor) {
|
|
29
|
+
pageInfo {
|
|
30
|
+
endCursor
|
|
31
|
+
hasNextPage
|
|
32
|
+
}
|
|
33
|
+
nodes {
|
|
34
|
+
uri
|
|
35
|
+
title
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Hook API [#hook-api]
|
|
42
|
+
|
|
43
|
+
The generated hook returns a TanStack infinite-query result, with the page data flattened for normal rendering.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
type InfiniteResult<TNode> = {
|
|
47
|
+
data?: {
|
|
48
|
+
nodes: TNode[]
|
|
49
|
+
total?: number | null
|
|
50
|
+
}
|
|
51
|
+
hasNextPage: boolean
|
|
52
|
+
isFetchingNextPage: boolean
|
|
53
|
+
fetchNextPage: () => Promise<unknown>
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use `fetchNextPage()` to load the next page.
|
|
58
|
+
|
|
59
|
+
## Example Usage [#example-usage]
|
|
60
|
+
|
|
61
|
+
```tsx filename="components/LatestPosts.tsx"
|
|
62
|
+
import { useInfinitePosts } from "@hooks/queries"
|
|
63
|
+
|
|
64
|
+
export function LatestPosts() {
|
|
65
|
+
const lookup = useInfinitePosts(undefined, {
|
|
66
|
+
refetchOnWindowFocus: false,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
{lookup.isLoading && <p>Loading...</p>}
|
|
72
|
+
|
|
73
|
+
{lookup.data?.nodes?.length ? (
|
|
74
|
+
<ul>
|
|
75
|
+
{lookup.data.nodes.map((post) => (
|
|
76
|
+
<li key={post.uri}>{post.title}</li>
|
|
77
|
+
))}
|
|
78
|
+
</ul>
|
|
79
|
+
) : null}
|
|
80
|
+
|
|
81
|
+
{lookup.hasNextPage && (
|
|
82
|
+
<button disabled={lookup.isFetchingNextPage} onClick={() => lookup.fetchNextPage()}>
|
|
83
|
+
{lookup.isFetchingNextPage ? "Loading..." : "Load more"}
|
|
84
|
+
</button>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Initial Data [#initial-data]
|
|
92
|
+
|
|
93
|
+
Infinite hooks accept `initialData`, but it must match the original GraphQL query structure, including the selected `nodes`, `pageInfo.hasNextPage`, and `pageInfo.endCursor` fields.
|
|
94
|
+
|
|
95
|
+
Only use `initialData` when you already have a matching connection result from a view, block, or app query. Otherwise, let the hook load normally in the browser.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Mutation Hooks (/docs/graphql/mutation-hooks)
|
|
2
|
+
|
|
3
|
+
**Generate mutation hooks for runtime GraphQL operations with side effects.**
|
|
4
|
+
|
|
5
|
+
Mutations are GraphQL operations with side effects, such as submitting a form, creating a shared planner, saving user data, or sending an enquiry.
|
|
6
|
+
|
|
7
|
+
Mutation files live under `queries/` and follow the same filename rules as query hooks: the file and operation should start with `Use`, and the operation name should match the filename.
|
|
8
|
+
|
|
9
|
+
```graphql filename="queries/shared-planner/UseCreateSharedPlanner.graphql"
|
|
10
|
+
mutation UseCreateSharedPlanner($ids: [Int!], $name: String, $userId: String) {
|
|
11
|
+
createSharedPlanner(input: { ferveIds: $ids, name: $name, ferveUserId: $userId }) {
|
|
12
|
+
planner {
|
|
13
|
+
id
|
|
14
|
+
name
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This generates `useCreateSharedPlanner` in `hooks/queries.ts`.
|
|
21
|
+
|
|
22
|
+
Unlike query hooks, mutation hooks do not run when rendered. You call `mutate()` or `mutateAsync()` from an event handler.
|
|
23
|
+
|
|
24
|
+
## Hook Usage [#hook-usage]
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { useState } from "react"
|
|
28
|
+
import { useCreateSharedPlanner } from "@hooks/queries"
|
|
29
|
+
|
|
30
|
+
export function CreateSharedPlanner() {
|
|
31
|
+
const [name, setName] = useState("")
|
|
32
|
+
const create = useCreateSharedPlanner()
|
|
33
|
+
|
|
34
|
+
if (create.isSuccess) {
|
|
35
|
+
return <p>Planner created: {create.data.createSharedPlanner?.planner?.id}</p>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<form
|
|
40
|
+
onSubmit={(e) => {
|
|
41
|
+
e.preventDefault()
|
|
42
|
+
if (create.isPending) return
|
|
43
|
+
|
|
44
|
+
create.mutate({
|
|
45
|
+
name,
|
|
46
|
+
ids: [123, 456],
|
|
47
|
+
})
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{create.isError && <p>{create.error.message}</p>}
|
|
51
|
+
<input disabled={create.isPending} value={name} onChange={(e) => setName(e.currentTarget.value)} />
|
|
52
|
+
<button disabled={create.isPending} type="submit">Create Planner</button>
|
|
53
|
+
</form>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
<Callout>
|
|
59
|
+
Many built-in WPGraphQL mutations require WordPress admin privileges. Public-facing mutations usually need to be registered by the project. See [Extending GraphQL](./extending).
|
|
60
|
+
</Callout>
|
|
61
|
+
|
|
62
|
+
## Hook API [#hook-api]
|
|
63
|
+
|
|
64
|
+
The generated hook returns a TanStack mutation result:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const mutation = useCreateSharedPlanner({
|
|
68
|
+
onSuccess(data) {
|
|
69
|
+
// Optional success handler
|
|
70
|
+
},
|
|
71
|
+
onError(error) {
|
|
72
|
+
// Optional error handler
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Useful returned properties include:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
type MutationResult<TData, TVars> = {
|
|
81
|
+
status: "idle" | "pending" | "error" | "success"
|
|
82
|
+
isPending: boolean
|
|
83
|
+
isSuccess: boolean
|
|
84
|
+
isError: boolean
|
|
85
|
+
error: QueryError | null
|
|
86
|
+
data: TData | undefined
|
|
87
|
+
mutate: (variables: TVars, options?: MutationOptions<TData>) => void
|
|
88
|
+
mutateAsync: (variables: TVars, options?: MutationOptions<TData>) => Promise<TData>
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You can pass mutation options to the hook itself, or to `mutate()` / `mutateAsync()`.
|
|
93
|
+
|
|
94
|
+
## Manual Mutation [#manual-mutation]
|
|
95
|
+
|
|
96
|
+
Each generated mutation hook also has a static `.mutate()` method for non-hook usage.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { useCreateSharedPlanner } from "@hooks/queries"
|
|
100
|
+
|
|
101
|
+
async function createPlanner() {
|
|
102
|
+
const result = await useCreateSharedPlanner.mutate({
|
|
103
|
+
name: "Weekend picks",
|
|
104
|
+
ids: [123, 456],
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return result.createSharedPlanner?.planner
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Prefer the hook in React components so loading and error states stay tied to the UI.
|