Infinite recursion using React Hooks - reactjs

I am using React's State Hook in a functional component for getting therapist from database. From which I seen, in useEffect() where setTherapists([therapists].concat(therapists)); is, the list is called in an infinite recursion. I can't see where the problems is, or how do I need to proper call the list.
After I correct get the therapists array I need to render all the therapist. This is how I thought about, but I don't know exactly how to write with Hooks State:
function renderTherapists() {
const items = this.state.therapists.map( (t, idx) => (
<TherapistCard therapist={t} key={idx} />
))
return (
<div ref={0} className="therapist-list">
{ items }
</div>
)
}
My current functional component:
function TypeArticleOne(props) {
const [ therapists, setTherapists ]= useState([]);
const [speciality, setSpeciality]= useState('ABA');
const [pageLoading, setPageLoading]= useState(true);
const topProfilesUrl = 'therapists/top/profiles'
useEffect(() => {
console.log(speciality);
getTopTherapists();
window.scrollTo(0, 0);
}, []);
const getTopTherapists = () => {
setPageLoading(true);
loadTopTherapists();
};
const loadTopTherapists = () => {
console.log("second");
props.actions.reqGetTherapistsTopProfiles({
body: {},
headers: null,
resource: `${topProfilesUrl}`
})
};
useEffect(() => {
let apiData = props.apiData;
if (apiData.topProfiles && apiData.topProfiles.success) {
const therapists = apiData.topProfiles.therapists;
setPageLoading(false);
setTherapists([therapists].concat(therapists));
}
}, [pageLoading, therapists]);

You need to pass the Therapists coming in the props to the default value of your useState declaration:
const [ therapists, setTherapists ] = useState(apiData.topProfiles.therapists);
So when it's re-rendered, it doesn't get called again in a loop.
The constant therapists is shadowing the therapist list from your outer scope.
if (apiData.topProfiles && apiData.topProfiles.success) {
const therapists = apiData.topProfiles.therapists;
setPageLoading(false);
setTherapists([therapists].concat(therapists));
}
Also, as it's already an array, you can call the concat method without put the list within brackets [].
Like:
const loadedTherapists = apiData.topProfiles.therapists;
setPageLoading(false);
setTherapists(therapists.concat(loadedTherapists));

Related

How do i trigger a useEffect based on a sharedValue from the Reanimated libary

How do i trigger a useEffect based on a sharedValue from the Reanimated libary?
const sharedValue = useSharedValue(0);
useEffect(() => {
console.log("exectue functions");
}, [sharedValue.value]);
Is there a best practice for this. Or is there another way to trigger functions (sync and async) based on a change in a sharedValue.
You can use:
useAnimatedReaction;
useDerivedValue;
Solution with useAnimatedReaction
const sharedValue = useSharedValue(0);
useAnimatedReaction(() => {
return sharedValue.value;
}, (result, previous) => {
if (result !== previous) {
console.log(result)
}
}, []); // No need to pass dependencies
Solution with useDerivedValue
const sharedValue = useSharedValue(0);
useDerivedValue(() => {
console.log(sharedValue.value);
}, []); // No need to pass dependencies
useSharedValue in the Reanimated v2 library actually returns a reference and in react useEffect does not trigger on mutation in reference variable. So if you want to execute functions with changing useSharedValue I suggest you use useCallback or a function trigger.
EDIT:
UseCallback would only work for node references such as
const Component = () => {
const [isMounted, toggle] = useReducer((p) => !p, true);
const [elementRect, setElementRect] = useState();
const handleRect = useCallback((node) => {
setElementRect(node?.getBoundingClientRect());
}, []);
return (
<>
{isMounted && <div ref={handleRect}>Example</div>}
<button onClick={toggle}>Toggle</button>
<pre>{JSON.stringify(elementRect, null, 2)}</pre>
</>
);
};
If you want to make one with sharedRef then functional trigger is your only option:
const Component = () => {
const shared = useSharedValue(0);
const _incShared = () => shared.value++;
return(
<View></View>
)
}

Filtering react component state causes infinite render loop

I'm retrieving some data from an API using useEffect, and I want to be able to filter that returned data using a prop being fed into the component from its parent.
I'm trying to filter the state after it is set by useEffect, however it looks like the component is going into an infinite render loop.
What do I need to do to prevent this?
export default function HomeJobList(props: Props): ReactElement {
const [listings, setListings] = React.useState(null);
useEffect(() => {
const func = async () => {
let res = await service.getListings();
setListings(res);
};
func();
}, []);
if (props.searchTerm && listings) {
let filtered = listings.filter((x) => x.positionTitle.includes(props.searchTerm));
setListings(filtered);
}
return (
<>
<div>do stuff</div>
</>
);
}
I understand that the use of the setListing function is then causing a rerender after the filtering, which then causes another setListing call. But what's the best way to break this loop?
Should I just have another state value that maintains the last searchTerm used to filter and check against that before filtering?
Or is there a better way?
It's an indinite loop because every time you filter, you set it as a state variable, which causes re-rendering and filtering & setting the variable again - thus a loop.
I suggest you do it all in one place (your useEffect is a good place for that, because it only executes once.
useEffect(() => {
(async () => {
const res = await service.getListings();
const filtered = res.filter((x) => x.positionTitle.includes(props.searchTerm));
setListings(filtered);
})();
}, []);
When the state changes that trigger a re-render, that's why you have an infinite loop; what you have to do is to wrap your filtering in a useEffectthat that depends on the searchTerm prop, like this:
import React, { useEffect } from 'react';
export default function HomeJobList() {
const [listings, setListings] = React.useState(null);
useEffect(() => {
const func = async () => {
let res = await service.getListings();
setListings(res);
};
func();
}, []);
useEffect(() => {
if (listings) {
let filtered = listings.filter(x =>
x.positionTitle.includes(props.searchTerm)
);
setListings(filtered);
}
}, [props.searchTerm]);
return (
<>
<div>do stuff</div>
</>
);
}
You need to create a function that's called inside the JSX you're returning.
Actually, you'll need to render the component every time the Props objects changes. That's achieved by calling the function in the JSX code.
Example:
Function:
const filteredListings = () => {
if (props.searchTerm && listings) {
let filtered = listings.filter((x) => {
x.positionTitle.includes(props.searchTerm));
}
return filtered;
}
}
Return Statement:
return (
<ul>
{
filteredListings().map((listing) =>
<li>{listing.title}</li>
);
}
</ul>
);
What you need is a useEffect which does the filtering when props changes.
Replace this
if (props.searchTerm && listings) {
let filtered = listings.filter((x) => x.positionTitle.includes(props.searchTerm));
setListings(filtered);
}
with
useEffect(()=>{
if (props.searchTerm) {
setListings(prevListing => {
return prevListing.filter((x) => x.positionTitle.includes(props.searchTerm))
});
}
}, [props.searchTerm] )

Why does my UseState hook keeps on failing?

I want to use UseState hook for updating data in my Table component. The data to be used in the Table component is fetched by another function which is imported paginationForDataAdded.
Its look like stackoverflow due to re-rendering.
setAllData(searchResults); will re-render the component and again make api call and repated.
right way to call API.
const [allData, setAllData] = useState([]);
useEffect(function () {
const {
searchResults,
furnishedData,
entitledData
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
setAllData(searchResults);
});
Assuming paginationForDataAdded is a function that returns a Promise which resolves with an object that looks like the following:
{
searchResults: { resultarray: [...] },
furnishedData: [...],
entitledData: [...]
}
You should do the following your in component:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
paginationForDataAdded({
searchFunction: search,
collectionsData: collections,
})
.then(
({ searchResults, furnishedData, entitledData }) => {
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData);
}
)
.catch(/* handle errors appropriately */);
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
However, if paginationForDataAdded is not an asynchronous call, then you should do the following:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
const {
searchResults,
furnishedData,
entitledData,
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData)
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
Hope this helps.

useEffect array dependency is called in every render when array is not changed

import React, { useState, useEffect } from "react";
export default function App() {
const [columns, setColumns] = useState([
{ name: "a" },
{ name: "b" },
{ name: "c" }
]);
const [isOpen, setIsOpen] = useState(false);
const addName = () => setColumns([...columns, { name: "r" }]);
const toggleOpen = () => setIsOpen(!isOpen);
return (
<>
<List columns={columns} />
<button onClick={toggleOpen}>Toggle</button>
<button onClick={addName}>Add</button>
<p>{isOpen.toString()}</p>
</>
);
}
const List = ({ columns }) => {
const names = columns.map(col => col.name);
useEffect(() => {
console.log("Names is changed to: ", names);
}, [names]);
return <p>{names.join(" ")}</p>;
};
Names is changed to: is called, when isOpen state is changed in App component.
I want the console.log to be executed only when names array is changed.
I think in List component, it is creating a new array whenever render, so that the previous array and the new array are not equal.
You should memoize the component so it will render only on props change (or if comparison function passed as 2nd argument).
Currently, List rendered due to its parent App render.
const List = ({ columns }) => {
const names = columns.map((col) => col.name);
useEffect(() => {
console.log("Names is changed to: ", names);
}, [names]);
return <p>{names.join(" ")}</p>;
};
const MemoList = React.memo(List);
export default function App() {
return (
<>
<MemoList columns={columns} />
</>
);
}
See working example:
For class component, use React.PureComponent or implement shouldComponentUpdate.
const names = columns.map(col => col.name);
Creates a new array every time and useEffect thinks that dependencies have changed.
To avoid that either pass names directly to useEffect:
useEffect(() => {
console.log("Names is changed to: ", names);
}, names);
Or useMemo to get the same array object:
const names = useMemo(() => columns.map(
col => col.name
), [columns]);

Map an array in a functional component

function TypeArticleOne(props) {
let apiData = props.apiData;
const [ therapists, setTherapists ] = useState(apiData.topProfiles.therapists);
const [speciality, setSpeciality]= useState('ABA');
const [pageLoading, setPageLoading]= useState(true);
const topProfilesUrl = 'therapists/top/profiles'
useEffect(() => {
console.log(speciality);
getTopTherapists();
window.scrollTo(0, 0);
}, []);
const getTopTherapists = () => {
setPageLoading(true);
loadTopTherapists();
};
const loadTopTherapists = () => {
console.log("second");
props.actions.reqGetTherapistsTopProfiles({
body: {},
headers: null,
resource: `${topProfilesUrl}`
})
};
useEffect(() => {
if (apiData.topProfiles && apiData.topProfiles.success) {
const therapistsLoad = apiData.topProfiles.therapists;
setPageLoading(false);
setTherapists([therapists].concat(therapistsLoad));
}
}, []);
How to map an array in a functional component? I want to map the therapists array from the functional component above.
I call the therapists in an array from an database and I need to map them to render in a card, inside an functional component.
const renderTherapists = (props) => {
const items = props.therapists.map( (t, idx) => (
<TherapistCard therapist={t} key={idx} />
))
return (
<div ref={0} className="therapist-list">
{ items }
</div>
)
}
EDIT: You probably don't need the React Fragment as you already have a as pointed out in the comments. Possibly the point on destructuring might still help.
ORIGINAL:
You might need to wrap the array in a React Fragment (<>{array}</>). This is required as you cannot directly return an array of components.
Also not sure what the structure of your therapist object is, but if you want to destructure it then it should be ({t, idx}) => instead of (t, idx) => (add the curly braces).
const renderTherapists = (props) => {
const items = props.therapists.map( ({t, idx}) => (
<TherapistCard therapist={t} key={idx} />
))
return (
<div ref={0} className="therapist-list">
<>
{ items }
</>
</div>
)
}
This is for React 16.2.0 and later. See the blog post here: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html

Resources