I'm trying to dynamically change an element name for reuse of a function.
static renderDetails(props, parentTableElementOpen, parentTableElementClose, ) {
let coverageRows;
if (props.length !== 0) {
return (
<span>
{parentTableElementOpen}
{props.map((columnData, index) => {
if (index === props.length - 1) {
coverageRows = (<TableRowCol classNames={styles.text_align_right}>{columnData}</TableRowCol>);
}
else {
coverageRows = (<TableRowCol>{columnData}</TableRowCol>);
}
return coverageRows;
})}
{parentTableElementClose}
</span>
);
}
return null;
}
The call to this function is below.
Utils.renderDetails(this.props.columnData, '<TableRow>', '</TableRow>');
The parentTableElementOpen and parentTableElementClose will have the names of the elements I'm after.
The rendered page doesn't seem to recognize them and instead of a <TableRow> </TableRow> element type it renders just text <TableRow> </TableRow>
Maybe a bit tricky or overly complicated what I'm trying to do here but thought it could be a good refactor between 2 identical functions.
There might be a solution that actually could work in the way you described but I think you're thinking of this using an HTML mindset. Keep in mind that with React you're rendering a Component which is not an HTML Tag/XML even though it shares similarities with the syntax.
In your case you're passing a string so it is rendering a string.
I think what you want is a generic component that renders the children, not a function that tries to pick a component. Maybe something like this:
class MyTableRow extends React.Component {
render() {
return ( //do whatever customization you want here.
<TableRowCol>
{this.props.children} //renders what's "inside" the tag. You can pass this or specify it in the JSX
</TableRowCol>
)
}
}
If you think about what you're doing in that utility call you're actually specifying the tag you want to use and then just passing props which in the world of React is identical to:
//Some render function
...
<MyUtilityObject props={props} />
...
My first instinct would be to "invert" the design to use components as that seems to be how React is designed.
EDIT
I didn't realize that element.props.children was readonly so the idea below isn't going to work.
My suggestion in this case would be as above. In general this abstract method really isn't doing anything and could be refactored into a custom component so instead of a function call you use the component
<MyTable>
{ row.map( (row, index) => {
switch(row.type) {
case 'data1':
return <MyCustomRow row={row} key={index} />
case 'data2':
return <MyCustomRow2 row={row} key={index} />
default:
return null
}
})
</MyTable>
NOPE
Now that being said, if you wanted to maintain this signature and you have a good reason what you probably want to do is this:
static renderDetails(props, parentElement) {
if(props.length === 0) {
return null; //nothing to see here!
}
let coverageRows;
let children = props.map((columnData, index) => {
if (index === props.length - 1) {
coverageRows = (<TableRowCol classNames={styles.text_align_right}>{columnData}</TableRowCol>);
}
else {
coverageRows = (<TableRowCol>{columnData}</TableRowCol>);
}
return coverageRows;
})
parentElement.children = children
return <span>parentElment</span> //I'm not sure why you need the span, but if you do great. I would just return the parentElement
}
//Called by...
render() {
...
renderDetails(props, <TableRow />)//an actual table row instance, not the tag name as a string
...
}
I didn't test any of this but it should get you moving in the right direction. I would recommend writing a custom component that renders children so you understand how that works. It will save you a lot of time down the road.
Related
I am currently working on a project that requires dynamically injecting one component into another.
My project is using Redux, so I came up with two possible solutions which both have their advantages and disadvantages, but I don't know which one to choose. I know that by nature, React encourages composition, but I'm still curious to know if the second approach (simpler and faster to use) is still good :
export const SlideOverComponents = {
'UserCreate': UserCreate,
'UserUpdate': UserUpdate,
};
The idea is to register all components that can be injected as a key value pair, and dispatch a Redux action with the key and the props required by this component.
{(!!componentKey && !!SlideOverComponents[componentKey]) && React.createElement(SlideOverComponents[componentKey], props)}
Then in my parent container, I just read this key and use the React.createElement to display the injected one.
This solution is working fine and is easy and fast to use because I just have to register any new component to the object to make it work.
Is this approach "ok" ? Or should I use composition ?
(I'm asking from a "good practice" or "anti-pattern" point of view.)
Yes that's fine, as long as the interface between all of the SlideOverComponents are completely identical. Your code is more verbose than it needs to be. You don't need createElement either if you assign it to a variable first
const Component = SlideOverComponents[componentKey]
return (
<div>
{Component && <Component {...props} />}
</div>
)
Edit:
I noticed that you are using TypeScript from other answers. Considering that, I still think you can use Composition but with types using String Literal Types like this:
type SlideOverComponentsType = "update" | "create";
type SlideOverComponentsProps = UserUpdateProps | UserCreateProps;
type SlideOverProps = {
key: SlideOverComponentsType;
} & SlideOverComponentsProps;
function SlideOver({ key, ...props }: SlideOverProps) {
switch (key) {
case "update":
return <UserUpdate {...props} />;
case "create":
return <UserCreate {...props} />;
default:
return null; // this will never happen but need to be addressed
}
}
And with an approach like that, you don't need an "Object" to store all the possible types of SlideOverComponents. You also guarantee that the props will always be using the proper interface and if eventually, you pass it wrongly TS will warn you about that.
Again: consider using types instead of declaring "options" as objects for cases like this.
Hope that this could help you or give you some good ideas!
Original Answer:
You can still use Composition for this and create some kind of check or `switch` statement inside the "Generic" Component. That way you could avoid adding so many checks(`if`s) outside of the parent component and guarantee that eventually non-existing `keys` could fallback to a default behavior or even to an error.
There are several ways of implementing it but one using switch that I like is this one:
function UserInteraction({ key, ...props }) {
switch (key) {
case "create": {
return <UserCreate {...props} />;
}
case "update": {
return <UserUpdate {...props} />;
}
default: {
return null;
// or you could thrown an error with something like
throw new Error(`Error: key ${key} not present inside component User`);
}
}
}
You could also use the Object.keys() method to accomplish almost the same behavior:
const UserInteractionOptions = {
"create": UserCreate,
"update": UserUpdate,
}
function UserInteraction({ key, ...props }) {
if (!Object.keys(UserInteractionOptions).includes(key)) {
return null;
// or you could thrown an error with something like
throw new Error(`Error: key ${key} not present inside component User`);
}
const InteractionComponent = UserInteractionOptions[key];
return <InteractionComponent {...props} />;
}
The main idea is to isolate the logic from deciding which component to render (and if it can be rendered) inside that component.
For future reading, you could check on TypeScript and how this can be easily handled by types, coercion, and the checks for non-present keys could be made before even the code runs locally.
A little of nitpicking: you are not "injecting" a Component inside another Component. You are just passing a key to deciding if the Parent Component renders or not the Child component through a flag. The injection of one Component into another involves passing the full component as a prop and just rendering it (or customizing it, eventually).
You could look at how React decides to render the children prop and how it decides if it is null, a string, or a ReactComponent to render an actual component. Also, a good topic to research is Dependency Injection.
As a simple example, injecting a component could looks like this:
function Label({ text }) {
return <p>{text}</p>;
}
function Input({ Label, ...props }) {
return (
<div>
<Label />
<input {...props} />
</div>
);
}
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.
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;
});
}
I am trying to build a simple dynamically updated, interactive list that styles each <li></li> according to the css rules of a .clicked class, when you click on them.
The app is composed of two components, a parent and a child and the code in question is the following (taken from the child):
handleClick(e) {
document.getElementById(e.currentTarget.id).setAttribute("class","clicked");
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x,i)=>{ return (<li id={i} key={i} className={i%2==0 ? "white" : "grey"}
onClick={this.handleClick}>{x}</li>); })
return (
<div>
<ul id="ul">{ pro }</ul>
</div>
What is happening here is basically that the parent is passing to the child a sentences prop (an array of sentences that will form the basis for the formation of a dynamic list).
The controversial part is me using DOM manipulation in the form of document.getElementById(e.currentTarget.id).setAttribute("class","two");
in order to change the class of the dynamically created html from jsx.
The code above works, however it does not feel as best practice. The whole advantage in using react is to use virtual dom and optimize the way the DOM is updated.
My questions are the following:
1) Am I right to feel this way? (that my solution is not best practice?)
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
If you know this question to be a duplicate, please leave a comment and I ll remove it.
1) Am I right to feel this way? (that my solution is not best practice?)
It is correct to assume that this is not an ideal approach, manipulating the DOM via vanilla js in React has its place (Example Use Cases) but should not be done unless absolutely necessary. Also, it is not ideal to use the index from Array.prototype.map as the key on your components as if they change order it can cause confusion for React as the keys would map differently in that case.
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
You should make use of the component state. If you want each clicked element to maintain the clicked class then make a piece of state that caches the elements that have already recieved the clicked class. if only the most recently clicked element gets the clicked class then simply cache an identifier to the appropriate element in the state. You could also use refs for this purpose though the overusage of them is somewhat discouraged by facebook.
Here is a quick snipped that will toggle the click class on each <li>
class Test extends Component {
constructor() {
super();
this.state = {
clicked: {}
};
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x, i) => {
const color_class = i % 2 === 0 ? "white" : "grey";
const clicked_class = this.state.clicked[i] === true ? "clicked" : "";
let clicked = Object.assign({}, this.state.clicked); // Dont mutate state!!!
return (
<li
id={i}
key={i}
className={`${color_class} ${clicked_class}`}
onClick={e => {
if (clicked.hasOwnProperty(i)) {
delete clicked[i];
} else {
clicked[i] = true;
}
this.setState({ clicked });
}}
>
{x}
</li>
);
});
return (
<div>
<ul id="ul">
{pro}
</ul>
</div>
);
}
}
My app has a feature toggle functionality that tells my UI whether or not a piece of UI should be rendered. I would like to create a Higher Order Component to conditionally render these types of components. In one scenario, I'm trying to conditionally render a list, but I'm running into this error:
ConditionalRender(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
This makes sense since I just am trying to render the children of this component. Here's what I've got so far:
https://jsfiddle.net/fmpeyton/cykmyabL/
var settings = { showHello: true, showGoodbye: false};
function ConditionalRender (props) {
var output = null;
if(props.shouldRender) output = props.children;
console.log(output);
// return (<li>{output}</li>); // This works, but isn't desired html structure
return ({output});
}
function App (props) {
return (
<ul>
<ConditionalRender shouldRender={props.settings.showHello}>
<li>Hello!</li>
</ConditionalRender>
<ConditionalRender shouldRender={props.settings.showGoodbye}>
<li>Goodbye...</li>
</ConditionalRender>
</ul>
);
}
ReactDOM.render(
<App settings={settings} />,
document.getElementById('container')
);
If I can help it, I would just like to render the children without any additional logic.This HOC would also handle more complex children down the line. Something like this:
<ConditionalRender shouldRender={props.settings.showHello}>
<div>
<p> blah blah blah</p>
<table>
<!-- ... -->
</table>
</div>
</ConditionalRender>
Any ideas?
Try this:
function App(props) {
return (
<ul>
{props.settings.showHello && <li>Hello!</li>}
{props.settings.showGoodbye && <li>Goodbye...</li>}
</ul>
);
}
P.S. Your code doesn't work because of this line:
return ({output});
Assuming you have es2015 support, it would be treated as object property shorthand. So it's the same as:
return {output: output};
which is not what React expects to get from the render method.
You could try this:
function ConditionalRender(props) {
if (props.shouldRender) {
// Make sure we have only a single child
return React.Children.only(props.children);
} else {
return null;
}
}
But this is not the simplest way. See more here.
P.P.S. Your ConditionalRender component is not what is called Higher-Order Component. According to the docs, HOC is a function that takes a component and returns a new component.