cant set a state inside function (react) - javascript

Learning by coding, here i have simple graph, where are different datas such as current, all and filtered data, first code works fine, but i wanted to setState so i changed code a little (second code), if i uncomment 'setTest' it give 'Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.' is there a fix to this or should i somehow use useEffect?
and another question is why should i call function 'kk()', and not just reference to it 'kk' because when i use reference it give error 'Uncaught TypeError: Cannot read properties of undefined' but when calling function it wont give error, but then i have used reference to function without problem 'onClickNode={onClickNode}' .
1:
import { Graph } from "react-d3-graph";
<Graph data={
hideData ?
curentData ? allData : filteredData
: allData }
onClickNode={onClickNode}
/>
2:
import { Graph } from "react-d3-graph";
const [test, setTest] = useState<any>("testData1");
const kk = () => {
// setTest("testData2")
return curentData ? allData : filteredData;
};
<Graph
data={hideData ? kk() : allData} onClickNode={onClickNode}
/>
English is not my mother language so could be mistakes.

You should not call a state changing function within your rendering logic without some sort of condition. Without a condition to prevent a state change results in an infinite loop as you are seeing.
One option you can try is call setTest("testData2") on a particular user action, like this:
const [test, setTest] = useState<any>("testData1");
const kk = () => {
return curentData ? allData : filteredData;
};
const onClickNode = () => {
// Handle other click logic...
setTest("testData2");
};
<Graph data={hideData ? kk() : allData} onClickNode={onClickNode} />
But without seeing more of that code, that option may not make sense for you.
Another option would be to check the value of test before calling setTest("testData2"), like this:
const [test, setTest] = useState<any>("testData1");
const kk = () => {
if (test !== "testData2") {
setTest("testData2");
}
return curentData ? allData : filteredData;
};
<Graph data={hideData ? kk() : allData} onClickNode={onClickNode} />

Related

render a part JSX by another function component - reactJS

I have a function component like that
function myMainComponent() {
const MyUIA() {
//some calulations about attrs
return (<div><ComponentA {...attrs}></div>)
}
const MyUIB() {
//some calulations about attrs
return (<div><ComponentA {...attrs}></div>)
}
// Way A
/*
return (
<div>
<MyUIA/>
<MyUIB/>
</div>);
*/
// Way B
return (
<div>
{MyUIA()}
{MyUIB()}
</div>);
}
The results of render by WayA and WayB is the same, but in first case there is a flickering
So why the WayB has better performance without flickering ?
What is the best way to render part of renders thas exist in the same function component ?
does WayA and WayB have a specific name for example "render by calling native method" and "render by calling JSX method" in react ?
Since as we know every code we write in react is converted into JSX first, then it get rendered so if we see for native method the JSX element is created twice while for JSX it's created only once. So this is the case one can see the flickering. I hope it helps.
function myMainComponent() {
const MyUIA = () => {
return /*#__PURE__*/React.createElement("div", null,/*#__PURE__*/React.createElement(ComponentA, null)); };
const MyUIB = () => {
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(ComponentA, null));};
// Way A
return /*#__PURE__*/React.createElement("div", null,
/*#__PURE__*/React.createElement(MyUIA, null),
/*#__PURE__*/React.createElement(MyUIB, null));
// Way B
return /*#__PURE__*/React.createElement("div", null, MyUIA(), MyUIB());
}

How do i return an object from my react state

