How to place return code in a function: React - reactjs

I currently have a react project I'm working on. My render method looks like this going into my return method:
render() {
let elements = [];
this.dropdownCounter().forEach(item => {
if(item != "attributeProduct") {
console.log('setting');
elements.push(
<Dropdown
title={this.state[item][0]['title']}
arrayId={item}
list={this.state[item]}
resetThenSet={this.resetThenSet}
/>
);
}
});
this.state.attributeProduct.map(attributeItem => {
elements.push(
<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>
);
});
return (
I have a lot of code going on in the render area due to different drop downs dependent on other methods. Is there a way that I can do something like this instead?
render() {
allMyPrereturnStuff()
return()
}
Then just place all this code in allMyPrereturnStuff()? I've tried creating this function and passing everything there but it doesn't work due to all the "this". Any ideas?

Yes, you can easily drop in normal javascript expressions into JSX:
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{this.renderMoreStuff()}
</div>
);
You can even base it on flags:
const shouldRenderMoreStuff = this.shouldRenderMoreStuff();
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{shouldRenderMoreStuff ? this.renderMoreStuff() : null}
</div>
);
Do note that it is often an anti-pattern to have render* methods in your components other than the normal render method. Instead, each render* method should probably be its own component.

Don't forget to bind your allMyPrereturnStuff() method in the constructor so "this" will work inside it.
constructor(props) {
super(props);
// ... your existing code
this.allMyPrereturnStuff = this.allMyPrereturnStuff.bind(this);
}
allMyPrereturnStuff = (params) => {
// ... all the code
}
However, you might want to consider breaking out the code to components, which is more Reacty way to do things.
For example, you could refactor this
this.state.attributeProduct.map(attributeItem => {
elements.push(<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>);
});
To something like (somewhat pseudocody):
const DropdownList = (props) => {
return (<Dropdown
title={props.attributeItem.name}
arrayId='attributeMetaProduct'
list={props.list}
resetThenSet={props.resetThenSet}
/>);
}
And in the original component's render function, have something like
render() {
return (this.state.attributeProduct.map(attributeItem => {
<DropdownList attributeItem={attributeItem}
list={ this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID) }
resetThenSet={this.resetThenSet}
/>);
}

Related

How to fix the error "react use hook cannot be called inside a callback function" using react?

i am using useHook named useGetCompanyByItemId in the return statement.
and so i am getting the error
"react hook cannot be called in a callback function"
what i am trying to do?
i am querying for owneditems and shareditems.
and i display both the items. in the Content div i do mapping and there i am calling the useGetCompanyByItemId hook and i get the error.
below is my code,
function Parent() {
const ownedItems = [{ //somearray of objects}];
const sharedItems = [{//somearray of objects}];
const getCurrentItems = () => {
return ownedItems.concat(sharedItems);
}
return (
<Wrapper>
{getCurrentItems.length> 0 &&
<FirstWrapper>
//somedivs
</FirstWrapper>
<Content>
{springProps.map((index) => {
const item = getCurrentItems()[index];
const isSharedItem = item && item.cognitoId !== cognitoId;
const company = useGetCompanyByItemId(item.id); //here is the error
return (
<>
{isSharedItem &&
<div>
<span>company</span>
</div>
}
</>
}
)
}
);
</Content>
</Wrapper>
);
}
i am not sure how to fix this. i need to pass the item.id for the useGetCompanyById hook and i dont know how to pass that item.id from outside the return statement since that would fix that error.
could someone help me fix this error. thanks.
Extract this logic to a component
function Item({ item, isSharedItem }) {
const company = useGetCompanyByItemId(item.id);
return (
<>
{isSharedItem && (
<div>
<span>company</span>
</div>
)}
</>
);
}
and then use it in your loop
springProps.map((index) => {
...
return <Item item={item} isSharedItem={isSharedItem} key={index} />
I can see two ways of refactoring here:
Option 1: If you dont have control over the custom hook to modify
Extract the iteration into a component:
const Company = ({itemId, isSharedItem}) => {
const company = useGetCompanyByItemId(itemId);
return (<>
{isSharedItem &&
(<div>
<span>{company}</span>
</div>)
}
</>);
}
Use the above component while you iterate.
Option 2: If you have control over the custom hook:
I would recommend to refactor custom hook to return a method than object. Sample usage:
const {getCompanyByItemId} = useFetchCompany();
.
.
.
anywhere in the code,
getCompanyByItemId(itemId)
Obvious advantage with above option:
Readable and extendable and use it anywhere and even pass around
You don't have to worry about refactoring and code splitting just not to break hook rules(do so if it makes sense ofcourse).

How to show dinamically a react component

I'm working in a React project and I need to render some Components based on a layout.
--asumme you have an array that tells you the components you need to render:
Layout1 = ['Events', 'Photo', 'News']
--And a function that, depends on the module, render the especific component with some data:
layout1.forEach(function(layout) {
someFuncions(layout, somedata);
});
someFunction = (layout, data) => {
layout.forEach( function(Comp) {
if(Comp == 'Events') {
return (<Events module-data={data} />)
} else if(Comp == 'Photo') {
return (<Photo module-data={data} />)
} else if(Comp == 'News') {
return (<News module-data={data} />)
}
});
}
-- Since it is possible to have many components I want to find a way to avoid the "if" function to determine what component should I render.
How can I achieve this? Thanks a lot guys
You can use a switch similar syntex but maybe a little cleaner.
switch(comp) {
case 'Events':
return (<Events module-data={data} />)
case 'Photo':
return (<Photo module-data={data} />)
case 'News':
return (<News module-data={data} />)
}
You can pass the component in the array directly.
import Events from 'path/to/Events'
import Photo from 'path/to/Photo'
import News from 'path/to/News'
Layout1 = [Events, Photo, News]
And then wherever you call the function.
From your original question, it seems like the layout1 in your question is an array of Layout1 like arrays.
layout1.forEach(function(layout) {
someFuncions(layout, somedata);
});
someFunction = (layout, data) => {
// returning from forEach won't result in anything. you probably want to map
layout.forEach( function(Component) {
return (<Component module-data={data} />)
})
}
I would approaching it by creating a map for your components, then doing a lookup when rendering.
const components = {
Events, // Shorthand for 'Events': Events
Photo,
News
};
const layout = ['Events', 'Photo'];
const someFunction = (layout, data) =>
layout.map((name) => {
const Component = components[name];
return <Component module-data={data} />;
});
What about using a javascript object that maps the layout to the desired component? This way, it's clear that you intend to render a different component depending on the layout.
See the following example:
const someFunction = (layout, data) => {
const componentMap = {
'Events': <Events module-data={data} />,
'Photo': <Photo module-data={data} />,
'News': <News module-data={data} />
}
return componentMap[layout];
}

useRef in a dynamic context, where the amount of refs is not constant but based on a property

In my application I have a list of "chips" (per material-ui), and on clicking the delete button a delete action should be taken. The action needs to be given a reference to the chip not the button.
A naive (and wrong) implementation would look like:
function MemberList(props) {
const {userList} = this.props;
refs = {}
for (const usr.id of userList) {
refs[usr.id] = React.useRef();
}
return <>
<div >
{
userList.map(usr => {
return <UserThumbView
ref={refs[usr.id]}
key={usr.id}
user={usr}
handleDelete={(e) => {
onRemove(usr, refs[usr.id])
}}
/>
}) :
}
</div>
</>
}
However as said this is wrong, since react expects all hooks to always in the same order, and (hence) always be of the same amount. (above would actually work, until we add a state/any other hook below the for loop).
How would this be solved? Or is this the limit of functional components?
Refs are just a way to save a reference between renders. Just remember to check if it is defined before you use it. See the example code below.
function MemberList(props) {
const refs = React.useRef({});
return (
<div>
{props.userList.map(user => (
<UserThumbView
handleDelete={(e) => onRemove(user, refs[user.id])}
ref={el => refs.current[user.id] = el}
key={user.id}
user={user}
/>
})}
</div>
)
}

react dynamically add children to jsx/component

Situation
I'm attempting to render a component based on template jsx pieces. Reason being because I have a few of these situations in my app but with subtle customizations and I'd rather leave the business logic for the customizations in the respective component, not the factory.
Example
Parent Template
<div></div>
Child Template
<p></p>
In the render function I want to add n child components to the parent. So if n=4 then I would expect output like
<div>
<p></p>
<p></p>
<p></p>
<p></p>
</div>
I tried using parentTemplate.children.push with no avail because the group template is still JSX at this point and not yet a rendered component. How can I accomplish this task using jsx + react ?
Extra
Here is what my actual code looks like so far
render() {
let groupTemplate = <ListGroup></ListGroup>
let itemTemplate = (item) => {
return <ListGroup.Item>{item.name}</ListGroup.Item>;
}
if (this.props.itemTemplate) itemTemplate = this.props.itemTemplate;
let itemsJsx;
this.props.navArray.forEach((item) => {
groupTemplate.children.push(itemTemplate(item))
});
return (
[groupTemplate]
);
}
From the parent:
const groupTemplate = <ListGroup className='custom-container-classes'></ListGroup>
const itemTemplate = <ListGroup.Item className='customized-classes'></ListGroup.Item>
<NavigationFactory groupTemplate={groupTemplate} itemTemplate={itemTemplate}>
</NavigationFactory>
You could use render props, which is the best way to achieve something like that:
The parent:
<NavigationFactory
itemTemplate={itemTemplate}
render={children => <ListGroup className='custom-container-classes'>
{children}
</ListGroup>}
/>
The Child:
render() {
this.props.render(this.props.navArray.map(item => <ListGroup.Item key={item.id}>
{item.name}
</ListGroup.Item>))
}
This will let you define the container on your calling function and enables you to create the chidlren as you want.
Hope this helps.
Unclear on your question but I have attempted to figure out based on your code on what you are trying to do, see if it helps, else I'll delete my answer.
The mechanism of below code working is that it takes in groupTemplate which in your case you'll be passing in as a prop which then takes children within it and returns them composed all together. There's default children group template which you can use if no related prop is passed and you have a mapper function within the render which determines which template to use and renders n number of times based on navArray length.
componentDidMount() {
// setup default templates
if (!this.props.navButtonContainerTemplate) {
this.prop.navButtonContainerTemplate = (items) => {
return <ListGroup>{items}</ListGroup>
}
}
if (!this.props.navButtonTemplate) {
this.props.navButtonTemplate = (item) => {
return (
<ListGroup.Item>
{item.name}
</ListGroup.Item>
)
}
}
}
render() {
const itemsJsx = this.props.navArray.map(obj => {
const itemJsx = this.props.navButtonTemplate(obj)
return itemJsx;
});
return this.props.navButtonContainerTemplate(itemsJsx);
}
Pass the contents as regular children to the ListGroup via standard JSX nesting:
render() {
let itemTemplate = (item) => {
return <ListGroup.Item>{item.name}</ListGroup.Item>;
}
if (this.props.itemTemplate) itemTemplate = this.props.itemTemplate;
return (
<ListGroup>
{this.props.navArray.map(item => (
itemTemplate(item)
))}
</ListGroup>
);
}

How can I get the cumulative width of a list of components?

I have a list of "selections" that are displayed using a component. I need to find the rendered width of all these selections. My template looks like this:
{props.selections.map((chip: SelectOptionType) => {
return (
<Chip text={chip.label} />
)
}
Typically, in a non-React application, I'd probably put a class on the <Chip /> and use jquery to select elements of that class name, then loop over them and just sum the widths together:
let sum: number = 0;
$(".someClassName").forEach(($el) => sum += $el.offsetWidth);
I know the suggested way of doing something similar to this is using refs, but it seems you cant create an array of refs. I tried doing something like this:
{props.selections.map((chip: SelectOptionType, index: number) => {
chipsRefs[index] = React.createRef<HTMLDivElement>();
return (
<div ref={chipsRefs[index]}>
<Chip text={chip.label} />
</div>
)
}
But as I quickly learned each Ref inside chipsRefs ended up with a null current.
Now I'm a bit at a loss for this and have tried finding examples of this use case but have come up empty.
Can you try this ?
ref={ref => {
chipsRefs[index] = ref
}}
Try doing something like this: https://codesandbox.io/s/awesome-haibt-zeb8m
import React from "react";
class Selections extends React.Component {
constructor(props) {
super(props);
this._nodes = new Map();
}
componentDidMount() {
this.checkNodes();
}
checkNodes = () => {
let totalWidth = 0;
Array.from(this._nodes.values())
.filter(node => node != null)
.forEach(node => {
totalWidth = totalWidth + node.offsetWidth;
});
console.log(totalWidth);
};
render() {
const { selections } = this.props;
return (
<div>
{selections.map((value, i) => (
<div key={i} ref={c => this._nodes.set(i, c)}>
{value}
</div>
))}
</div>
);
}
}
export default Selections;
The function we defined in the ref prop is executed at time of
render.
In the ref call-back function, ref={c => this._nodes.set(i, c)}
we pass in the index (i) provided by .map() and the html element
(c) that is provided by the ref prop, in this case the div itself.
this._nodes.set(i, c) will create a new key-value pair in our
this._nodes iterable, one pair for each div we created. Now we have recorded HTML elements (nodes) to work with that contain all the methods we need to calculate the totalWidth of your rendered list.
Lastly in checkNodes() we get the .offsetWidth of each node to get our totalWidth.

Resources