Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
12-23-2019 11:05 AM
Hi,
I am working on implementing useQuery
, useEffect
, and useMutation
to write to the graph and then update the UI to rerender a list of elements. I am having trouble finding a solid example to follow. Has anyone been working with these?
So I currently have one component <Rank />
that uses useQuery
to pull data down into a table. In the component I have <CreateRank />
that uses useMutation
to write to the graph. That aspect of it works perfectly fine as I can write to the graph. However, I am stuck at the moment on how/where to implement useEffect
to update and rerender once I have submited and written to the graph.
Solved! Go to Solution.
12-28-2019 05:32 PM
This is a bit of a long response, but I am hopeful others might make sense of this for them to implement a similar solution.
So after following the code shown in here, this is what I did that is working:
This is in my Strike.js
file
const GET_STRIKES = gql`
{
Strike {
name
}
}
`;
export default function Strike() {
const classes = useStyles();
const [order, setOrder] = useState("asc");
const [orderBy, setOrderBy] = useState("name");
const handleSortRequest = property => {
const newOrderBy = property;
let newOrder = "desc";
if (orderBy === property && order === "desc") {
newOrder = "asc";
}
setOrder(newOrder);
setOrderBy(newOrderBy);
};
const getSorting = (order, orderBy) => {
return order === "desc"
? (a, b) => (b[orderBy] < a[orderBy] ? -1 : 1)
: (a, b) => (a[orderBy] < b[orderBy] ? -1 : 1);
};
const { loading, error, data } = useQuery(GET_STRIKES);
if (loading) return "Loading...";
if (error) return `Error ${error.message}`;
return (
<div className={classes.root}>
<Grid container spacing={3}>
<Grid item /*xs={12}*/ sm={6}>
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell
key="name"
sortDirection={orderBy === "name" ? order : false}
>
<Tooltip title="Sort" placement="bottom-start" enterDelay={300}>
<TableSortLabel
active={orderBy === "name"}
direction={order}
onClick={() => handleSortRequest("name")}
>
Name
</TableSortLabel>
</Tooltip>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.Strike //.slice()
.sort(getSorting(order, orderBy))
.map(n => {
return (
<TableRow key={n.name}>
<TableCell component="th" scope="row">
{n.name}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Grid>
<Grid item /*xs={12}*/ sm={6}>
<RankSelectFilter />
<CreateStrike data={data} GET_STRIKES={GET_STRIKES} />
</Grid>
</Grid>
</div>
);
}
This is in my CreateStrike.js
file:
const CREATE_STRIKE = gql`
mutation CreateStrike($name: String!) {
CreateStrike(name: $name) {
name
}
}
`;
export default function CreateStrike({ data, GET_STRIKES }) {
const classes = useStyles();
const [name, setName] = useState("");
const [CreateStrike] = useMutation(
CREATE_STRIKE,
{
update(cache, { data: { CreateStrike } }) {
const { Strike } = cache.readQuery({ query: GET_STRIKES });
cache.writeQuery({
query: GET_STRIKES,
data: { Strike: Strike.concat([CreateStrike]) },
})
}
}
);
const handleClick = event => {
CreateStrike({
variables: { name: name }
});
}
return (
<div className={classes.root} /*justifyContent="flex-start"*/>
<form className={classes.container} noValidate autoComplete="off">
<CreateStrikeTextField
name={name}
setName={setName}
/>
</form>
<Button
onClick={handleClick}
className={classes.button}
color="primary"
variant="contained"
>
Submit
</Button>
</div>
);
}
The key aspect in Strike.js
was passing data
and GET_STRIKES
to the CreateStrike
component.
<CreateStrike data={data} GET_STRIKES={GET_STRIKES} />
Then in CreateStrike.js
I use the following to run the mutation
and update data
which then causes Strike
to rerender.
const [CreateStrike] = useMutation(
CREATE_STRIKE,
{
update(cache, { data: { CreateStrike } }) {
const { Strike } = cache.readQuery({ query: GET_STRIKES });
cache.writeQuery({
query: GET_STRIKES,
data: { Strike: Strike.concat([CreateStrike]) },
})
}
}
)
I am not entirely certain it is best practice to pass data
like this, but in this instance it works well and I am not needing to use useEffect
.
In summary, the code shown above rerenders a table upon submitting and adding an entry using useQuery
and useMutation
and that does not require useEffect
.
12-27-2019 05:29 AM
If you're using the Grandstack and the useQuery and useMutation hooks there's not really any instances when you need to employ useEffect. I've got a production application created and it doesn't contain useEffect anywhere in the GraphQL/Apollo logic. That being said my use cases might be way different from what you're doing, but maybe give us an example of how/where you're wanting to place the useEffect hook and the community might be able to steer you in the correct direction. Also, here are the official Apollo GraphQL React Hooks docs that might help as well https://www.apollographql.com/docs/react/api/react-hooks/
12-27-2019 09:56 AM
Please note, the current layout/component nesting is temporary as I flush out the layout and functionality. If you have recommendations on how to adjust nesting of components to make this work more smoothly I am open doing that.
The layout/nesting of the components is as follows:
App.js
> TabPanel.js
> Strike.js
> Table component (as seen on left of image above)
> CreateStrike.js (as seen on right of image above)
I utilize the useQuery
method to call the database and populate the Table
component. In the nested CreateStrike.js
I use the useMutation
method to write to the database. It was my understanding that I would need to use useEffect
to update the table on the left. I tried following several examples, and have read the React Hooks useEffect and looked over the link you sent. So I figure I am missing something simple.
12-27-2019 12:07 PM
You should be able to manipulate the state of whatever you’re messing with via what’s returned from your mutation. So if you’re table values are updated by a mutation you can assign the returned values to your table. I’m in a bad spot to give code examples but let’s say your table values are in a useState variable then you can update them after the mutation completes and returns the updated values etc. I’ll try to do a better example when I can sit down but I hope that helps.
12-27-2019 12:42 PM
Yeah I am thinking something along a similar line. I have 2 flights ahead of me today so I think I will be working on this. I'll let you know if I figure it out.
Thanks
12-28-2019 04:41 AM
Here's an example of some code in my application:
const DivisionOrder = ({ history, match, enqueueSnackbar }) => {
const classes = WellFormStyles();
const AddDOI = useMutation(ADD_DIVISION_ORDER);
const RemoveDOI = useMutation(REMOVE_DIVISION_ORDER);
const updateNotes = useMutation(EDIT_WELL);
const { data, loading, error } = useQuery(DOIQuery, {
variables: { id: match.params.wellID },
});
if (loading) {
return <LoadingView />;
}
if (error) {
return error.toString();
}
// Break well out of data returned from query
const Well = data.Well[0] || [];
So that's the initial query used to get that division order, but could also easily be all division orders, etc. Then you can call your mutations:
onRowUpdate: (newData, oldData) => {
return AddDOI({
variables: {
from: { id: Well.id },
to: { id: newData.Tract.id },
data: pushData,
},
})
.then(({ data: { AddWellDivisionOrder } }) => {
remove(oldData.tableData.id);
insert(oldData.tableData.id, {
...pushData,
Tract: { id: newData.Tract.id },
mesDOI,
mpiDOI,
sroDOI,
});
})
.catch((error) => console.log(error));
I'm using a library(Formik) to insert and remove those new objects into a table. So I'm updating my state there. You could do something like create a variable:
const [tableValues, setTableValues] = useState({Default Object});
And then when you call your mutation and you get something return you could add that to your state:
return YourMutation({
variables: {},
}).then( data => { setTableValues[...tableValues, data.theDataYouNeed] })
and then you'll have updated your state without the need for useEffect, or you can find an example that will show you how to useState with useEffect if you want.
12-28-2019 07:20 AM
Thanks! I will see if I can get something like this to work for me. I will let you know if I have any additional questions.
The issue with finding an example with useEffect
is that they seem overly simple and not really working with useMutation
and useQuery
. I am hopeful I can take what you shared and work it into my app. I will let you know.
12-28-2019 12:01 PM
Just wanted to let you know I got things working. I ended up reading over this section a bit and playing around. I will put together a post showing my code so other can possibly reference it. Stay tuned...
12-28-2019 02:12 PM
Good deal. Yeah there's a lot you can do with the cache too. Optimistic UI, etc. Looking forward to seeing what you got done.
12-28-2019 05:32 PM
This is a bit of a long response, but I am hopeful others might make sense of this for them to implement a similar solution.
So after following the code shown in here, this is what I did that is working:
This is in my Strike.js
file
const GET_STRIKES = gql`
{
Strike {
name
}
}
`;
export default function Strike() {
const classes = useStyles();
const [order, setOrder] = useState("asc");
const [orderBy, setOrderBy] = useState("name");
const handleSortRequest = property => {
const newOrderBy = property;
let newOrder = "desc";
if (orderBy === property && order === "desc") {
newOrder = "asc";
}
setOrder(newOrder);
setOrderBy(newOrderBy);
};
const getSorting = (order, orderBy) => {
return order === "desc"
? (a, b) => (b[orderBy] < a[orderBy] ? -1 : 1)
: (a, b) => (a[orderBy] < b[orderBy] ? -1 : 1);
};
const { loading, error, data } = useQuery(GET_STRIKES);
if (loading) return "Loading...";
if (error) return `Error ${error.message}`;
return (
<div className={classes.root}>
<Grid container spacing={3}>
<Grid item /*xs={12}*/ sm={6}>
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell
key="name"
sortDirection={orderBy === "name" ? order : false}
>
<Tooltip title="Sort" placement="bottom-start" enterDelay={300}>
<TableSortLabel
active={orderBy === "name"}
direction={order}
onClick={() => handleSortRequest("name")}
>
Name
</TableSortLabel>
</Tooltip>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.Strike //.slice()
.sort(getSorting(order, orderBy))
.map(n => {
return (
<TableRow key={n.name}>
<TableCell component="th" scope="row">
{n.name}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Grid>
<Grid item /*xs={12}*/ sm={6}>
<RankSelectFilter />
<CreateStrike data={data} GET_STRIKES={GET_STRIKES} />
</Grid>
</Grid>
</div>
);
}
This is in my CreateStrike.js
file:
const CREATE_STRIKE = gql`
mutation CreateStrike($name: String!) {
CreateStrike(name: $name) {
name
}
}
`;
export default function CreateStrike({ data, GET_STRIKES }) {
const classes = useStyles();
const [name, setName] = useState("");
const [CreateStrike] = useMutation(
CREATE_STRIKE,
{
update(cache, { data: { CreateStrike } }) {
const { Strike } = cache.readQuery({ query: GET_STRIKES });
cache.writeQuery({
query: GET_STRIKES,
data: { Strike: Strike.concat([CreateStrike]) },
})
}
}
);
const handleClick = event => {
CreateStrike({
variables: { name: name }
});
}
return (
<div className={classes.root} /*justifyContent="flex-start"*/>
<form className={classes.container} noValidate autoComplete="off">
<CreateStrikeTextField
name={name}
setName={setName}
/>
</form>
<Button
onClick={handleClick}
className={classes.button}
color="primary"
variant="contained"
>
Submit
</Button>
</div>
);
}
The key aspect in Strike.js
was passing data
and GET_STRIKES
to the CreateStrike
component.
<CreateStrike data={data} GET_STRIKES={GET_STRIKES} />
Then in CreateStrike.js
I use the following to run the mutation
and update data
which then causes Strike
to rerender.
const [CreateStrike] = useMutation(
CREATE_STRIKE,
{
update(cache, { data: { CreateStrike } }) {
const { Strike } = cache.readQuery({ query: GET_STRIKES });
cache.writeQuery({
query: GET_STRIKES,
data: { Strike: Strike.concat([CreateStrike]) },
})
}
}
)
I am not entirely certain it is best practice to pass data
like this, but in this instance it works well and I am not needing to use useEffect
.
In summary, the code shown above rerenders a table upon submitting and adding an entry using useQuery
and useMutation
and that does not require useEffect
.
All the sessions of the conference are now available online