How to implement HOC characteristics width useHooks? - reactjs

I'm want to rewrite my high-order-component with useHooks. it is possible reuse stateful logic like hoc?
I have worked with hoc for a while, I think it is esay to solution some problem.
I tried to implement same features like hoc use useHooks, sorry... I failed
// HOC
const Hoc = WrapperComponent => (
class extends React.Component {
state = {
toggle: false
}
handleClick = () => {
this.setState({ toggle: !this.state.toggle })
}
render() {
const { toggle } = this.state
return (
<>
<button onClick={handleClick}>click</button>
{toggle && <WrapperComponent />}
</>
)
}
}
)
// Component A
function CompA () {
return 'class comp a'
}
// reuse logic with hoc
export default Hoc(CompA)
// this is my code.
// but i think it's hoc style. not really hooks idea
function useHooks(WrapperComponent) {
const [toggle, setToggle] = useState(false)
return () => (
<>
<button onClick={() => setToggle(!toggle)}>click</button>
{toggle && <WrapperComponent />}
</>
)
}
//
export default useHooks(ClassCompA)

Hooks is designed to share any necessary logic between the components.
Presentational elements like JSX are not included in this logic. They are best left at the components which can be composed to any level necessary.
For your example using the HOC, there would need to be a component for the presentation and hooks for sharing the logic.
const { useState, Fragment } = React;
function useToggle() {
const [ show, setShow ] = useState(false);
const toggle = () => {
setShow(show => !show);
}
return {
show,
toggle,
}
}
function Toggler({ children }) {
const { show, toggle } = useToggle();
return (
<Fragment>
{show && children }
{<button onClick={toggle}>Toggle View</button>}
</Fragment>
);
}
function App() {
return (
<Toggler>
<h1>This content can be toggled</h1>
</Toggler>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>

Related

Convert functional component to class component in React - invalid hook call

I'm new with React and have seen Material-UI as a nice library, so I would like use the dashboard template in my project. Here the source code from Material-UI library documentation
But the question came to me, how can I change this functional component to class component to fit my project. I've tried something like below:
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
},
...
}));
export class DashboardDemo extends Component {
constructor(props) {
super(props);
}
render() {
const { classes } = useStyles();
const [open, setOpen] = React.useState(true);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight);
return (HTML code here...)
}
}
It shows an error. Can anyone help?
You cannot use hooks in class-based components. You can however make a "bridge" functional component (FC) that consumes the hook and then passes it down to a class as a prop. However, at that point you might as well just use a FC.
Again, classes and FC can coexist in the same project. They are tools in your toolbox and should be used at the appropriate time.
const {useState, Component} = React;
class ClassCmp extends Component {
render() {
return (
<div>
<div>Foo: {this.props.foo}</div>
<button onClick={this.props.onClick}>Click Me</button>
</div>
);
}
}
const HookWrapper = () => {
const [foo, setFoo] = useState("hello");
return (
<ClassCmp foo={foo} onClick={() => setFoo("world")}/>
);
};
const App = () => (
<div>
<HookWrapper />
</div>
);
ReactDOM.render(
<App/>,
document.getElementById("app")
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="app"></div>

Patterns in React (wrapper)

Good day. I'm building a tree of components and want to use functions of root component in other components of tree. I throw function reference through all tree.
Also I use the object if me need get value from the function in not root componet.
Can you help me?
Can you show me how to do this as HOC ?
If it will be not so hard for you show examples on my code.
import React from 'react';
class Page extends React.Component{
Answer = {
value : ''
}
doSomething(){
console.log(this.Answer.value);
console.log('Ready!');
}
render(){
return(
<div>
<div>
<Body
ParentFunc={()=>this.doSomething()}
ParentParameters={this.Answer}
/>
</div>
</div>
)
}
}
export default Page
class Body extends React.Component{
render(){
const{
ParentFunc,
ParentParameters
} = this.props
return(
<div>
<div>
<SomeComponent
ParentFunc={()=>ParentFunc()}
ParentParameters={ParentParameters}
/>
</div>
</div>
)
}
}
class SomeComponent extends React.Component{
getAnswer(){
const{
ParentFunc,
ParentParameters
} = this.props
ParentParameters.value = 'Some text'
ParentFunc()
}
render(){
return(
<div onClick={()=>this.getAnswer()}>
We can?
</div>
)
}
}
I don't believe a Higher Order Component alone will solve your basic issue of prop drilling. A React Context would be a better fit for providing values and functions generally to "want to use functions of root component in other components of tree".
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
In a typical React application, data is passed top-down (parent to
child) via props, but such usage can be cumbersome for certain types
of props (e.g. locale preference, UI theme) that are required by many
components within an application. Context provides a way to share
values like these between components without having to explicitly pass
a prop through every level of the tree.
Start by creating your Context and Provider component:
const QnAContext = React.createContext({
answer: {
value: ""
},
doSomething: () => {}
});
const QnAProvider = ({ children }) => {
const answer = {
value: ""
};
const doSomething = () => {
console.log(answer.value);
console.log("Ready!");
};
return (
<QnAContext.Provider value={{ answer, doSomething }}>
{children}
</QnAContext.Provider>
);
};
Render QnAProvider in your app somewhere wrapping the React subtree you want to have access to the values being provided:
<QnAProvider>
<Page />
</QnAProvider>
Consuming the Context:
Class-based components consume contexts via the render props pattern.
<QnAContext.Consumer>
{({ answer, doSomething }) => (
<SomeComponent doSomething={doSomething} answer={answer}>
We can?
</SomeComponent>
)}
</QnAContext.Consumer>
Functional components can use the useContext React hook
const SomeComponent = ({ children }) => {
const { answer, doSomething } = useContext(QnAContext);
getAnswer = () => {
answer.value = "Some text";
doSomething();
};
return <div onClick={this.getAnswer}>{children}</div>;
};
Here is where using a Higher Order Component may become useful. You can abstract the QnAContext.Consumer render props pattern into a HOC:
const withQnAContext = (Component) => (props) => (
<QnAContext.Consumer>
{(value) => <Component {...props} {...value} />}
</QnAContext.Consumer>
);
Then you can decorate components you want to have the context values injected into.
const DecoratedSomeComponent = withQnAContext(SomeComponent);
...
<DecoratedSomeComponent>We can with HOC?</DecoratedSomeComponent>
Note: The point of doing all this was to move the values and functions that were previously defined in Page into the Context, so they are no longer passed from Page though Body to SomeComponent (or any other children components).
Demo
Sandbox Code:
const QnAContext = React.createContext({
answer: {
value: ""
},
doSomething: () => {}
});
const QnAProvider = ({ children }) => {
const answer = {
value: ""
};
const doSomething = () => {
console.log(answer.value);
console.log("Ready!");
};
return (
<QnAContext.Provider value={{ answer, doSomething }}>
{children}
</QnAContext.Provider>
);
};
const withQnAContext = (Component) => (props) => (
<QnAContext.Consumer>
{(value) => <Component {...props} {...value} />}
</QnAContext.Consumer>
);
class SomeComponent extends React.Component {
getAnswer = () => {
const { doSomething, answer } = this.props;
answer.value = "Some text";
doSomething();
};
render() {
return (
<button type="button" onClick={this.getAnswer}>
{this.props.children}
</button>
);
}
}
const DecoratedSomeComponent = withQnAContext(SomeComponent);
class Body extends React.Component {
render() {
return (
<div>
<div>
<QnAContext.Consumer>
{({ answer, doSomething }) => (
<SomeComponent doSomething={doSomething} answer={answer}>
We can?
</SomeComponent>
)}
</QnAContext.Consumer>
</div>
<div>
<DecoratedSomeComponent>We can with HOC?</DecoratedSomeComponent>
</div>
</div>
);
}
}
class Page extends React.Component {
render() {
return (
<div>
<div>
<Body />
</div>
</div>
);
}
}
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<QnAProvider>
<Page />
</QnAProvider>
</div>
);
}
Based on your current code I am making the assumption that Body does not modify the values of ParentFunc and ParentParameters before passing them down to SomeComponent.
You have a hierarchy
<Page>
<Body>
<SomeComponent>
</Body>
</Page>
and you want to pass props from Page to SomeComponent without going through Body.
You can do this using children
children is a special prop representing the JSX child elements of the component. We make it so that Body renders the children that it got through props:
class Body extends React.Component{
render() {
return(
<div>
<div>
{this.props.children}
</div>
</div>
)
}
}
We set that children prop by using a <SomeComponent/> element inside of the <Body>:
render() {
return (
<div>
<div>
<Body>
<SomeComponent
ParentFunc={() => this.doSomething()}
ParentParameters={this.Answer}
/>
</Body>
</div>
</div>
);
}
Note that you cannot directly modify the value that you got from the parent, which is what you were doing with ParentParameters.value = 'Some text'. If you want to update the state of the parent then you need to do that through your callback function props. So your code should look something like this:
import React from "react";
class Body extends React.Component {
render() {
return (
<div>
<div>{this.props.children}</div>
</div>
);
}
}
class SomeComponent extends React.Component {
state = {
showAnswer: false
};
onClick() {
// update answer in parent
this.props.setAnswer("Some text");
// change state to reveal answer
this.setState({ showAnswer: true });
}
render() {
return (
<div>
{this.state.showAnswer && <div>Answer is: {this.props.answer}</div>}
<div onClick={() => this.onClick()}>We can?</div>
</div>
);
}
}
class Page extends React.Component {
state = {
value: ""
};
render() {
return (
<div>
<div>
<Body>
<SomeComponent
answer={this.state.value}
setAnswer={(answer) => this.setState({ value: answer })}
/>
</Body>
</div>
</div>
);
}
}
export default Page;

