Render Props vs HOC? - javascript

I'm trying to learn React from scratch and having a deep knowledge of concepts !
Today I was searching about HOC, Render Props and the differences between the two. I've checked render times for both. I wrote a console.log('rendered') into render to check render times in browser console.
HOC: When I used HOC to write an usable component, I saw after each changes on props I've render for HOC and component that used HOC.
Render Prop: In this case I've changed the props, but only wrapper component has rendered. because with render props we load only one component and inject codes to use that component feature !
So, Is it a benefit to use Render Props instead HOC components? Or HOC components are usable and powerful yet?
Thanks

HOC, Render Props and now hooks all serve to the same purpose: Share stateful logic between components. There is actually no way to tell which one is better or worst. All depends on your use case.
High Order Components are composable. It's easy to nest them
const withProps = (Component) => connect(mapState, mapDispatch)(<Component foo='bar' />)
Children as a function is a bad pattern for composability, nesting looks a lot like a callback hell cause they need to be executed inside an jsx block
const Component = () =>{
return(
<Consumer>
{
props =>(
<ThemeConsumer>
{
theme => <Child {...props} {...theme} />
}
</ThemeConsumer>
)
}
</Consumer>
)
}
On the other hand, render props it's easy to set up, have less boilerplate and in most cases are easier to reason about.
Hooks bring the best of both worlds
hooks are composable, can be easily nested, and are simple to reason about cause after all they're just plain old functions
const useConfig = () =>{
const customProps = useCustomProps()
const theme = useContext(ThemeContext)
return [customProps, theme]
}
const Component = () =>{
const [props, theme] = useConfig()
}
But again: There is no such thing as the best pattern. It's just a matter of where are you going to use it.

Related

Best way to consolidate state dependent boiler plate in React Functional component

