Reputation: 6799
I am loading data in from a Neo4J database using use-neo4j and have created a custom hook. It makes a query to the database and then converts the data to a format I can use for plotting the data.
It is a lot of code but basically neo4jToJSON
converts the data from Neo4J, then I set it to the state, lastly I return the state to be used in other components.
Note:
Before this I was just returning neo4jToJSON
and had no issues. But the moment I incorporate state into the function and return the state it throws this error.
Edit:
I have tried the suggested answer in the updated code below but now the data just returns undefined.
import React, { useState, useEffect } from "react";
import { useReadCypher } from "use-neo4j";
const useFetchData = () => {
const [jsonData, setJsonData] = useState();
// Query to Neo4j database to get all the relevant data for all the users
const getAllUserData = // a query string that is quite long so I didn't include it
// Pass query to use-neo4j to get the data
const { result } = useReadCypher(getAllUserData);
const filters = [
"name",
"longitude",
"latitude",
"agreeableness",
"openness",
"neuroticism",
"extraversion",
"conscientiousness",
"angry",
"fear",
"joy",
"sadness",
"love",
"country",
"question1",
"question2",
"question3",
"question4",
"question5",
"question6",
"question7",
"question8",
"question9",
"question10",
"question11",
"question12",
"question13",
"question14",
"question15",
"question16",
"question17",
"question18",
"question19",
"question20",
"question21",
"question22",
"question23",
"question24",
"question25",
"question26",
"question27",
"question28",
"question29",
"question30",
"question31",
"question32",
"question33",
"question34",
"question35",
"question36",
"question37",
"question38",
"question39",
"question40",
"question41",
];
useEffect(() => {
if (result) {
const { records } = result;
const neo4jToJSON =
records &&
records.map(({ _fields }) => {
// User name
const screenName = _fields[0];
// Country of the user
const country = _fields[2][0];
// Create the object with these values as the keys:
//
// "name",
// "longitude",
// "latitude",
// "agreeableness",
// "openness",
// "neuroticism",
// "extraversion",
// "conscientiousness",
// "angry",
// "fear",
// "joy",
// "sadness",
// "love",
// "country"
const userPersonality = filters.reduce((acc, curr, idx) => {
if (idx === 0) {
// set the property name to the screenName
acc[curr] = screenName;
} else if (idx >= 1 && idx <= 12) {
// if the index is in the range of 1 to 12
// it belongs to _fields[1]
// set the properties of _fields[1] to their respective values
// values come in the form of strings so apply parseFloat on them
acc[curr] = parseFloat(_fields[1][idx - 1]);
} else if (idx === 13) {
// set the property country
acc[curr] = country;
}
return acc;
}, {});
const finalResults = filters.reduce((acc, curr, idx) => {
const questions = _fields[3];
if (idx > 13) {
// Access the first element of the array of arrays which contains
// the questions by idx - 14
// The current value starts from idx 14 of filters which is
// question1
// So if idx is equal to 14:
// idx - 14 will give the question for the first question which is
// at index 0
//
// Ternary operator here checks if the array value is not empty if
// it is not empty then set the value of the current question to be
// the array of questions
// Else set the value to be an empty array which is falsy
questions[idx - 14]
? (acc[curr] = questions[idx - 14])
: (acc[curr] = []);
}
return acc;
}, userPersonality);
return finalResults;
});
setJsonData(neo4jToJSON);
}
}, []);
return { jsonData };
};
export default useFetchData
Upvotes: 0
Views: 212
Reputation: 17430
While useEffect
is one of the way to avoid calling state setter functions inside of the render cycle, in your case, useState
isn't needed at all!
It's an anti-pattern to use state for something that can be synchronously computed within render.
const FILTERS = [
"name",
"longitude",
"latitude",
// ...etc.
];
const computeData = ({ records } = {}) => {
return (
records &&
records.map(({ _fields }) => {
const screenName = _fields[0];
const country = _fields[2][0];
const userPersonality = filters.reduce((acc, curr, idx) => {
if (idx === 0) {
acc[curr] = screenName;
} else if (idx >= 1 && idx <= 12) {
acc[curr] = parseFloat(_fields[1][idx - 1]);
} else if (idx === 13) {
acc[curr] = country;
}
return acc;
}, {});
const questions = _fields[3];
const finalResults = filters.reduce((acc, curr, idx) => {
if (idx > 13) {
questions[idx - 14]
? (acc[curr] = questions[idx - 14])
: (acc[curr] = []);
}
return acc;
}, userPersonality);
return finalResults;
})
);
};
export default function useFetchData() {
// Query to Neo4j database to get all the relevant data for all the users
const getAllUserData = "?whatever"; // a query string
// Pass query to use-neo4j to get the data
const { result } = useReadCypher(getAllUserData);
// no need for state here, just compute on every render.
return { jsonData: computeData(result) };
}
useMemo
If you figure that some computation only happens once when the data is received and mostly never changes, and/or it's an heavy computation that could lead to a bottleneck, consider using useMemo
to memoize the computation result and only re-compute if the input changes.
import { useMemo } from "react";
//...computeData, filters, etc. here...
export default function useFetchData() {
// Query to Neo4j database to get all the relevant data for all the users
const getAllUserData = "?whatever"; // a query string
// Pass query to use-neo4j to get the data
const { result } = useReadCypher(getAllUserData);
// no need for state here, just memoize the result.
const jsonData = useMemo(() => computeData(result), [result]);
return { jsonData };
}
Also notice that in each example, I've moved some stuff around, so that the hook logic is easier to grasp and to isolate the computation logic.
Upvotes: 1
Reputation: 6146
You are calling a state setter synchronously from your render function. Never do this. That's what triggers the re-render. And then triggers it again, and again, and again.
const useFetchData = () => {
const [jsonData, setJsonData] = useState();
const { result } = useReadCypher(getAllUserData);
if (result) {
setJsonData(something);
}
If you need to set some state when result
changes you can useEffect
const useFetchData = () => {
const [jsonData, setJsonData] = useState();
const { result } = useReadCypher(getAllUserData);
useEffect(() => { if (result) setJsonData(something) }, [result]);
Upvotes: 1
Reputation: 639
Move your logic into useEffect
function
useEffect(() => {
effect
return () => {
cleanup
}
}, [input])
Like this
useEffect(() => {
// Query to Neo4j database to get all the relevant data for all the users
const getAllUserData = // a query string that is quite long so I didn't include it
// Pass query to use-neo4j to get the data
const { result } = useReadCypher(getAllUserData);
if (result) {
// Convert the data from Neo4j which comes in the form of an Object to
// another Object that has keys which are elements in the array filters and
// values which are the _fields from the Object from Neo4j
const { records } = result;
const neo4jToJSON =
records &&
records.map(({ _fields }) => {
// User name
const screenName = _fields[0];
// Country of the user
const country = _fields[2][0];
const userPersonality = filters.reduce((acc, curr, idx) => {
if (idx === 0) {
// set the property name to the screenName
acc[curr] = screenName;
} else if (idx >= 1 && idx <= 12) {
// if the index is in the range of 1 to 12
// it belongs to _fields[1]
// set the properties of _fields[1] to their respective values
// values come in the form of strings so apply parseFloat on them
acc[curr] = parseFloat(_fields[1][idx - 1]);
} else if (idx === 13) {
// set the property country
acc[curr] = country;
}
return acc;
}, {});
const finalResults = filters.reduce((acc, curr, idx) => {
const questions = _fields[3];
if (idx > 13) {
// Access the first element of the array of arrays which contains
// the questions by idx - 14
// The current value starts from idx 14 of filters which is
// question1
// So if idx is equal to 14:
// idx - 14 will give the question for the first question which is
// at index 0
//
// Ternary operator here checks if the array value is not empty if
// it is not empty then set the value of the current question to be
// the array of questions
// Else set the value to be an empty array which is falsy
questions[idx - 14]
? (acc[curr] = questions[idx - 14])
: (acc[curr] = []);
}
return acc;
}, userPersonality);
return finalResults;
});
setJsonData(neo4jToJSON);
}
}
Upvotes: 0