Let's imagine we have a Page component in our NextJS app using the dynamic route of pages/[slug].js
. This page will get its content from a corresponding JSON file located at posts/[slug].json
. Thus, when you visit /hello-world
, it will display the contents of /posts/hello-world.json
. We can set up a very simple version of this with the following code:
// /pages/[slug].js
import * as React from 'react'
export default function Page({ post }) {
return (
<>
<h1>{post.data.title}</h1>
</>
)
}
Page.getInitialProps = function(ctx) {
const { slug } = ctx.query
let content = require(`../posts/${slug}.json`)
return {
post: {
fileRelativePath: `/posts/${slug}.json`,
data: content.default,
},
}
}
The getInitialProps
function is run by Next when the page is requested to load the data, and the return value is passed to our component as its initial props. Take note of fileRelativePath
; we'll need that when we set up the form.
The next-tinacms-json
package provides a hook to help us make JSON content editable. useLocalJsonForm
receives an object matching the following interface:
// A datastructure representing a JSON file stored in Git
interface JsonFile<T = any> {
fileRelativePath: string
data: T
}
and returns the contents of data
after it's been exposed to the editor.
To use this hook, install next-tinacms-json
:
npm install next-tinacms-json
Since the object we're returning from getInitialProps
already matches the JsonFile
interface, all that's required is to pass this object into useLocalJsonForm
, and replace the post
object in our render with the hook's return value:
// /pages/[slug].js
import * as React from 'react'
import { useLocalJsonForm } from 'next-tinacms-json'
export default function Page({ post }) {
+ const [postData] = useLocalJsonForm(post)
return (
<>
+ <h1>{postData.title}</h1>
</>
)
}
Page.getInitialProps = function(ctx) {
const { slug } = ctx.query
let content = require(`../posts/${slug}.json`)
return {
post: {
fileRelativePath: `/posts/${slug}.json`,
data: content.default,
},
}
}
By default, useLocalJsonForm
creates a text field for each value in data
. It's possible to customize the form by passing a second argument into useLocalJsonForm
:
export default function Page({ post }) {
const [postData] = useLocalJsonForm(post, {
fields: [
{
name: 'title',
label: 'Post Title',
component: 'text',
},
],
})
return (
<>
<h1>{postData.title}</h1>
</>
)
}
There is another hook, useGlobalJsonForm
, that registers a Global Form with the sidebar.
Using this hook looks almost exactly the same as the example for useLocalJsonForm
. This hook expects an object with the properties, fileRelativePath
and data
. The value of data
should be the contents of the JSON file. The Global Form can be customized by passing in an options object as the second argument.
Using a hook is an incredibly flexible and intuitive way to register forms with Tina. Unfortunately hooks only work with function components in React. If you need to register a form with Tina on a class component, or are fond of the higher-order component pattern, jsonForm
is the function to reach for.
jsonForm
accepts two arguments: a component and an optional form configuration object. The component being passed is expected to receive data as props that matches the jsonFile
interface outlined below.
// A datastructure representing a JSON file stored in Git
interface JsonFile<T = any> {
fileRelativePath: string
data: T
}
jsonForm
returns the original component with a local form registered with Tina. Below is the same example from useLocalJsonForm
, but refactored to use the jsonForm
HOC.
Example
/*
** 1. import jsonForm
*/
import { jsonForm } from 'next-tinacms-json'
import * as React from 'react'
function Page({ jsonFile }) {
return (
<>
<h1>{jsonFile.data.title}</h1>
</>
)
}
/*
** 2. Wrap the Page component with jsonForm
*/
const EditablePage = jsonForm(Page)
/*
** 3. Export the editable component
*/
export default EditablePage
/*
** 4. Call your data fetching method
** on the editable component
*/
EditablePage.getInitialProps = function(ctx) {
const { slug } = ctx.query
let content = require(`../posts/${slug}.json`)
return {
/*
** 5. Ensure your return data has
** this shape.
*/
jsonFile: {
fileRelativePath: `/posts/${slug}.json`,
data: content.default,
},
}
}