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>
Related
I came across this behaviour recently and was trying to understand its cause. Basically, what I noticed so far is that a React child component will be mounted and unmounted on state changes of its parent. However, a jsx containing the same child component does not.
I put together this simplified example to demonstrate the behaviour.
const Child = ({ title }) => {
const [count, setCount] = React.useState(0);
const increment = () => setCount((x) => x + 1);
return (
<button onClick={increment}>
{title} Current count = {count}
</button>
);
};
const App = () => {
const [, setState] = React.useState(false);
const rerender = () => setState((x) => !x);
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;
return (
<div>
<button onClick={rerender}>Re render parent</button>
<br />
<ChildWrapper />
{childWrapperJsx}
</div>
);
}
const domContainer = document.querySelector('#root');
const root = ReactDOM.createRoot(domContainer);
const e = React.createElement;
root.render(e(App));
<script src="https://unpkg.com/react#18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#18/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Does anyone know the reason behind this? Is there a way to prevent React from unmounting a child component in this case?
I think your question is about the difference between the two buttons' behavior, one is coming back to zero after clicking to rerender the parent component and the other not.
First of all, we should understand the life-cycle of a function component, the render is executing each state change.
function App() {
/**
* it will be executed each render
*/
return (<div />);
}
Also we have to understand the difference between create a component and instantiate a component.
// this is creating a component
const ChildWrapper = () => <Child title="Component" />;
// this is instantiating a component
const childWrapperJsx = <Child title="jsx" />;
JSX is only a tool to transpile the syntaxe of this <Child title="jsx" /> to React.createElement('div', { title: "jsx" }) for example. To explain better, the code is transpiled to something like this:
// this is creating a component
const ChildWrapper = () => React.createElement('button', { title: 'Component' });
// this is instantiating a component
const childWrapperJsx = React.createElement('button', { title: 'jsx' }) ;
Without going deep in the hole. In your implementation we have both components implemented into the render of the parent, like this.
function App() {
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;
return (<div />);
}
Right now we realized that the first implementation is creating a new component each render, so that react can't memoize the component in the tree, it is not possible to do it.
// each render ChildWrapper is a new component.
const ChildWrapper = () => <Child title="Component" />;
And the second one, the childWrapperJsx is already a react element instantiated and memoized. React will preserve the same instance on the parent component life cycle.
According to React's best practice, it is not recommended to create components inside the render of another component. If you try to put both implementations outside of the component, you will be able to see that both components won't be unmounted after the parent component's render.
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;
function App() {
return (
<>
<ChildWrapper />
{childWrapperJsx}
</>
);
}
I'm somewhat confused on the relationship between functional components in React and the Render Props and HOC patterns.
That is,
is it true that the only way to create Render Prop is with a class component?
is it true that the only way to create an HOC is with a class component?
And the same for usage.
I'm trying to find examples of Render Props and HOC's with functional components and all I find are class components. I get that React Hooks do a lot of the same, but I'm trying to understand how the Render Props and HOC patterns can apply to functional components (or if they do at all)
Edit Below:
Applying what #chaimFriedman suggested, this is what I came up with using no class or component for an HOC hoping it makes sense.
import React, { useState, useEffect } from 'react';
import useAxios from 'axios-hooks';
function withFetching(url) {
return function(Speakers) {
return () => {
const [speakerData, setSpeakerData] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [{ data, loading }, refetch] = useAxios('http://localhost:4000/speakers');
useEffect(() => {
setSpeakerData(data);
setIsLoading(loading);
}, [loading]);
if (isLoading) return <div>loading..</div>;
return <Speakers data={speakerData}></Speakers>;
};
};
}
const Speakers = function(props) {
//debugger;
return (
<ul>
{props.data.map((speaker) => (
<li key={speaker.id}>
<span>
{speaker.firstName} {speaker.lastName}
</span>
</li>
))}
</ul>
);
};
const API = 'http://localhost:4000/speakers';
export default withFetching(API)(Speakers);
Both render props and HOC can absolutely apply to functional components. Let's think a little more about what each one really is to see why they do in fact work with functions as well as classes.
Render props is when you have a prop that is a function which returns JSX. This of course should work for function components because aside from life cycle methods there really isnt much that is different than class components. Here is an example with code.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(props) {
return (
props.children()
);
}
function App() {
return (
<div className="App">
<Renderer>
{() => {
return (
<h1>I am being rendered by Renderer</h1>
);
}}
</Renderer>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Now for HOC.
A HOC really is just a higher order function, but because we use it in react we call it a higher order component. A higher order function is a function which either accepts a function as an argument, or returns a function. Now a functional component can absolutely do this. Here is an example.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(Wrapped) {
return function New(props) {
return <Wrapped {...props} />
}
}
function Child(props) {
return (
<h1>Hello {props.name}</h1>
);
}
function App() {
const C = Renderer(Child)
return (
<div className="App">
<C name="john" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
EDIT: I realized my HOC example was wrong so updated.
I hope this helps.
Here is the sample code converting example from React documentation into function component. https://reactjs.org/docs/render-props.html
import React from "react";
const Cat = ({mouse}) => {
return (
<img
src="/cat.png"
alt="cat"
style={{ position: "absolute", left: mouse.x, top: mouse.y }}
/>
);
};
const Mouse = (props) => {
const [state, setState] = React.useState();
const handleMouseMove = (event) => {
setState({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={handleMouseMove}>
{props.render(state)}
</div>
);
};
const MouseTracker = () => {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={(mouse) => <Cat mouse={mouse} />} />
</div>
);
};
export const App = () => {
return (
<div className="App">
<MouseTracker />
</div>
);
}
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>
I have a hook component like this:
import React, { useState} from "react";
const MyComponent = props => {
const [value, setValue] = useState(0);
const cleanValue = () => {
setValue(0);
};
return (<span><button onClick={()=>setValue(1)}/>{value}<span>)
}
I want to reset value from the parent component. How I can call clean value from parent component? the parent component is a stateful component.
If the parent has to control the child state then probably the state must reside in the parent component itself. However you can still update child state from parent using ref and exposing a reset method in child. You can make use of useImperativeHandle hook to allow the child to only expose specific properties to the parent
const { useState, forwardRef, useRef, useImperativeHandle} = React;
const Parent = () => {
const ref = useRef(null);
return (
<div>
<MyComponent ref={ref} />
<button onClick={() => {ref.current.cleanValue()}} type="button">Reset</button>
</div>
)
}
const MyComponent = forwardRef((props, ref) => {
const [value, setValue] = useState(0);
const cleanValue = () => {
setValue(0);
};
useImperativeHandle(ref, () => {
return {
cleanValue: cleanValue
}
});
return (<span><button type="button" onClick={()=>setValue(1)}>Increment</button>{value}</span>)
});
ReactDOM.render(<Parent />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app"/>
From the React documentation about Fully uncontrolled component with a key:
In order to reset the value ..., we can use the special React attribute called key. When a key changes, React will create a new component instance rather than update the current one. Keys are usually used for dynamic lists but are also useful here.
In this case, we can use a simple counter to indicate the need for a new instance of MyComponent after pressing the Reset button:
const { useState } = React;
const Parent = () => {
const [instanceKey, setInstanceKey] = useState(0)
const handleReset = () => setInstanceKey(i => i + 1)
return (
<div>
<MyComponent key={instanceKey} />
<button onClick={handleReset} type="button">Reset</button>
</div>
)
}
const MyComponent = () => {
const [value, setValue] = useState(0)
return (
<span>
<button type="button" onClick={()=>setValue(v => v + 1)}>{value}</button>
</span>
)
};
ReactDOM.render(<Parent />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app"/>
You can't / shouldn't. Using hooks instead of stateful class components doesn't change the fact that if you want the parent to own the state, you need to declare the state in the parent.
It should look something like this, depending on when you want to reset the value (here I used another button):
const MyButton = (props) = (
// Whatever your button does, e.g. styling
<span>
<button {...props} />
<span>
)
const Parent = props => {
const [value, setValue] = useState(0);
const cleanValue = () => setValue(0);
return (
<div>
<MyButton onClick={() => setValue(1)}>
{value}
</MyButton>
<button onClick={cleanValue}>
Reset
</button>
</div>
)
}
I am trying to create an HOC which makes a regular functional component »Touchable«.
So I have that an HOC like that:
const Touchable = (Component, handler) => {
const T = ({props, children}) => {
const Instance = React.createElement(
Component,
{
...props,
onTouchStart: (e) => handler.touchStart(e),
/*more listeners*/
}, children);
}
return T;
}
Another Component like so:
const Button = ({props, children}) => <div>…</div>;
export default Touchable(Button, {touchStart: () => {}});
Using this like so:
<Button>Hallo</Button>
results in (react developer Panel):
<Button onTouchStart={…}>
<div>…</div>
</Button>
But what I really would like to have is:
<Button>
<div onTouchStart={…}>…</div>
</Button>
I have tried to clone the Instance, but somehow I have no luck. How can I do that?
Can't you just return the Component without React.createElement part?
Something like:
const TouchableHOC = (Component, handler) =>
(props) =>
<Component
{...props}
onClick={(event) => handler.touchStart(event)}
/>
const YourComponent = ({ children, onClick }) =>
<button onClick={onClick}>{children}</button>
const EnhancedComponent = TouchableHOC(
YourComponent,
{ touchStart: () => console.log('touchStart') }
)
const WithoutRest = TouchableHOC(
'button',
{ touchStart: () => console.log('touchStart') }
)
const App = () =>
<div>
<EnhancedComponent>
Click-me
</EnhancedComponent>
<WithoutRest>
No props at all
</WithoutRest>
</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>
You could make use of React.cloneElement to clone the children and attach custom props to them
const Touchable = (Component, handler) => {
const T = ({props, children}) => {
const Instance = React.createElement(
Component,
props,
React.cloneElement(children, {
onTouchStart: (e) => handler.touchStart(e),
/*more listeners*/
});
)
}
return T;
}