How to dynamically change the react element in jsx - reactjs

I would like to refactor some legacy code. The use case is this. Someone previously had created a new component that basically had all the exact same attributes as the old component. The render method returns either old or new based on a flag, but the attribute values are all the same. This results in many redundant lines that were copy pasted.
return (
isSomeBoolTrue ? (<OriginalComponent a={a} b={b} ..... z={z} />):
(<NewComponent a={a} b={b} ..... z={z} />)
);
I'd like some way to remove the duplicate attributes and have it look something like this:
return (isSomeBoolTrue ? (<OriginalComponent):(<NewComponent) a={a} b={b} .... />

You can also try the following approach.
const SelectedComponent = isSomeBoolTrue ? OriginalComponent : NewComponent;
return <SelectedComponent a={a} b={b} z={z} />

One option is to put the props into an object, then spread the object into the each component's props while using the conditional operator.
const propsToPassDown = { a, b, z };
return (
isSomeBoolTrue
? <OriginalComponent {...{ propsToPassDown }} />
: <NewComponent {...{ propsToPassDown }} />
);
Another approach is to use React.createElement instead of JSX, allowing you to use the conditional operator when selecting the component to pass as the first argument.
return React.createElement(
isSomeBoolTrue ? OriginalComponent : NewComponent,
{ a, b, z },
null
);

Related

Rendering conditionals in our code for react.js jsx

Which is better practice??
{someBoolean ? <showMe/> : null }
or
{someBoolean && (<showMe/>)}
does it matter in the end? what are pros and cons? do we need to call null? Can one vs the other cause problems down the road? what is different syntax doing anyway?
On my case I prefer using this {someBoolean && ()} instead of the {someBoolean ? : null }. This answer is based on my preference.
Pros: you will never need to consider the ':null' else statement on the end and if you have a lot of types for the someBoolean for example you have default props for that component and your report form will have a lot of someBooleantypes you need to
consider them at the same time.
In cases like
<ParentComponent />
<ChildComponent someBoolean={true}/>
</ParentComponent>
<ParentComponent />
<ChildComponent someBooleanv2={true}/>
</ParentComponent>
ChildComopnent.jsx
const ChildComponent =({someBoolean,someBooleanv2})=>{
return (<div>
{someBoolean && !someBooleanv2 (<showMe/>)}
{someBooleanv2 && !someBoolean (<showMeV2/>)}
<div/>);
}
Most of the time I use it on a single ReportsForm if I would like to use it overtime to be efficient with formik functionalities

How can I get the JSX elements rendered by a component?

Short version: I have a component type (a class or a function) and props for it. I need to "render" the component to obtain its representation in JSX elements.
(I use the quotes because I mean «render into JSX elements» not «render into UI» and I am not sure about the terminology.)
Example:
const Foo = (props) => <div><Bar>{props.x + props.y}</Bar></div>;
// is an equivalent of `const elements = <div><Bar>3</Bar></div>;`
const elements = render2elements(Foo, { x: 1, y: 2 });
function render2elements(type, props) {
/* what should be here? */
}
Long version (for background story enthusiasts, may be skipped imo)
I have a React code whose very simplified version looks like this:
function Baby(props) {
/* In fact, it does not even matter what the component renders. */
/* It is used primarily as a configuration carrier. */
}
function Mother({ children }) {
const babies = getAllBabies(React.Children.toArray(children));
const data = parseData(babies);
return buildView(data);
}
function SomeOtherComponent(props) {
const { someProps1, someProps2,
someProps3, someCondition } = someLogic(props);
return (
<Mother>
<Baby {...someProps1} />
<Baby {...someProps2} />
{someCondition ? <Baby {...someProps3} /> : null}
</Mother>
);
}
It may be strange but it works. :) Until someone wants to do a little refactoring:
function Stepmother(props) {
const { someProps1, someProps2,
someProps3, someCondition } = someLogic(props);
return (
<>
<Baby {...someProps1} />
<Baby {...someProps2} />
{someCondition ? <Baby {...someProps3} /> : null}
</>
);
}
function SomeOtherComponent(props) {
return <Mother><Stepmother {...props} /></Mother>;
}
Now the Mother receives in its children only a JSX element for the Stepmother and can not parse the JSX elements for the Baby'ies. :(
So we return to my original question: I need to "render" Stepmother and then parse its internal JSX representation. But how can I do this?
P.S. I used functional components for brevity, but of course, all examples could use class components as well.
Thank you.
Don't do that.
I strongly encourage you to just rethink this solution altogether, ESPECIALLY if
It is used primarily as a configuration carrier.
...but.
So this kinda works however there's a couple of caveats:
if a component passed to that function is a class component and has some state, you won't be able to use any of it, in general it will probably cause a ton of issues that I'm not aware of
if a component passed is a function component, you can't use any hooks. It will just throw an error at you.
function render2elements(component, props) {
if (component.prototype.isReactComponent) {
return new component(props).render();
}
return component(props);
}
So if your "babies" are really simple this technically would work. But you just shouldn't refactor it the way you want and, again, ideally rethink this whole concept.

How do you use React.cloneElement through Fragments?

