I have a function which returns JSX to some React components. I realized that I need to also include some of the state variables of those components in the JSX. Each component might need to change the JSX or the values in it (or both). See the code below.
export function getDisplayBlock(a: number) {
if (a == 1) {
return <>{title}</>;
} else if (a == 2) {
return (
<>
{title}
{subtitle}
</>
);
} else if (a == 3) {
return (
<>
<img src={companyLogo} />
{caption}
</>
);
}
}
This doesn't work because in getDisplayBlock the title,subtitle,companyLogo and caption are not known.
Some workarounds I considered:
Pass all those (title,subtitle) as another argument to the function. this requires recalling the function everytime these variables are updated
Return a string instead of JSX and substitute the parameter values with regex, then use dangerouslySetInnerHTML.
"Hack it" (advantage here is that you call the function once to get the JSX that the component needs):
export function getDisplayBlock(this: any, a: number) {
if (a == 1) {
return () => <>{this.state.title}</>;
} else if (a == 2) {
return () => (
<>
{this.state.title}
{this.state.subtitle}
</>
);
} else {
return () => (
<>
<img src={this.state.companyLogo} />
{this.state.caption}
</>
);
}
}
Any ideas appreciated!
There is no way to refresh the return value of the function without recalling it. You can checkout if something like React.Context is what you're interested in though it won't really improve the amount of calls to the rendering code and thus performance (is that the concern ?).
I don't fully understand the use case, i.e. if the parent components will always ever only render one of the jsx return values, then spread them out into separate components instead of having them in the same function.
The end result will be the same, the function will need to be recalled every time the state updates. But the code will be much cleaner.
Update per refreshed question:
If you want to control the JSX passed in, maybe rendering children in the subcomponent will work for you like so:
const GetDisplayBlock = props => {
const { a } = props;
return (
<>
{a == 1 ? (
<>{props.children}</>
) : a == 2 ? (
<>
<diV>{props.children}</diV>
</>
) : (
<>
<p>{props.children}</p>
</>
)}
</>
);
};
...
Then in the rendering call, you may call it like so:
<GetDisplayBlock a={3}>
<div>buyaka</div>
</GetDisplayBlock>
Related
I'm trying to create a helper function that nest a passed element/component as a children of itself of several depths.
So something like this
const Chain = nested(4, () => <div />)
return (
<Chain />
)
Should render divs nested 4 levels
<div>
<div>
<div>
<div />
</div>
</div>
</div>
I implemented the function in React like this.
/** nest the given element in itself of specified depth */
export const nested = (
depth: number,
element: () => JSX.Element
): FC<any> => () => {
let chain = element();
for (let i = 0; i < depth; i++) {
chain = React.createElement(
element().type,
element().props,
chain
);
}
return chain;
};
It renders and displays the nested Divs correctly, but gives back an error message i don't understand how to correct.
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
Try going with a recursive solution.
const createInnerElements = (count, childElement) => {
if (count == 1) {
return <div>{childElement}</div>
}
return createInnerElements(count - 1, <div>{childElement}</div>);
}
// in your parent component,
return <>{createInnerElements(4, <></>)}</>
Here's the solution I settled with. Credit to #senthil balaji's answer.
export const NestedTags = (depth: number, childElement: JSX.Element): JSX.Element => {
if (depth === 1) {
return (
<childElement.type {...childElement.props}>
{childElement}
</childElement.type>
);
}
return NestedTags(
depth - 1,
<childElement.type {...childElement.props}>
{childElement}
</childElement.type>
);
};
and should be wrapped in a React Fragment using used in parent.
<>
{NestedTags(5, <div className='segment'><div>)}
</>
The caveat for this is that it can't nest when passed a React Component Type like so:
const Test = ({className}: {className: string}) => (
<a className={className}>hello</a>;
)
//...
return (
<>
{NestedTags(5, <Test className='test'/>}
</>
)};
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).
I have a component which renders post on page.
On this component I have comments section.
In addition I have 2 components one for if user is not comment author and another one if the user is comment author.
Difference is that if user is author of the comment,comments will render with delete/edit buttons.
Now about the problem.
const checkCommentAuthor = (): boolean => {
return comments.map((item: any): boolean => {
if (item.username === localStorage.getItem("username")) {
return true;
} else {
return false;
}
});
};
return (
checkCommentAuthor ? (
< IsCommentAuthor />
) : ( <IsNotCommentAuthor/> )
);
};
If I have 2 comments on the post and let's say only one comment belongs to me(I mean I'm the author) the function will return <IsCommentAuthor/> for both of them.
Is there any possible way to return <IsNotCommentAuthor/> for some comments and < IsCommentAuthor /> for some?
You can simply return components in functions also.
Like
const checkCommentAuthor = () => {
return comments.map((item) =>
item.username === localStorage.getItem('username') ? (
<IsCommentAuthor />
) : (
<IsNotCommentAuthor />
)
)
}
Also in your code, there are two return() defined so only first one will return the function and code will never reach to next return()
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>
)
}
I'm using typescript and react for a project and I have a case of nested components where I only want the inner component to result in content if some condition is true.
For example:
export const SomeOverview = (info) => {
... // some data manipulation
return (
<div>
<div><.... a bunch of stuff</div>
<SomeDetail data={data}/>
</div>
)
}
export const SomeDetail = (info) => {
...
if (<some condition using info) {
return (
<div>
<some content using info>
</div>
)
}
return null or <div/> ?
}
I can't just not return anything if I don't go into the if statement or else I get the react element error.
So for now I had tried putting in an empty div but it seems kind of like a hack. Is there a "better" way of accomplishing?
Returning null is the way to go.
From the official documentation:
In rare cases you might want a component to hide itself even though it
was rendered by another component. To do this return null instead of
its render output.
https://reactjs.org/docs/conditional-rendering.html#preventing-component-from-rendering