Issue with returning react component from function - reactjs

I am trying to return a list of myCustomComponent from a list so they it can be rendered but it throws this error or goes into the infinity loop. The error is as follows:
Expected an assignment or function call and instead saw an expression react
const renderMyData = () => {
// groupedData is an array in the component made through useState()
const myData = groupedData.map((c, index) => {
return <MyCustomComponent key={index} data={c} />; It goes into the infinity loop
});
return myData;
};
Function is called like this in the component:
{renderMyData()}

Related

When are components defined in functions evaluated? (React Hooks)

Suppose I have a component that renders a list item:
const ListItem = ({ itemName }) => {
return (
<div>
{itemName}
</div>
)
}
And because this list item is used in many places in my app, I define a custom hook to render the list and control the behavior of each list instance:
const useListItems = () => {
const [ showList, setShowList ] = useState(true)
const { listItemArray, isLoaded } = useListContext() // Context makes api call
const toggleShowList = setShowList(!showList)
function renderListItems() {
return isLoaded && !!listItemArray ? listItemArray.map((itemName, index) => (
<ListItem key={index} isVisible={showList} itemName={itemName}/>
))
:
null
}
// Some other components and logic...
return {
// ...Other components and logic,
renderListItems,
toggleShowList,
}
}
My first question is, when will the array of ListItems actually be evaluated ? Will the jsx resulting from renderListItems() be calculated every time renderListItems() is called? Or would it happen every time useListItems() is called?
Second question: if I call useListItems() in another component but don't call renderListItems(), does that impact whether the components are evaluated?
I have been struggling to find an answer to this, so thanks in advance.

Use component function in React loop

I heard that () is not good at the rendering performance so it should not be used. However, in this case, it can not get the results when passing the function without ().
You can check the reseult in CodeSendbox: Link
const comp = () => {
return <div>This is Component</div>;
};
const elements = [comp(), comp]; // Please Attention This Line!
const items = [];
for (const [index, value] of elements.entries()) {
items.push(
<div>
{index}: {value} <br />
</div>
);
}
export default function Index() {
return <>{items}</>;
}
Result is:
0:
This is Component
1:
Why only comp() but not comp? What is the best practice in this case?
You can use JSX instead of calling the function:
const comp = () => {
return <div>This is Component</div>;
};
const elements = [comp, comp]; // Remove the function call, save only the reference
const items = [];
// need to use Value with V instead of v for JSX
for (const [index, Value] of elements.entries()) {
items.push(
<div>
{index}: <Value/> <br /> // render as JSX
</div>
);
}
export default function Index() {
return <>{items}</>;
}
Here's a sandbox link showing how it works: https://codesandbox.io/s/cranky-mclean-7ymzr
comp is an arrow function that returns a JSX literal. It's a "function" that is stored in a variable called comp. When called/invoked as comp(), notice the parenthesis, it is executed and the computed return value is returned in the array. When used without the parenthesis, like comp, this is simply the reference to the variable (that can be invoked like a function).
Functions aren't valid react children nor valid JSX.
Introducing JSX
const comp = () => {
return <div>This is Component</div>;
};
const elements = [comp(), comp]; // saves a JSX literal, and a function
const items = [];
for (const [index, value] of elements.entries()) {
items.push(
<div>
{index}: {value} <br /> // trying to render a function here in `value`
</div>
);
}
export default function Index() {
return <>{items}</>;
}
I assume you got the idea () is "not good at rendering performance" from some documentation about in-line anonymous arrow functions and event handlers and mapping an array of data. That is a different issue altogether. I believe your situation is just a misunderstanding about how arrow functions are invoked.

Unable to display promise data inside a method in render

I realize I can't return objects in a react render but I'm having a problem figuring out how to do this.
Error message:
Invariant violation, objects are not valid as a React child error
My method:
displayListItemComponent = item => {
return this.getConcernsDB(item.values)
.then(returnedConcerns => {
if(returnedConcerns.length) {
returnedConcerns.forEach(el => {
return(el.name);
})
}
}
)
}
Partial render:
<CollapseBody>
{ el.details.map(item => (
this.displayListItemComponent(item)
))}
</CollapseBody>
Here I am trying to return el.name from displayListItemComponent
I am attempting to return displayListItemComponent in my react render.
My assumption was that by returning the function this.getConcernsDB that it would chain down and it would return el.name. How can I achieve this?
Your method returns a Promise. Render should be synchronous and have no side effects (e.g. it should only return allowed values from props and state). Therefore you need to store your data in state, and render elements from state
You could do something like this. Also what does el.name contain?
displayListItemComponent =(props) => {
const {item} = props;
const [data, setData] = useState();
useEffect(() => {
// i see you did this.getConcernsDB. where is it coming from?
getConcernsDB(item.values)
.then(returnedConcerns => {
setData(returnedConcerns.values(el => el.name))
)
}, [])
if(!data) return <SomeLoadingSpinner />
// now you have access to the names in `data` variable. Return whatever component you want using the `data`
}

Why am I able to conditionally call a hook one way, but not the other?

Context:
When I refresh the dashboard, useHasPermission makes an async call to determine if the
user has access to somePermission.
Issue:
hasPermission initially evaluates to false, but once the async call has completed hasPermission evaluates to true.
This causes the useQuery, Apollo hook to not be called on the first render, and then called on the second render.
The following error is shown:
"Rendered more hooks than during the previous render."
Question:
Why does this error only happen in example A and not example B?
// Example A: Does not work
const Dashboard = () => {
const hasPermission = useHasPermission([somePermission]);
const getDashboardData = () => {
const { loading, data, error } = useQuery(SOME_QUERY, {
variables: { ...someVars }
});
return <Table ={data} loading={loading} error={error}><Table>
};
return (
{hasPermission ? getDashboardData() : null}
<Announcements></Announcements>
)
}
// Example B: Works
const Dashboard = () => {
const hasPermission = useHasPermission([somePermission]);
const DashboardData = () => {
const { loading, data, error } = useQuery(ACCOUNTS_FOR_CUSTOMER_DASHBOARD, {
variables: { ...someVars }
});
return <Table ={data} loading={loading} error={error}><Table>
};
return (
{hasPermission ? (
<DashboardData></DashboardData>
) : null}
<Announcements></Announcements>
)
}
Hooks aren't meant to be conditionally used.
In the first example, you are conditionally calling a function that uses a new hook and returns JSX, so this breaks the rules of hooks.
In the second example, you are creating a new component DashboardData that mounts conditionally. So because it is a new component it is allowed.
So the difference between the two is in "A" useQuery belongs to the Dashboard component, where in "B" it belongs to DashboardData.

React TypeScript - passing a callback function

The following code would have worked happily in JavaScript. I am using React+TypeScript, hence this is a JSX file:
Calling component:
<FiltersPanel onFiltersChanged={() => { console.log('I was called') }} />
Inner Component:
const FiltersPanel = (onFiltersChanged) => {
const handleFiltersChange = () => {
onFiltersChanged(); /* I am getting `TypeError: onFiltersChanged is not a function` */
};
return (<div onClick={handleFiltersChange}/> );
};
export default FiltersPanel;
Why is TypeScript complaining that it cannot find the function, while the function is certainly passed as a prop.
You passed the function as a props so you should receive it to your component as a props like this
const FiltersPanel = (props) => { }
then use it like this
props.onFiltersChanged()
OR you can use destructuring like this
const FiltersPanel = ({onFiltersChanged}) => { }
so you can use it without the props object like this
onFiltersChanged()

Resources