How to prevent HOC from rendering if wrapped component renders nothing - reactjs

I have components that can return null in render based on their props. Props are not the same between components. Now I want all of my components to render a fancy divider after them. I decide to use one shared HOC for this (e.g. by importing said HOC in my components and exporting the new wrapped component).
However, now I end up in a situation where if my actual components render nothing I still get the divider rendered from the HOC and my page only contains lonely dividers. In this case I would want the HOC to render nothing. Can I solve this in the HOC? I could check different props before rendering the HOC+wrapped component but that would have to be done everywhere. If not HOC, is there some other way of solving this?
import React from 'react'
import ReactDOM from 'react-dom'
const withDivider = (Component) => {
return ({ fancyDividerProp, ...props }) => (
<>
<Component {...props} />
<hr some-fancy-prop={fancyDividerProp} />
</>
)
}
const SpicyComponent = ({isSpicy}) => {
if (!isSpicy) return null
return (
<p>Spicy!</p>
)
}
const SpicyWithDivider = withDivider(SpicyComponent)
const SweetAndSourComponent = ({isSweet, isSour}) => {
if (!isSweet || !isSour) return null
return (
<p>Sweet&Sour!</p>
)
}
const SweetAndSourWithDivider = withDivider(SweetAndSourComponent)
class App extends React.Component {
render() {
return (
<div>
<SpicyWithDivider isSpicy={false} />
<SweetAndSourWithDivider isSweet={true} isSour={false} />
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
)

Related

Map through two arrays of components and strings and render in one component

I have two arrays that I want to map through:
const social = ["Snapchat", "TikTok", "Dribbble", "Discord", "Facebook"];
const socialIcons = [<SnapchatIcon />, <DribbbleIcon />];
The socialIcons array are all components
How can I send both values as props into my DummyRectangle component? Here is my current code:
{social.map((s, index) => (
<div className="dummy_buttonsWrapper">
<DummRectangle social={s} socialIcons={i} />
</div>
))}
And here is DummyRectangle component:
function DummRectangle({ social, socialIcons }) {
// console.log("---->", socialIcons);
return (
<div>
<p>{social}</p>
{<socialIcon/>} // render social icon component
</div>
);
}
To do so, you don't need to wrap tags around your socialIcon in your DummRectangle. Also, it doesn't seem that you are passing the socialIcon component at all. If I were you, I would do something like this:
The following two are the components as an example that you would like to render (in your case - socialIcons)
// Comp1.js
import React from "react";
const Comp1 = () => <div>actual Comp1</div>;
export default Comp1;
// Comp2.js
import React from "react";
const Comp2 = () => <div>actual Comp2</div>;
export default Comp2;
Now, in your main Parent component, you would simply get the current component of the componentName (in your case - social) by accessing your component's array with an index. Then, you would pass this currentComponent as props to your Child component where you want to render it.
// App.js
import React from "react";
import Comp1 from "./Comp1";
import Comp2 from "./Comp2";
import DummyComponent from "./DummyComponent";
export default function App() {
const componentNames = ["Comp1", "Comp2"];
const components = [<Comp1 />, <Comp2 />];
return (
<div className="App">
{componentNames.map((name, index) => {
const currentComponent = components[index];
return (
<div>
<DummyComponent componentName={name} component={currentComponent} />
</div>
);
})}
</div>
);
}
In your Child component, you can simply render it by enclosing it into the brackets - no need to add tags. React will do all the rendering for you. In your case it would be { socialIcon }
// DummyComponent.js
import React from "react";
const DummyComponent = ({ componentName, component }) => {
return (
<div>
<p>{componentName}</p>
{component}
</div>
);
};
export default DummyComponent;
Link to Codesandbox with the above code for reference: click here

How to add {props.children} to a React component

i have many components which have {props.children} deeply nested inside.
considery DRY principle is there a way to add this using some React pattern.
example
let's say i have two components,
Comp1.js
import React from "react";
const Comp1 = props => {
return (
<div>
<h1>{props.children}</h1>
</div>
);
};
export default Comp1;
Comp2.js
import React from "react";
const Comp2 = props => {
return (
<div>
<div>
<h1>{props.children}</h1>
</div>
</div>
);
};
export default Comp2;
if you see above code we have both Comp1 and Comp2 have line of code {props.children} repeated inside.
what i want now is some function which will add this line of code, something like below,
const addPropsChildrenToComp = (Comp)=>{
return(
(props)=>{
///do somehting here
}
)
}
const Comp1 = props => {
return (
<div>
<h1></h1>
</div>
);
};
Comp1WithPropsChildren = addPropsChildrenToComp(Comp1)
using HOC doesn't work because, in HOC we never modify passed component.
anyway to aceve this.?
to get more idea of my problem see this demo: https://codesandbox.io/s/trusting-http-pd1yu
in there i woul like to see CompWithPropsChildren component render props.children inside it.
I think I see what you're trying to get to, and you can accomplish this just using another component.
import React from "react";
import ChildComp from "./ChildComp";
const Comp1 = props => {
return (
<div>
<ChildComp {...props} />
</div>
);
};
export default Comp1;
import React from "react";
const ChildComp = props => {
return <h1>{props.children}</h1>
}
Assuming your ChildComp has some complex logic you don't want to duplicate, this will make it reusable for you.

React router 'to' through props in const

I suspect this is more about React (or JS) than routing.
To avoid props collision, I am using forwardRef, defined in a const. The 'to' I want to set through props passed from parent component, and I cannot get this work. In the example below, where it says to={props.toUrl} it only works if it is to='/hello'. How do I do this, so I can use the ButtonRouter component from the Demo component?
import React from "react";
import { MemoryRouter as Router } from "react-router";
import { Link } from "react-router-dom";
import Button from "#material-ui/core/Button";
const CollisionLink = React.forwardRef((props, ref) => (
<Link innerRef={ref} to={props.toUrl} {...props} />
));
function ButtonRouter(props) {
return (
<Router>
<Button component={CollisionLink}>Routing w/o props collision</Button>
</Router>
);
}
export default function Demo() {
return <ButtonRouter toUrl="/hello" />;
}
I think you might be missing props from ButtonRouter:
function ButtonRouter(props) {
return (
<Router>
<Button component={(routerProps) => <CollisionLink {...routerProps} {...props} />}>Routing w/o props collision</Button>
</Router>
);
}

React: Is it bad practice to import a child component directly rather than pass in as a dependency?

I may be over thinking this, but I am curious if importing a child component directly is bad practice with regards to coupling and testing.
Below is a simple example:
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
<Header></Header>
<div>{this.props.importantContent}</div>
</div>
)
}
}
To me it looks like there is now coupling between Widget and Header. With regards to testing, I don't see an easy way to mock the Header component when testing the Widget component.
How do other larger React apps handle cases like this? Should I pass Header in as a prop? If using react-redux, I can inject header with the Connect method like below to reduce boilerplate. Is that sound?
import { connect } from 'react-redux';
import Header from './header.jsx';
class Widget extends React.Component {
render() {
return (
<div>
{this.props.header}
<div>{this.props.importantContent}</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
header: Header
}
}
export default connect(mapStateToProps)(Widget)
I am interested is simple doing what the community is generally doing. I see that one solution is doing shallow rendering to test on the main part of the component and not the child components using something like Enzyme.
Thoughts or other ideas?
Passing elements / components as props is a good idea. Having default props is a good idea too:
const Widget = ({
header = <div>Default Header.. </div>,
content = <div>Default Content.. </div>
}) =>
<div>
{header}
{content}
</div>
Then elsewhere in your app:
<Widget header={<Header title="Foo" />} content="content from props" />
No need to inject using connect
You can also pass a component, not just an element if you want to interact with props / send data back to parent:
const Widget = ({
Header = props => <div>Default Header.. </div>,
Content = props => <div>Default Content.. </div>
}) =>
<div>
<Header />
<Content />
</div>
Elsewhere:
<Widget Header={Header} Content={props => <Content />} />
As long as the component always renders the same thing it can be directly rendered as a child rather than the parent.
If all other portions of the Component remain constant and only the Header can be different across pages then you could actually implement it as an HOC instead of passing it as a props
const MyCompFactory = ({CustomHeader = DefaultHeader}) => {
return class Widget extends React.Component {
render() {
return (
<div>
<CustomHeader/>
<div>{this.props.importantContent}</div>
</div>
)
}
}
}
and use it like
const CustomComponent = MyCompFactory({CustomComponent: Header})
as long as testing is concerned in your case, you could just shallow render your component and then Search if the Header component is rendered something like
import Header from 'path/to/header'
const component = shallow(
<Widget {...customProps}/>
)
test('test' , () => {
expect(component.find(Header).exists()).toBe(true)
})

How to pass a function from outside of main component using react-redux's Provider

I have a main document:
<script>
function fnc (param) {
alert(param);
}
ReactDOM.render(
<Provider store={store}>
<App fnc={fnc}/>
</Provider>,
document.getElementById('root')
);
</script>
App component has has children and grandchildren. Is there a better way to pass fnc to grandchildren (and perhaps get it via connect, similar to how reducers are mapped to props) so it can be called from there than passing it as a prop through all nested components at all levels (App -> children -> grandchildren)?
React's context feature can be used for this sort of thing. In fact, it's what react-redux uses to get the store from the Provider to the connect function.
When dealing with context, it is usually best to, similarly to react-redux, use a provider component to add it, and a higher-order component (HOC) to access it.
Provider:
import React from 'react';
import propTypes from 'prop-types';
class FunctionProvider extends React.Component {
getChildContext () {
return {
fnc: this.props.fnc
};
}
render () {
return React.Children.only(this.props.children);
}
}
FunctionProvider.childContextTypes = {
fnc: propTypes.func
};
HOC:
import React from 'react';
import propTypes from 'prop-types';
function withFnc(WrappedComponent) {
class WithFnc extend React.Component {
render() {
return < WrappedComponent fnc={this.context.fnc} {...props} />
}
WithFnc.contextTypes = = {
fnc: propTypes.func
};
return WithFnc
}
Add FncProvider:
function fnc (param) {
alert(param);
}
ReactDOM.render(
<Provider store={store}>
<FncProvider fnc={fnc}>
<App />
</FncProvider>
</Provider>,
document.getElementById('root')
);
Wrap Component:
import React from 'react';
class MyComponent extends React.Component {
render() {
return <button onClick={() => this.props.fnc('test')}>Click Me!</button>
}
}
export connect(mapStateToProps)(withFnc(MyComponent)
This might be a bit over the top though if you only need to pass it one or two layers deep and to a handful of components. Sometimes props are a perfectly fine solution.
If you particularly wanted to use redux to solve this, you could create a middleware that calls fnc on a specific action (I'll use ES6 syntax for this because it's way cleaner, but let me know if you need it to be browser compatible and I'll add that too):
const fncMiddleware = (fnc) => (store) => (next) => (action) => {
if (action.type === 'CALL_FNC') {
fnc(action.param);
}
return next(action);
}
The include the middleware when you create the store:
function fnc (param) {
alert(param);
}
var store = createStore(reducer, applyMiddleware(fncMiddleware(fnc)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Now you can dispatch an action like { type: 'CALL_FNC', param='test' } from you connected components like any other action.
Redux's connect is simply a Higher Order Function that enhances your component by injecting redux's state into props (mapStateToProps is one example of it).
You can use the very same approach to pass your func only to specific components as you wish.
const withCustomFunctionHOC = WrappedComponent => {
const yourCustomFunction = () => alert('it works!')
return (props) =>
<WrappedComponent
{...props}
yourCustomFunction={yourCustomFunction}
/>
}
const ChildComponent = ({ a, b, yourCustomFunction }) =>
<div style={{ marginTop: 20, marginBottom: 20 }}>
ChildComponent
<div>Prop a: {a}</div>
<div>Prop b: {b}</div>
{yourCustomFunction
? <button onClick={yourCustomFunction}>I have customFunction (click me)</button>
: <div>I don't have customFunction</div>
}
</div>
const EnhancedChildComponent = withCustomFunctionHOC(ChildComponent)
const App = () =>
<div>
<ChildComponent a={1} b={2} />
<EnhancedChildComponent a={3} b={4} />
</div>
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Resources