I'm trying to understand if there is a better design pattern to this use case.
I have a react functional component such as this.
const ComponentMajor = (props) => {
[state, setState]
classes = useStyles()
//image a whole bunch of other state.
return (
<div stateDependentProp1 stateDependentProp2>{stateDependent3}</div>
<div stateDependentProp1 stateDependentProp2>{stateDependent3}</div>
<div stateDependentProp1 stateDependentProp2>{stateDependent3}</div>)
//imagine each of these is maybe 50 lines of code and we need 10 of them.
//perfect use case for code re use.
}
So naturally I create a div component, but since it's so coupled with ComponentMajor and only needs to be used in ComponentMajor to eliminate code dupl, i want it to live inside the same file.
Option 1
const ComponentMajor = (props) => {
[state, setState]
classes = useStyles()
const boilerPlateComp = (props) => <div props>{stateDependent3}</div>
//image a whole bunch of other state.
return(
<boilerPlateComp props/>
<boilerPlateComp props/>
<boilerPlateComp props/>)
}
This isn't good because every time ComponentMajor re-renders it redeclares boilerPlateComp and has weird behaviors.
Option 2
declare boilerPlateComp OUTSIDE ComponentMajor - but this makes it inefficient to share scope (which if defined inside ComponentMajor is not an issue) between them. It results in some annoying boiler plate ironically to pass the scope dependencies in as props.
The cleanest I was able to come up with was something like this, but there HAS to be a better way to do this in FUNCTIONAL components.
const boilerPlateComp = ({sharedScope, props}) => {
let {stateDep1, stateDep2, stateDep3, etc} = sharedScope
return (<div props>{stateDependent3}</div>)
}
const ComponentMajor = (props) => {
[state, setState]
classes = useStyles()
let sharedScope = {state, setState, classes, etc}
//image a whole bunch of other state.
return(
<boilerPlateComp props sharedScope={sharedScope}/>
<boilerPlateComp props sharedScope={sharedScope}/>
<boilerPlateComp props sharedScope={sharedScope}/>)
}
My Question Is:
What is the standard design pattern for this common use case? Is this a scenario where a class component would make more sense?
I tried playing around with memoizing the components declared WITHIN ComponentMajor but didn't seem to work.
Is there a clean way to use javascript apply to pass the scope onto the component instances?
Your second option is the correct one. Fundamentally in React, when you need some state to live above a component, that state has to be "passed-down" to the child component (this is sometimes called "prop drilling", especially if it has to be passed through other components). In other words, you do need that sharedScope prop:
<boilerPlateComp props sharedScope={sharedScope}/>
However, you could use a map to not have to repeat lines like that:
{someArray.map(() => <boilerPlateComp props sharedScope={sharedScope}/>}
And if you really didn't want to pass props you could use Context instead ... although using it to avoid a single component's worth of "prop drilling" would be more of an anti-pattern.

Usage of React HOC for Functional Components

I was referring to the below link (section : HOCs for Functional Components)
https://rossbulat.medium.com/how-to-use-react-higher-order-components-c0be6821eb6c
In the example, below is the code for the HOC;
//functional HOC with useState hook
import React, { useState } from 'react';
function withCountState(Wrapped) {
return function (props) {
const [count, setCount] = useState(0);
props['count'] = count;
props['setCount'] = setCount;
return <Wrapped {...props} />;
}
}
Also, the Wrapped component code is as below;
const Wrapped = (props) => {
const {count, setCount} = props;
return(
<div>
<h1>Counter Functional Component</h1>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Increment count
</button>
</div>);
};
For applying HOC to , we use
const EnhancedWrapped = withCountState(Wrapped);
Now I have 2 questions;
For consuming this component, do we just say <EnhancedWrapped> may be in our App.js or do we need anything else?
What benefit do we really get out of creating this HOC?
Viet has answered your questions. HOC is a way to make your components re-usable through composition. You can have other components which get wrapped by the HOC and now they would have access to the count and setCount functionality.
Depending upon what you are trying to accomplish, it's also a good idea to consider the pitfalls of HOC and consider alternate patterns such as :
Render Props: React Docs on Render Props
Using Custom Hooks over HOC Article on custom hooks
When using React Hooks, I'd personally prefer making custom hooks over using HOCs. And depending upon the use case, you may want to check out if React Context would make sense if multiple components are going to need a shared state.
For consuming this component, do we just say may be in our App.js or do we need anything else? Yes, just use HOC like any other JSX component.
What benefit do we really get out of creating this HOC? You can make it reusable. Let's say you want another component with different content inside, like , you could just create a new component by const AnotherEnhancedWrapped = withCountState(AnotherWrapped);

Pass data to one of the same components

<Comp1 />
<div>
<Comp1 />
<Comp2 />
</div>
I am new to React. I want to pass data from Comp2 to its sibling Comp1 only. I know using a parent component to pass props but in this case I have to rewrite Comp1 to get state from its parent, which will affect all the Comp1. How can I make only chosen Comp1 receive the data and don't bother the else?
There is not a straightforward solution to this, but you do have a couple of options:
Option 1
The most direct way would be as you described - having Comp2 pass data up to its parent using an event listener, then having the parent pass it back down to Comp1. This can be an optional prop being passed to Comp1, so it doesn't matter that your outer Comp1 won't receive that prop.
For example:
import React from 'react';
const Comp1 = ({data='Default Value'}) => (
<p>{data}</p>
)
const Comp2 = ({onData}) => (
<button onClick={e => onData(Math.random())}>Change Value</button>
)
export default function App() {
let [data, setData] = React.useState(null);
return (
<div>
<Comp1/>
<div>
<Comp1 data={data}/>
<Comp2 onData={setData}/>
</div>
</div>
);
}
This is probably your best option, and by the sound of things, it might be good to find a way to refactor your app so that this option becomes more viable. There's usually a way to change your app structure to make this work better.
If you really want siblings to have a more direct line of communication with each other, you could give Comp1 a ref of Comp2, but I wouldn't encourage this.
Option 2
Another option would be to use contexts. This gives anyone the power to communicate with anyone who uses the same context. There is a lot of power in this feature. Some people set up a Redux-like system using contexts and reducers to let any part of the application (or larger component they put the context provider in) communicate with any other part. See this article for more information on using contexts to manage application state.
import React from 'react';
let context = React.createContext()
const Comp1 = () => {
let ctx = React.useContext(context) || {};
return <p>{ctx.data || 'Default Value'}</p>
}
const Comp2 = () => {
let ctx = React.useContext(context);
return <button onClick={e => ctx.setData(Math.random())}>Change Value</button>
}
export default function App() {
let [data, setData] = React.useState();
return (
<div>
<Comp1/>
<div>
<context.Provider value={{data, setData}}>
<Comp1/>
<Comp2/>
</context.Provider>
</div>
</div>
);
}
Option 3
For completeness, A third option would be using something like Redux to help share state. Only use this option if you are already using Redux, or if you really want/need it and understand what you're getting into. Redux is not for every project, everyone does not need it.
Side Note
I realize you said you were new to React. For brevity and for other Googlers, I used a lot of React hooks in my examples (The functions like React.useState, React.useContext, etc). These can take a little bit to understand, and I don't expect you to learn how to use them just to solve your problem. In fact, if you're new to React, I would strongly encourage you to just go with option 1 using the class syntax you've learned how to use already. As you get some more practice and start feeling the limits of the first option, then you can start trying the other things out.
In react, data always moves from top to down, so there is no true way to pass information sibling to sibling without going through some higher structure. You could use context, but again, its provider has to wrap around both sibling components, meaning it has to be implemented in the parent component(App). It is also intended for passing data between deeply nested sibling components to avoid passing props multiple levels deep. In your case where props only have to be passed one level deep, it is best to just store state in the parent component(App).
Here is what context would look like for your App (its more trouble than its worth at this point):
https://codesandbox.io/s/objective-hellman-sdm55?file=/src/App.js
For this use case I would suggest using the useState hook in the parent component and passing down a value & function to the specific child components.
pseudo code:
<Parent>
const [value, setValue] = useState();
<Comp1 onClick={setvalue} />
<Comp2 value={value} />
</Parent>
In my opinion, for your use case, Redux and the Context API are a bit overkill.
You can research about state and props.
References: https://flaviocopes.com/react-state-vs-props

useContext only works in stateless functional component

I'm trying to get to grips with the new useContext function in React. Works great in stateless functionality components. For example:
import React from 'react';
import LocaleContext from '../LocaleContext';
const Link = ({ text, url }) => {
const locale = useContext(LocaleContext);
return (
<a href={`/${locale}/${url}`}>
{text}
</a>
);
};
export default Link;
I also want to use useContext in stateful components, and even non React functions, but when I do so, I get the following error:
Hooks can only be called inside the body of a function component.
The message seems simple enough to understand, but is this really true? I can only use it in a stateless functional component? If so, it seems kind of pointless to me, because it's super easy to use a simple HOC or the traditional method of:
<Locale Consumer>
{locale => (
...
)}
</LocaleConsumer>
So what gives here? I have the latest version of every package in my project. Not sure if it matters but I'm developing a NextJS site here.
If you really want to use classes (i actually came from Angular and i still prefer use classes) you can workaround easily like that:
class ComponentImpl extends React.Component<any> {
constructor(props?) {
super(props);
}
render() {
return (
<div>
CounterButton: <button onClick={() => {this.props.appContext.setCount(this.props.appContext.count + 5)}}>App Counter + 5</button>
</div>
)
}
}
export function Component() {
let appContext = useContext(AppContext);
return <ComponentImpl appContext={appContext}></ComponentImpl>
};
And you just use it: <Component></Component>
The problem is what the error says. React hooks aren't available in class components. Due to differences between class components and function components, hooks cannot be used with the former.
As the documentation says,
Hooks let you use more of React’s features without classes. Conceptually, React components have always been closer to functions. Hooks embrace functions, but without sacrificing the practical spirit of React. Hooks provide access to imperative escape hatches and don’t require you to learn complex functional or reactive programming techniques.
Hooks are supposed to address common use cases that are specific to class components which couldn't be previously implemented with stateless functional components alone. Functional components aren't stateless since React 16.8 and are allowed to have a state and trigger own updates.
As for useContext hook,
When the provider updates, this Hook will trigger a rerender with the latest context value.
It would be messed up in class component due to difference between functional and class components. Component function is called each time the component is rendered:
const Foo = props => {
const context = useContext(Context);
// use context
}
There's no place in class component that would behave the same way except render function. And if lifecycle-specific tasks go to render function, this means that a class was a wrong choice, and class component needs to be refactored to a function. A counterpart to useContext in class components is contextType, which is currently restricted to single context.
For multiple contexts it's still required to receive them through context Consumer inside render, or as props from higher-order component wrapper:
const contextsHOC = (contexts = {}) => Comp => (
props => {
const contextProps = {};
for (const prop in contexts) {
// eslint-disable-next-line react-hooks/exhaustive-deps
contextProps[prop] = React.useContext(contexts[prop]);
}
return <Comp {...props} {...contextProps}/>;
}
);
#contextsHOC({ bar: BarContext, baz: BazContext });
export default class FooComponent extends Component {
// contexts are mapped to this.props.bar and this.props.baz
...
}
// or
class FooComponent extends Component { ... }
export default contextsHOC({ ... })(FooComponent);
Passing contexts as props allows for additional optimization with PureComponent or shouldComponentUpdate.
useContext is a hook that consumes a context and can only be used in functional components.
If you want to consume context in class components, you will need to look at alternative methods such as Consumer Component, official docs for this here

Direct call of a functional component

Stateless functional component is just a function that receives props and returns React element:
const Foo = props => <Bar />;
This way <Foo {...props} /> (i.e. React.createElement(Foo, props)) in parent component could be omitted in favour of calling Foo directly, Foo(props), so React.createElement tiny overhead could be eliminated, yet this isn't necessary.
Is it considered a bad practice to call functional components directly with props argument, and why? What are possible implications of doing this? Can this affect the performance in negative way?
My specific case is that there's some component that is shallow wrapper over DOM element because this was considered a good idea by a third party:
function ThirdPartyThemedInput({style, ...props}) {
return <input style={{color: 'red', ...style}} {...props} />;
}
Here's a demo that shows this case.
This is widely accepted practice but the problem with it is that it's impossible to get ref of wrapped DOM element from stateless function, so the component uses React.forwardRef:
function withRef(SFC) {
return React.forwardRef((props, ref) => SFC({ref, ...props}));
// this won't work
// React.forwardRef((props, ref) => <SFC ref={ref} {...props } />);
}
const ThemedInput = withRef(ThirdPartyThemedInput);
This way it can be used as:
<ThemedInput ref={inputRef} />
...
inputRef.current.focus();
The obvious downside I'm aware of is that withRef requires a developer to be aware of wrapped component implementation, which isn't a usual requirement for HOCs.
Is it considered a proper approach in a situation like described above?
I don't think there's anything wrong with calling Stateless Functional Component directly. As you said it's even one tiny overhead eliminated. As to the possible implications, it would be bold to say that there are none implications and there will be none implications in the future because this is a really rare way of using SFC's. But everything points to conclusion that there shouldn't be any implications (it's just one function call less).
Anyway, below I'd like to present another way of doing this using findDOMNode instead of refs:
I've created Focus component that is really convenient to use but needs to be initialized first (since we need a way to trigger focus outside props since a component may be rerendered with the same props):
// focus.js
import React from "react";
import { findDOMNode } from "react-dom";
export default function createFocus() {
class Focus extends React.Component {
componentDidMount() {
Focus.now = () => {
findDOMNode(this).focus();
}
}
render() {
return this.props.children;
}
}
return Focus;
}
// index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import createFocus from './focus';
const Focus = createFocus();
import { ThirdPartyThemedInput } from './third-party-lib';
function App() {
return (
<div>
<button onClick={() => Focus.now()}>Proceed with form</button>
<Focus>
<ThirdPartyThemedInput placeholder="Fill me" />
</Focus>
</div>
);
}
render(<App />, document.getElementById('root'));
live at: https://stackblitz.com/edit/react-bpqicw
Functional components are very useful when you don't need to use any of the lifecycle method or don't need to update the component state. As far as you don't need to them, you're good and yet best to go with stateless component.
This will not hit the performance issue but gain the profit regarding its performance because we're just simply using function to render the component and not caring for its update, mounts, receive props, etc. But still there's no 100% gain using stateless component because react internally use class to render them.
It's about 45% improvement.
This post will also guide which one to choose between statefull component and stateless component.
Further, you can not only receive the props but can also receive the ref:
const stateless = (props, ref) => <ReturnComponent {...props} ref={ref} />
Okay, let me refine my statement. Most of the blogs and even the docs states that stateless component don't have ref. Here are a few Q/A prepared regarding this issue:
Do I need to use statefull component just to use ref?
No. I already mentioned that we must require the class based component if we have to work with component state or hook some lifecycle method.
How can I create ref in stateless component?
const stateless = () => {
// we can't do this.myRef = React.createRef()
// so, let's create an object
const RefObj = {}
// now, create ref in {RefObj}
RefObj.myRef = React.createRef()
return <input type="text" ref={myRef} />
}

Categories

Resources