I am trying to find an item from a collection, from the code below, in order to update my react component, the propertState object isnt empty, it contains a list which i have console logged, however I seem to get an underfined object when i console log the value returned from my findProperty function... I am trying update my localState with that value so that my component can render the right data.
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState()
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
return (<>{property.propertyName}</>)
}
export default PropertyComponent
There are a few things that you shall consider when you are finding data and updating states based on external sources of data --useParams--
I will try to explain the solution by dividing your code in small pieces
const PropertyComponent = () => {
const { propertyId } = useParams();
Piece A: Consider that useParams is a hook connected to the router, that means that you component might be reactive and will change every time that a param changes in the URL. Your param might be undefined or an string depending if the param is present in your URL
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
Piece B: useSelector is other property that will make your component reactive to changes related to that selector. Your selector might return undefined or something based on your selection logic.
const[property, setProperty] = useState()
Piece C: Your state that starts as undefined in the first render.
So far we have just discovered 3 pieces of code that might start as undefined or not.
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
Piece D: Here is where more problems start appearing, you are telling your code that in every render a function findProperty will be created and inside of it you are calling the setter of your state --setProperty--, generating an internal dependency.
I would suggest to think about the actions that you want to do in simple steps and then you can understand where each piece of code belongs to where.
Let's subdivide this last piece of code --Piece D-- but in steps, you want to:
Find something.
The find should happen if you have an array where to find and a property.
With the result I want to notify my component that something was found.
Step 1 and 2 can happen in a function defined outside of your component:
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
NOTE: I took the liberty of modify your code by simplifying a little
bit your find function.
Now we need to do the most important step, make your component react at the right time
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState({ propertyName: '' }); // I suggest to add default values to have more predictable returns in your component
/**
* Here is where the magic begins and we try to mix all of our values in a consistent way (thinking on the previous pieces and the potential "undefined" values) We need to tell react "do something when the data is ready", for that reason we will use an effect
*/
useEffect(() => {
// This effect will run every time that the dependencies --second argument-- changes, then you react afterwards.
if(propertyId, propertyState.properties) {
const propertyFound = findProperty(propertyId, propertyState.properties);
if(propertyFound){ // Only if we have a result we will update our state.
setProperty(propertyFound);
}
}
}, [propertyId, propertyState.properties])
return (<>{property.propertyName}</>)
}
export default PropertyComponent
I think that in this way your intention might be more direct, but for sure there are other ways to do this. Depending of your intentions your code should be different, for instance I have a question:
What is it the purpose of this component? If its just for getting the property you could do a derived state, a little bit more complex selector. E.G.
function propertySelectorById(id) {
return function(store) {
const allProperties = propertiesStateSelector(store);
const foundProperty = findProperty(id, allProperties);
if( foundProperty ) {
return foundProperty;
} else {
return null; // Or empty object, up to you
}
}
}
Then you can use it in any component that uses the useParam, or just create a simple hook. E.G.
function usePropertySelectorHook() {
const { propertyId } = useParams();
const property = useSelector(propertySelectorById(propertyId));
return property;
}
And afterwards you can use this in any component
functon AnyComponent() {
const property = usePropertySelectorHook();
return <div> Magic {property}</div>
}
NOTE: I didn't test all the code, I wrote it directly in the comment but I think that should work.
Like this I think that there are even more ways to solve this, but its enough for now, hope that this helped you.
do you try this:
const found = propertyState.properties.find(element => element.propertyId === propertyId);
setProperty(found);
instead of all function findProperty

Infinite loop when using useLazyQuery in React + Apollo/GraphQL?

My code so far looks something like this:
const { ID } = useParams();
const [getObjects, {loading, data}] = useLazyQuery(GET_OBJECTS_BY_ID);
const objectWithID = props.data.find(datum => datum._id == ID);
if (objectWithID.conditional) {
getObjects({variables: {objectIds: objectWithID.subObjects}});
//Do a bunch of other stuff including a separate render
}
else {...}
What I'm essentially doing is finding an object with the specified ID first, and then querying for its subObjects. I want to first find the objectWithID variable before querying, and then based on one of its parameters, conditionally use its value, hence I think useLazyQuery helps to achieve this. However, this causes an infinite loop: for some reason it's being called an infinite number of times, crashing my webpage. Am I using useLazyQuery incorrectly? How could I prevent this infinite loop?
In this case, you're executing the query inside the render method, meaning it'll just keep firing. Instead, consider using a useEffect hook:
const { ID } = useParams();
const [getObjects, { loading, data }] = useLazyQuery(GET_OBJECTS_BY_ID);
const objectWithID = useMemo(() => {
return props.data.find((datum) => datum._id == ID);
}, [props.data, ID]);
useEffect(() => {
if (objectWithID.conditional) {
getObjects({ variables: { objectIds: objectWithID.subObjects } });
}
}, [getObjects, objectWithID]);
return objectWithID.conditional ? (
<>Render one thing</>
) : (
<>Render the other thing</>
);
Note that I also threw in a useMemo hook to memoize the objectWithID value so it only changes when props.data or ID changes.

Using multiple refs on a single React element