React useState() hook does not update state when child.shouldComponentUpdate() returns false

I've started reading about React Hooks and tried to rewrite some small components to use them, but I've got a problem with setting state. Basically, when the child component <Content/> always returns false in shouldComponentUpdate, then parent's (<App/>.setShow) is called, but it does not update the state and <Content/> isn't rendered.
I've prepared a minimal working example below, where <App/> is a component based on React Hoos, and <App2/> is an equivalent class based Component. The latter one works, so apparently there is some nuance there I don't get.
const {useState} = React;
class Panel extends React.Component {
shouldComponentUpdate() { return false; }
render() { return (<button onClick={this.props.onClick}>Toggle</button>) }
}
class Content extends React.Component {
render() { return <div>Content</div> }
}
const App = (props) => {
const [show, setShow] = useState(true);
const toggle = () => {
console.log("Toggling visibility!", show);
setShow(!show);
}
console.log("[App] render()");
return (<div >
<h1>Hi at {Date.now()}</h1>
<Panel onClick={toggle} />
{show && <Content /> }
</div>)
}
class App2 extends React.Component {
state = { show: true }
render() {
const toggle = () => {
console.log("[App] toggle", this.state.show)
this.setState({ show: !this.state.show })
}
console.log("[App] render()");
return (<div >
<h1>Hi at {Date.now()}</h1>
<Panel onClick={toggle} />
{this.state.show && <Content />}
</div>)
}
}
ReactDOM.render(<div> <App/> <App2/> </div>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app">
</app>
You need to use functional updates because you have closure on value of show inside toggle function.
The closure happens because shouldComponentUpdate always returns false, so on first render the value of this.props.onClick will have stale state of show === true, which won't change on subsequential renders.
const App = () => {
const [show, setShow] = useState(true);
// Closure on value of show
// const toggle = () => {
// console.log("Toggling visibility!", show);
// setShow(!show);
// }
const toggle = () => {
setShow(prevState => {
console.log("Toggling visibility!", prevState);
return !prevState;
});
};
console.log("[App] render()");
return (
<div>
<h1>Hi at {Date.now()}</h1>
<Panel onClick={toggle} />
{show && <Content />}
</div>
);
};

React memo function with isEqual

I have this React.memo component that I want to render only if the props doesn't change with sending a second argument isEqual function.
When I console.log the wrapper component and the memmoized component I can see that its being rendered with the same props.. What am I doing wrong?
My wrapper component
export const WrapperComponent= props => {
console.log('MemoizeComponent', props);
return (
<MemoizeComponent name="memo"/>
);
}
export const WrapperComponent;
My memmoized component
export const Component = props => {
console.log('component: ', props.name)
return (
<div>{props.name}</div>
);
}
function isEqual(prevProps, nextProps) {
console.log(prevProps.name);
console.log(nextProps.name);
return prevProps.name === nextProps.name;
};
export const MemoizeComponent = React.memo(Component, isEqual);
console output:
memo
memo
component:memo
memo
memo
component:memo
Not sure what your question is but your code as is works correctly:
const { useState, memo, useRef } = React;
const useRendered = () => {
const rendered = useRef(0);
rendered.current++;
return rendered.current;
};
function App() {
const [, setRender] = useState();
const rendered = useRendered();
return (
<div>
<div>rendered {rendered} times</div>
<button onClick={() => setRender({})}>
re render
</button>
<WrapperComponent name="memo" />
</div>
);
}
const WrapperComponent = props => {
return <MemoizeComponent name="memo" />;
};
const Component = props => {
const rendered = useRendered();
return (
<div>
{props.name} rendered {rendered} times
</div>
);
};
function isEqual(prevProps, nextProps) {
return prevProps.name === nextProps.name;
}
const MemoizeComponent = memo(Component, isEqual);
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to update a child component without re-rendering without Redux

How do I update an adjacent (or child) component after a scroll event without re-rendering the parent component?
Adjacent scenario
<div>
<Scrollable onScroll={ ... } />
<DisplayPosition scrollEvent={ ... } />
</div>
Child scenario
return (
<div onScroll={ ... }>
<span> Don’t re-render me! </span>
<DisplayPosition scrollEvent={ ... } />
</div>
)
I am reluctant to reach for Redux for this problem as I would like to be able to solve it in a lightweight fashion
Without Redux
ParentComponent
export default class ParentComponent extends Component {
render() {
let parentCallback = () => {};
const onScroll = event => parentCallback(event);
const didScroll = callback => parentCallback = callback;
return (<div onScroll={onScroll}>
<ChildrenComponent whenParent={didScroll} />
...
ChildrenComponent
It will setup the callback to be executed by the parent
componentDidMount() {
this.props.whenParent(event => this.setState({ event }));
}
By updating the state your ChildrenComponent will re-render. The ParentComponent will not re-render as nothing on its state or properties changed.
You'll have access to your event on this.state.event. Check the snippet for an example with click event.
class ParentComponent extends React.Component {
render() {
console.log('hello ParentComponent');
let parentCallback = () => {};
const onClick = () => parentCallback(1);
const didClick = callback => parentCallback = callback;
return (
<div>
<button onClick={onClick}>Click To Add</button>
<ChildrenComponent whenParent={didClick} />
</div>
);
}
}
class ChildrenComponent extends React.Component {
state = { sum: 0 };
componentDidMount() {
const { whenParent } = this.props;
whenParent((adds) => {
const { sum } = this.state;
this.setState({ sum: sum + adds });
});
}
render() {
console.log('hello ChildrenComponent');
const { sum } = this.state;
return <div>{sum}</div>;
}
}
ReactDOM.render(
<ParentComponent />,
document.getElementById('container')
);
<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="container"></div>

Resources