React - Composition VS Configuration - reactjs

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>
);
}

Related

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.

Backbone => React - Higher Order Components, inheritance and specialisation

I have a legacy Backbone app which I have begun to rewrite in React. The app has a main view containing two subviews, arranged vetically. The top panel displays some data, and the bottom one displays the result of some algorithm taking this data as input. Since I have many different data sources, each with a different algorithm applied to it, I have an abstract base View class, which I then subclass for each data source, adding, decorating and overriding methods as necessary. Somewhat like this:
// Base View.
const BaseView = Backbone.View.extend({
events: {},
initialize() {
this.subViewA = // instantiate subview...
this.subViewB = // instantiate subview...
},
generateResultData() {
// 'Abstract' method which should be specialised to generate data rendered by subViewB...
},
render() {
// render subviews...
},
});
// Derived View.
const Derived = BaseView.extend({
events: {
// event handlers...
},
add(a, b) {
return a+b;
},
// additional methods...
generateResultData() {
return {
result: this.add(2,2);
}
},
})
This results in a shallow hierarchy of many similar View classes. It's all terribly imperative, but it's a simple, intuitive and easy-to-reason-about pattern, and just works. I'm struggling to see how to achieve the same thing in React, however. Given that subclassing of subclasses of React.Component is considered an anti-pattern, my focus has naturally been on composition, and in particular Higher Order Components. HOCs (which I find beautiful, but unintuitive and often just downright confusing) seem to involve adding general features, rather than specialising/refining something more general. I have also considered passing in more specialised versions of Componenet methods through props. but that just means I have to use the same boilerplate Component definition over and over again:
// General functional component, renders the result of prop function 'foo'.
function GeneralComponent(props) {
const foo = this.props.foo || ()=>"foo";
return (
<div>
<span> { this.props.foo() } </span>
</div>
)
}
// Specialised component 1, overrides 'foo'.
class MySpecialisedComponent extends React.Component {
foo() {
return this.bar()
}
bar() {
return "bar"
}
render() {
return (
<GeneralComponent foo={this.foo} />
)
}
}
// Specialised component 2, overrides 'foo' and adds another method.
class MyOtherSpecialisedComponent extends React.Component {
foo() {
return this.bar() + this.bar()
}
bar() {
return "bar"
}
baz() {
return "baz"
}
render() {
return (
<GeneralComponent foo={this.foo} />
)
}
}
The above is a very simplistic case, obviously, but essentially captures what I need to do (though I would of course be manipulating state, which the example does not do, for simplicity). I mean, I could just do things like that. But I want to avoid having to repeat that boilerplate all over the place. So is there a simpler and more elegant way of doing this?
Generally, if a component is stateless and doesn't use lifecycle hooks, there are no reasons for it to be Component class. A class that acts as a namespace and doesn't hold state can be considered an antipattern in JavaScript.
In constrast to some other frameworks, React doesn't have templates that would need to map variables in order for them to be available in view, so the only place where bar function needs to be mentioned is the place where it's called. JSX is an extension over JavaScript, JSX expressions can use any names that are available in current scope. This allows to compose functions without any classes:
const getBar => "bar";
const getBaz => "baz";
const getBarBaz => getBar() + getBaz();
const MySpecialisedComponent = props => <GeneralComponent foo={getBar} />;
const MyOtherSpecialisedComponent = props => <GeneralComponent foo={getBarBaz} />;
An anonymous function could be passed as foo prop instead of creating getBarBaz but this is generally discouraged because of unnecessary overhead.
Also, default prop values could be assigned with defaultProps without creating new ()=>"foo" function on each component call:
function GeneralComponent({ foo }) {
return (
<div>
<span> {foo()} </span>
</div>
)
}
GeneralComponent.defaultProps = { foo: () => 'foo' };
IMO what is throwing you off isn't inheritance vs composition, it's your data flow:
For example, many of my derived views need to do custom rendering after the main render. I'm using a third-party SVG library, and the data rendered into the 'result' subview is derived from analysis of rendered SVG elements in the main data view above it
So what you're trying to do here is have a child update props of a distantly related component after render, correct? Like this?
// after the svg renders, parse it to get data
<div id="svg-container">
<svg data="foo" />
<svg data="bar />
</div>
// show parsed data from svg after you put it through your algos
<div id="result-container">
// data...
</div>
There's a lot of state management libraries out there that will help you with this problem, that is, generating data in one component and broadcasting it to a distantly related component. If you want to use a tool built-in to react to address this you may want to use context, which gives you a global store that you can provide to any component that wants to consume it.
In your example your child classes have data-specific methods (add, etc.). IMO it's more typical in react to have a generic class for displaying data and simply passing it down map functions as props in order to rearrange/transform the rendered data.
class AbstractDataMap extends PureComponent {
static defaultProps = {
data: [],
map: (obj, i) => (<div key={i}>{obj}</div>)
};
render() {
const { data, map, children } = this.props;
const mapped = data.map(map);
return (
<Fragment>
{mapped.map((obj, i) => (
children(obj, i)
))}
</Fragment>
);
}
}
// in some other container
class View extends Component {
render() {
return (
<div>
<AbstractDataMap data={[1, 2, 3]} map={(n) => ({ a: n, b: n + 1 })}>
{({ a, b }, i) => (<div key={i}>a: {a}, b: {b}</div>)}
</AbstractDataMap>
<AbstractDataMap data={[2, 4, 6]} map={(n) => (Math.pow(n, 2))}>
{(squared, i) => (<div key={i}>squared: {squared}</div>)}
</AbstractDataMap>
</div>
);
}
}
IMO this pattern of using an HOC to abstract away the labor of explicitly using .map in your render calls (among other uses) is the pattern you are looking for. However, as I stated above, the HOC pattern has nothing to do your main issue of shared data store across sibling components.
Answering my own question, which I've never donw before...
So my question really arose from a concern that I would need to refactor a large, imperative and stateful codebase so as to integrate with React’s composition-based model (also with Redux). But it occurred to me after reading the (very insightful and helpful) responses to my question that my app has two parallel parts: the UI, and an engine which runs the algorithms (actually it's a music analysis engine). And I can strip out the Backbone View layer to which the engine is connected quite easily. So, using React’s context API I've built an ‘AnalysisEngineProvider', which makes the engine available to subcomponents. The engine is all very imperative and classically object-oriented, and still uses Backbone models, but that makes no difference to the UI as the latter has no knowledge of its internals - which is how it should be (the models will likely be refactored out at some point too)...
The engine also has responsibility for rendering the SVG (not with BB views). But React doesn’t know anything about that. It just sees an empty div. I take a ref from the div and pass it to the engine so the latter knows where to render. Beyond that the engine and the UI have little contact - the divs are never updated from React state changes at all (other components of the UI are though, obviously). The models in the engine only ever trigger updates to the SVG, which React knows nothing about.
I am satisfied with this approach, at least for now - even if it's only part of an incremental refactor towards a fully React solution. It feels like the right design for the app whatever framework I happened to be using.

What is Reacts function for checking if a property applies?

Based off this Q&A:
React wrapper: React does not recognize the `staticContext` prop on a DOM element
The answer is not great for my scenario, I have a lot of props and really dislike copy-pasting with hopes whoever touches the code next updates both.
So, what I think might work is just re-purposing whatever function it is that React uses to check if a property fits to conditionally remove properties before submitting.
Something like this:
import { imaginaryIsDomAttributeFn } from "react"
...
render() {
const tooManyProps = this.props;
const justTheRightProps = {} as any;
Object.keys(tooManyProps).forEach((key) => {
if (imaginaryIsDomAttributeFn(key) === false) { return; }
justTheRightProps[key] = tooManyProps[key];
});
return <div {...justTheRightProps} />
}
I have found the DOMAttributes and HTMLAttributes in Reacts index.t.ts, and could potentially turn them into a massive array of strings to check the keys against, but... I'd rather have that as a last resort.
So, How does React do the check? And can I reuse their code for it?
The following isn't meant to be a complete answer, but something helpful for you in case I forget to come back to this post. The following code is working so far.
// reacts special properties
const SPECIAL_PROPS = [
"key",
"children",
"dangerouslySetInnerHTML",
];
// test if the property exists on a div in either given case, or lower case
// eg (onClick vs onclick)
const testDiv = document.createElement("div");
function isDomElementProp(propName: string) {
return (propName in testDiv) || (propName.toLowerCase() in testDiv) || SPECIAL_PROPS.includes(propName);
}
The React internal function to validate property names is located here: https://github.com/facebook/react/blob/master/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js
The main thing it checks the properties against is a "possibleStandardNames" property-list here: https://github.com/facebook/react/blob/master/packages/react-dom/src/shared/possibleStandardNames.js
So to reuse their code, you can copy the property-list in possibleStandardNames.js into your project, then use it to filter out properties that aren't listed there.

React JS: Loop through a multi-dimensional array and determine/load the correct component

Using React, I'm fetching data from an API, a sample of which can be seen here.
I need to loop through the body section (multi-dimensional array) and then determine what type of 'block' it is:
heading
text
quote
frameImgeBlock
quoteImageBlock
frameQuoteBlock
Depending which block type it is then I need to create/load the corresponding React component (as I'm imagining it's better to separate these into individual React components).
How is it best to approach this in React/JS?
I may be able to tweak the API output a little if someone can suggest an easier approach there.
You could use a "translation" function like so, which would call external components:
// Import external deps
import Heading from './src/heading';
// Later in your component code
function firstKey(obj) {
return Object.keys(obj)[0];
}
getDomItem(item) {
const key = firstKey(item);
const val = item[val];
switch (key) {
case "heading":
return <Heading heading={ val.heading } ...etc... />
case "other key..."
return <OtherElm ...props... />
}
}
render() {
// data is your object
return data.content.body.map(item =>
this.getDomItem(item[firstKey(item)])
);
}

Why ref='string' is "legacy"?

In the React documentation they say:
React also supports using a string (instead of a callback) as a ref prop on any component, although this approach is mostly legacy at this point.
https://facebook.github.io/react/docs/more-about-refs.html
Take the following example:
class Foo extends Component {
render() {
return <input onClick={() => this.action()} ref={input => (this._input = input)} />;
}
action() {
console.log(this._input.value);
}
}
Why should I prefer this, instead of:
class Foo extends Component {
render() {
return <input onClick={() => this.action()} ref='input' />;
}
action() {
console.log(this.refs.input.value);
}
}
?
It seems just much more clean and easier the second example.
Are there risks that the string method will be deprecated?
NB: I'm looking for the "official" answer to the statement in the documentation, I'm not asking about personal preferences and so on.
While perhaps more simple, the old refs API can get difficult in some edge cases, like when used in a callback. All kind of static analysis is a pain with strings, too. The callback based API can do everything the string API can do and more with just a little added verbosity.
class Repeat extends React.Component {
render() {
return <ul> {
[...Array(+this.props.times)].map((_, i) => {
return <li key={i}> { this.props.template(i) } </li>
})
} </ul>
}
}
class Hello extends React.Component {
constructor() {
super();
this.refDict = {};
}
render() {
return <Repeat times="3" template={i => <span ref= {el => this.refDict[i] = el}> Hello {i} </span>} />
{/* ^^^ Try doing this with the string API */}
}
}
Further discussion and a bit more comprehensive list of the possible issues with the string based api can be found from issue #1373, where the callback based api was introduced. I'll include here a list from the issue description:
The ref API is broken is several aspects.
You have to refer to this.refs['myname'] as strings to be Closure Compiler Advanced Mode compatible.
It doesn't allow the notion of multiple owners of a single instance.
Magical dynamic strings potentially break optimizations in VMs.
It needs to be always consistent, because it's synchronously resolved. This means that asynchronous batching of rendering introduces potential bugs.
We currently have a hook to get sibling refs so that you can have one component refer to it's sibling as a context reference. This only works one level. This breaks the ability to wrap one of those in an encapsulation.
It can't be statically typed. You have to cast it at any use in languages like TypeScript.
There's no way to attach the ref to the correct "owner" in a callback invoked by a child. <Child renderer={index => <div ref="test">{index}</div>} /> -- this ref will be attached where the callback is issued, not in the current owner.
The docs call the old string API "legacy" to make it clearer that the callback-based API is the preferred approach, as is discussed in this commit and in this PR which are the ones that actually put those statements to the documentation in the first place. Also note that a few of the comments imply that the string based refs api might be deprecated at some point.
Originally posted by danabramov on https://news.ycombinator.com/edit?id=12093234
String refs are not composable. A wrapping component can’t “snoop” on a ref to a child if it already has an existing string ref. On the other hand, callback refs don’t have a single owner, so you can always compose them.
String refs don’t work with static analysis like Flow. Flow can’t guess the magic that framework does to make the string ref “appear” on this.refs, as well as its type (which could be different). Callback refs are friendlier to static analysis.
The owner for a string ref is determined by the currently executing component. This means that with a common “render callback” pattern (e.g. <DataTable renderRow={this.renderRow} />), the wrong component will own the ref (it will end up on DataTable instead of your component defining renderRow).
String refs force React to keep track of currently executing component. This is problematic because it makes react module stateful, and thus causes weird errors when react module is duplicated in the bundle.

Resources