I'm using the useHover() react hook defined in this recipe. The hook returns a ref and a boolean indicating whether the user is currently hovering over element identified by this ref. It can be used like this...
function App() {
const [hoverRef, isHovered] = useHover();
return (
<div ref={hoverRef}>
{isHovered ? 'Hovering' : 'Not Hovering'}
</div>
);
}
Now let's say that I want to use another (hypothetical) hook called useDrag which returns a ref and a boolean indicating whether the user is dragging the current element around the page. I want to use this on the same element as before like this...
function App() {
const [hoverRef, isHovered] = useHover();
const [dragRef, isDragging] = useDrag();
return (
<div ref={[hoverRef, dragRef]}>
{isHovered ? 'Hovering' : 'Not Hovering'}
{isDragging ? 'Dragging' : 'Not Dragging'}
</div>
);
}
This won't work because the ref prop can only accept a single reference object, not a list like in the example above.
How can I approach this problem so I can use multiple hooks like this on the same element? I found a package that looks like it might be what I'm looking for, but I'm not sure if I'm missing something.
A simple way to go about this is documented below.
Note: the ref attribute on elements takes a function and this function is later called with the element or node when available.
function App() {
const myRef = useRef(null);
return (
<div ref={myRef}>
</div>
);
}
Hence, myRef above is a function with definition
function(element){
// something done here
}
So a simple solution is like below
function App() {
const myRef = useRef(null);
const anotherRef = useRef(null);
return (
<div ref={(el)=> {myRef(el); anotherRef(el);}}>
</div>
);
}
A React ref is really nothing but a container for some mutable data, stored as the current property. See the React docs for more details.
{
current: ... // my ref content
}
Considering this, you should be able to sort this out by hand:
function App() {
const myRef = useRef(null);
const [hoverRef, isHovered] = useHover();
const [dragRef, isDragging] = useDrag();
useEffect(function() {
hoverRef.current = myRef.current;
dragRef.current = myRef.current;
}, [myRef.current]);
return (
<div ref={myRef}>
{isHovered ? 'Hovering' : 'Not Hovering'}
{isDragging ? 'Dragging' : 'Not Dragging'}
</div>
);
}

ReactJS: Component doesn't rerender on state change

I am trying to update state on click event using react hooks. State changes, but component doesn't rerender. Here is my code snippet:
function ThirdPage() {
const [selectedIngredients, setSelectedIngredients] = useState([])
const DeleteIngredient = (ingredient) => {
let selectedIngredientsContainer = selectedIngredients;
selectedIngredientsContainer.splice(selectedIngredientsContainer.indexOf(ingredient), 1);
setSelectedIngredients(selectedIngredientsContainer);
console.log(selectedIngredients);
}
const selectedIngredientsDiv = selectedIngredients.map(ingredient =>
(
<div className={styles.selectedIngredientsDiv}>{ingredient}
<div className={styles.DeleteIngredient}
onClick={() => {
DeleteIngredient(ingredient)}}>x</div></div>
))
return (
...
What am I doing wrong? Thanks in advance!
Issue with you splice as its not being saved to selectedIngredientsContainer. I would do following:
selectedIngredientsContainer = selectedIngredientsContainer.filter(value => value !== ingredient);
or
selectedIngredientsContainer.splice(selectedIngredientsContainer.indexOf(ingredient), 1 );
setSelectedIngredients([...selectedIngredientsContainer]);
Hope it helps.
normally I would leave an explanation on what's going on but tldr is that you should check first to make sure that you're array isn't empty, then you you can filter out the currentIngredients. Also you don't need curly brackets to call that function in the jsx but that can be personal flavor for personal code. I apologize if this doesn't help but I have to head out to work. Good luck!
function ThirdPage() {
const [selectedIngredients, setSelectedIngredients] = useState([]);
const DeleteIngredient = ingredient => {
// let selectedIngredientsContainer = selectedIngredients;
// selectedIngredientsContainer.splice(selectedIngredientsContainer.indexOf(ingredient), 1);
// setSelectedIngredients(selectedIngredientsContainer);
// console.log(selectedIngredients);
if (selectedIngredients.length > 0) {
// this assumes that there is an id property but you could compare whatever you want in the Array.filter() methods
const filteredIngredients = setSelectedIngredients.filter(selectedIngredient => selectedIngredient.id !== ingredient.id);
setSelectedIngredients(filteredIngredients);
}
// nothing in ingredients - default logic so whatever you want
// log something for your sanity so you know the array is empty
return;
};
const selectedIngredientsDiv = selectedIngredients.map(ingredient => (
<div className={styles.selectedIngredientsDiv}>
{ingredient}
<div className={styles.DeleteIngredient} onClick={() => DeleteIngredient(ingredient)}>
x
</div>
</div>
));
}
The answer is very Simple, your state array selectedIngredients is initialized with an empty array, so when you call map on the empty array, it will not even run once and thus DeleteIngredient is never called and your state does not change, thus no re-render happens

Categories