Connecting your React app to Neo4j with React Hooks
For my role as a Developer Experience Engineer at Neo4j, I’ve spent some time recently looking at how developers interact with Neo4j. It’s always been a surprise that there weren’t more community tools that make it easier to work with, especially for a front-end framework as popular as React.
Granted, in a lot of cases, a Front-End Developer will be interacting with an API, and we’ve got that covered with tools along with the official drivers and frameworks like Spring Data Neo4j or GRANDstack.
But if your only aim is to quickly prototype an application or to build a Graph App then there’s a lot of boiler plate. You’d need to obtain some login credentials, create a driver, open a session, and run a query/transaction all before you can get to the fun part.
So with that in mind, last week I started working on a new project and posted this teaser on Twitter:
The teaser contained screenshots to some experiments that served as the precursor to the use-neo4j
library.
React Hooks for Neo4j
A while ago, I wrote the vue-neo4j plugin which was designed to cut down the boilerplate in Vue.js prototypes. Although the underlying concepts are the same, the conventions in React are very different. In React you would use Hooks or Effects to fetch data from an external source.
If you’re interested in what is going on under the hood, all of the code is available on Github.
Context is Important
React uses Context to provide a way of passing data through a component tree without having to pass a prop through the components at each level (also known as prop drilling).
The use-neo4j
plugin works around its own Context — this is what makes the Neo4j Driver instance available throughout the application.
There are two ways of creating this context — taking inspiration from the Apollo Client implementation, the use-neo4j
library exports a Neo4jProvider
, a functional component, which initiates the context.
You can initialize this context in one of two ways. If you know the credentials of the server up-front, you can create an instance of the driver using the createDriver
function and pass it as a prop into the Neo4jProvider
.
import { Neo4jProvider, createDriver } from 'use-neo4j'// Create driver instance
const driver = createDriver('neo4j', 'localhost', 7687, 'neo4j', 'letmein')
ReactDOM.render(
<React.StrictMode>
{/* Pass it as a prop to the Neo4jProvider */}
<Neo4jProvider driver={driver}>
<App />
</Neo4jProvider>
</React.StrictMode>,
document.getElementById('root')
);
If you don’t know the details up-front, or neglect to pass a driver prop, you will instead be presented with a login form. On clicking Connect to Neo4j, the library will attempt to create a driver and connect to the Neo4j instance.
The form fields can be pre-populated by passing additional props to the Neo4jProvider
:
ReactDOM.render(
<React.StrictMode>
<Neo4jProvider
scheme="neo4j+s"
host="myauradb.neo4j.io"
port="7687"
username="username"
password="defaultpassword"
database="someotherdb"
>
<App />
</Neo4jProvider>
</React.StrictMode>,
document.getElementById('root')
);
Hooks for Querying Data
Once React has an instance of the Neo4j Driver to query against, you can use the useReadCypher
and useWriteCypher
hooks to run a query against the database. Both procedures take three arguments; the cypher query, an object to represent any parameters, and optionally the database to run the query against if running multiple databases. They both return an object which corresponds to the Neo4jResultState
interface.
useReadCypher(
cypher: string,
params?: Record<string, any>,
database?: string
): Neo4jResultState
The interface provides reactive properties to represent the query’s state:
const {
loading,
error,
records,
first
} = useReadCypher(query, params)
A component can react to changes to these components and re-render based on the results. For example, a loading message could be returned if the query hasn’t finished:
if ( loading ) return (<div>Loading...</div>)
Or if there was a problem executing the query…
if ( error ) return (<div className="error">{error.message}</div>)
The result
variable returns a QueryResult as it would when using the Promise API, an array of Record
s with a .get()
function for retrieving a value returned by the query.
const movies = result.records.map(row => row.get('movie'))
Or, if you are only expecting one row, the first
variable will allow you to access that row directly. Piecing it all together, your component may look like this:
function MyComponent({ title }) {
const query = `MATCH (movie:Movie {title: $title}) RETURN movie`
const params = { title } // Movie title passed as a prop
const { loading, first } = useReadCypher(query, params)
if ( loading ) return (<div>Loading...</div>) if ( error ) return (
<div className="error">{error.message}</div>
) // Get `m` from the first row
const movie = first.get('movie')
return (
<div>
{movie.properties.title}
was released in
{movie.properties.year}
</div>
)
}
More Information
For more information, check out the README on the Github Repository. I will be adding more functionality soon, and also incorporating this into a Graph App starter kit for React developers — making it easy for you to build prototypes, or a new Graph App for the rest of the community to enjoy.
These hooks may unintentionally expose Neo4j credentials and are therefore not intended for use in production (unless you’re building a Graph App) — for that, you’re better off building a REST API to sit between your application and the Neo4j database, or using GRANDstack.
If you have any questions, comments or suggestions feel free to get in touch.
I run a livestream on all things Neo4j and TypeScript every Tuesday — 13:00–14:00 UK time on the Neo4j Twitch Channel. You can also catch up with videos on the Neo4j Youtube Channel.