I'm using React.cloneElement to let a parent component control some of the props of it's children. A lot of the complexity of the ReactNode data structure is handled by the React helper methods React.Children.map and React.Children.toArray, like how they flatten Arrays for example, but they do NOT flatten Fragments. In my use case I would like to clone through the fragments so the props of any children in the Fragment get set as expected. My question is, is there a "built-in" way to do this, is it a reasonable thing to expect, and is my solution I'll show at the end OK if there isn't a better way?
For example:
<Parent>
<Child text="A" />
{ true && <Child text="B" /> }
{ false && <Child text="C" /> }
{ [
<Child text="D" />,
<Child text="E" />
]}
<>
<Child text="F" />
<Child text="G" />
</>
</Parent>
Here's how the helper methods behave in these situations:
React.Children.Map:
{ false && <Child text="C" /> } is converted to null
The array with D and E is flattened
The fragment with F and G in it is NOT flattened, you are just handed the Fragment element.
React.Children.toArray:
{ false && <Child text="C" /> } is removed (not included in the returned array)
The array with D and E is flattened
The fragment with F and G in it is NOT flattened, the Fragment element is included in the array.
This means when I do something simple like:
return (
<div>
{React.Children.map(props.children, c => {
if (!React.isValidElement(c)) return c;
else return React.cloneElement(c, {...c.props, isWhatever: true})
})}
</div>
);
The child elements inside of the Fragment will not get isWhatever set to true because the Fragment itself is returned and cloned and the child elements are not.
The solution I have come up with is to do something to recursively flatten Fragments like this:
function cloneThroughFragments(children: React.ReactNode) : React.ReactNode {
return React.Children.map(children, (c) => {
if (React.isValidElement(c) && (c as any).type === Symbol.for("react.fragment")) {
return cloneThroughFragments(c.props.children);
}
else {
if (!React.isValidElement(c)) return c;
return React.cloneElement(c, {...c.props, isWhatever: true});
}
});
}
Is there a better way to do this? Or a reason why I shouldn't do this? If not, is this the best way to match the Fragment?
NOTE: I learned the hard way that it is really important that the cloneElement calls are INSIDE of the React.Children.map callback. One version I tried build a flattened array, then looped over the flattened array and called cloneElement, but that resulted in key warnings from React! And you can't easily use toArray to do this because it will end up generating duplicate keys for the Fragment children. There are a lot of surprises and gotchas and "internal" React knowledge needed at this level, which is really why I'm asking this question.
Your solution is good, but you don't have to use magic Symbols, when you can directly compare the type (since 0.13):
function cloneThroughFragments(children: React.ReactNode) : React.ReactNode {
return React.Children.map(children, (c) => {
if (React.isValidElement(c)) {
if (c.type === React.Fragment) { // just compare to `React.Fragment`
return cloneThroughFragments(c.props.children);
}
return React.cloneElement(c, {...c.props, isWhatever: true});
}
return c;
});
}

Is render method of a component run in react-virtualized rowRenderer

I am using a List provided by react-virtualized. Inside the rowRenderer I return a more complex component. This component has some events defined for it. When an event is triggered, a part of the this component should be updated with new structure, e.g. a new input is rendered inside the row. This doesn't not seem to work when used inside the List.
<List height={height} width={1800} rowCount={this.state.leads.length} rowHeight={rowHeight} rowRenderer={this.rowRenderer} />
Here's the rowRenderer:
rowRenderer(props) {
let opacityvalue = 1
if (this.state.deleted.indexOf(props.index) >= 0) {
opacityvalue = 0.3
}
return (
<LeadItem {...props}
lead={this.state.leads[props.index]}
leadKey={props.index}
...
/>
)}
Here's the element that should show up when a specific event is triggered:
{self.props.lead.show ? <Selectize
queryfield={'tags'}
handleChange={(e) => this.props.updateLeadData(self.props.leadKey, 'tags', 'update', e)}
value={self.props.lead.tags}
create={false}
persist="false"
multiple
options={self.props.TAGS}
/> : <div>{taglist}</div>}
EDIT: Here's a simple example where I prove my point.
https://codesandbox.io/s/2o6v4my7pr
When user presses on the button, there must appear a new element inside the row.
UPDATE:
I see now that it's related to this:
https://github.com/bvaughn/react-virtualized#pure-components
I think you can achieve what you're trying to do by passing through this property to List as mentioned in the docs:
<List
{...otherListProps}
displayDiv={this.state.displayDiv}
/>

Dynamic composition reactjs

Is there a way of composition of dynamic component? For example, lets say I would a have a ListItems component that display its children in rows. But i want to reuse some of its behavior and I would like to to something like that:
<ListItems rowComponent="CarsItem" headerComponent="CarsHeader"/>
or
<ListItems rowComponent="BikesItem" headerComponent="BikesHeader"/>
How can I achieve such abstraction?
Somethin like that did not work
render:function(){
return (
<this.props.header/>
<this.props.body/>
)
}
If you can make sure you declare the class that's used for the children first, you can do something like this:
<ListItems rowComponent={BikesItem}/>
and then in your render method:
render() {
var Row = this.props.rowComponent;
return (
<div>
{this.props.items.map(function(item) {
return <Row item={item}/>;
})}
</div>
)
}
Hope this helps!

Resources