I have a react redux app and the modal is managed through the redux store. So when user gets to the home page the <Home /> and <Modal /> mount at the root of the application. From there, whenever the user clicks a button that should open the modal, displayModal action is dispatched like so:
const handleModalButtonClick = () => {
this.props.actions.displayModal({
className: 'modal-light',
component: () => <FunctionalComponentThatUsesHooks />
});
};
The <FunctionalComponentThatUsesHooks /> is what is supposed to open up in the modal. But this doesn't seem to work when the component passed is a component using hooks as I am seeing the invalid hooks call error:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:...
The component using hooks is a function component, so I know that's not the issue. I can't seem to figure out exactly why this isn't working. Maybe has to do with the fact that the modal is managed through the redux store or the way component is being passed? Any ideas? Thanks
update: the displayModal action has type SHOW which just just adds the data to the redux store as so:
case SHOW:
return {
...state,
modal: action.payload
};
in the <Modal />, using function mapStateToProps to get the component from the redux store. then the <Modal /> render calls this.modalBody which will render that component from the redux store as so:
get modalBody() {
const {component: InnerComponent} = this.props;
if (!InnerComponent) {
return null;
}
return (
<div className="modal-body" ref={this.modalBodyNode}>
<InnerComponent {...this.props} />
</div>
);
}
the <FunctionalComponentThatUsesHooks /> isn't any specific component, I've tried with multiple different components that use hooks, even simple ones that only use the useState hook like so:
import React, {useState} from 'react';
const FunctionalComponentThatUsesHooks = (props) => {
const [size] = useState(props.size);
return (
<button className={`btn-${size}`}>{props.children}</button>
);
};
export default FunctionalComponentThatUsesHooks;
Related
I would like to "Unmount a simple Functional Component" from the DOM. I searched a lot and saw most of the tutorials are based on Class Components and I did'nt see any simple example on it. My requirement is Unmounting a Functional component from the DOM on click on a button. Following is the component with the button which i likes to unmount when click on it. Hopes someone can help me to do it. Thanks in Advance !
import React from 'react'
function App() {
return (
<div className='app-component'>
<h2 className="h2">App Component</h2>
<button>Unmount This Component</button>
</div>
)
}
export default App
If you want to unmount a component then you can use conditional rendering where you can declare state in parent component and based on the state you can mount or unmount component as:
This is the parent component from where you want to mount or unmount
CODESANDBOX DEMO
If you want to toggle component once then you can do the following because there is only one way to change state i.e from Test component. If you unmount this component there is no way to mount it again. So you can also declare button in App component from where you can mount or unmount on click of a button. CODESANDBOX
Parent component
export default function App() {
const [isShowing, setIsShowing] = useState(true); // STATE
return (
<div className="App">
{isShowing && <Test setIsShowing={setIsShowing} />}
</div>
);
}
Child component
function Test({ setIsShowing }) {
function unmountComponent() {
setIsShowing(false);
}
return (
<div className="app-component">
<h2 className="h2">App Component</h2>
<button onClick={unmountComponent}>Unmount This Component</button>
</div>
);
}
You can use state flag for removing element like this:
import React from 'react'
function App() {
const [flag,setFlage]=useState(true);
return (
<div className='app-component'>
{flag?<h2 className="h2">App Component</h2>:null}
<button onClick={()=>setFlag(!flage)} >Unmount This Component</button>
</div>
)
}
export default App
//Example Component
import React, { useState } from 'react';
import IndependentComponent from './independent-component'
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<IndependentComponent />
</div>
);
}
//Independent Component
import React from 'react'
const IndependentComponent = function () {
console.log('This component is rendered when setCount is called in Example component')
return (
<div>Independent Sibling Component </div>
)
}
export default IndependentComponent
When a parent component is re-rendered, so are all its children. Thus even though IndependentComponent does not change with changes in parent component, it will still forcibly go through the render cycle.
There are 2 ways to prevent this, depending on whether it is a class based component or functional component.
For class based components, you could either extend the PureComponent class to never go through re-render cycle, or you could use shouldComponentUpdate for more fine tuned control of when the component should re-render.
In the functional components case, which you are looking for, you can use React.memo HOC to make it behave like PureComponent, with the optional callback function to also have the fine-tuned control of shouldComponentUpdate.
So, in your use case of functional component, all you need to do is export your independent component like:
export default React.memo(IndependentComponent);
I'm attempting to import a React functionComponent from an SVG and then send that to another component as a prop to render that svg. With the setup below, this compiles fine, but eventually crashes when trying to render the svg in browser with:
Error: Objects are not valid as a React child (found: object with keys {$$typeof, render}). If you meant to render a collection of children, use an array instead.
Classes below are simplified. But the gist of what I'm trying to do is:
In overlay.tsx:
import { ReactComponent as icon } from "/icon.svg";
import CustomItem from "/customItem";
const Overlay: React.FC<OverlayProps> = () => {
return (
<div>
<CustomItem icon={icon}/>
</div>
);
export default Overlay;
}
and in customItem.tsx:
import React from "react";
export interface CustomItemProps {
icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = ({icon}) => {
return (
<div>
{icon}
</div>
);
};
export default ApplicationsDropdownItem;
I assume my problem is somewhere around the syntax of {icon}, but I can not for the life of me find out what I'm suppose to use instead.
Answer
The icon you are importing is a component, therefore it must be called to render the JSX.
<Icon {...props}/> (correct) or {Icon(props)} (not recomended)
Since it is a component, you should also name it Icon and not icon.
Take a look at this blog post that explains SVGR.
TL;DR - Best approach for rendering components
A. Call the component in your render method with component syntax <MyComponent/> not MyComponent().
B. Instantiate your component as a variable, and pass that to your render method's JSX block.
More info
#DustInCompetent brought to light the issue of calling a component as a function inside a JSX block.
As explained here and here, that will lead to react not registering a components hooks and lead to state and other problems.
If you are implementing a High Level Component (HOC), then you should not call a component within the render method (return statement in functional components), as this leads to problems for similar registration issues of the component.
import React from "react";
import { ReactComponent as SampleIcon } from "/sample_icon.svg";
export interface CustomItemProps {
Icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = (props) => {
const Temp = props.Icon as React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
return (
<div>
<Temp/>
</div>
);
};
<CustomItem Icon={SampleIcon}/>
I think you should use <Icon /> instead of {icon} because it's a component.
I'm trying to prevent a re-render when using custom hook for hours now -.-, need some help ;O|
(Dont know if I should call this custom hook or functional hoc though)
I have a MessageList component that display a SimpleMessage wrapped in WithAvatarHeader.
Here is my profiler result:
Every time I add a message to the list, all messages are rendered again.
This isn't happening when I only use SimpleMessage in MessageList
Is there a way to memo(WithAvatarHeader) ?
MessageList :
import React from "react";
import SimpleMessage from "./SimpleMessage";
import WithAvatarHeader from "./WithAvatarHeader";
const MessageList = props => {
const Message = WithAvatarHeader(SimpleMessage);
return (
<div className="message-list">
{props.messages.map(message => {
return <Message message={message} key={message._id}/>;
})}
</div>
);
};
SimpleMessage:
import React, { memo } from "react";
const SimpleMessage = props => {
return (
<div className="simple-message">
{props.message}
</div>
);
};
export default memo(SimpleMessage);
WithAvatarHeader:
import React from "react";
const WithAvatarHeader = WrappedComponent => props => {
return <WrappedComponent {...props} />;
};
export default WithAvatarHeader;
Thanks for the help :-)
You should not declare component inside another component.
Once you move declaration outside:
const Message = WithAvatarHeader(SimpleMessage);
const MessageList = props => {
return (
<div className="message-list">
{props.messages.map(message => {
return <Message message={message} key={message._id}/>;
})}
</div>
);
};
you will be fine.
Reason is reconciliation process that decides what's to drop, what to create and what to update.
Besides your JSX says it still same element <Message> React checks component's constructor(it does not work with text representation from JSX). And it will referentially different(since you re-declare this constructor on next render). So React drops every <Message> and create them from scratch. Keeping declaration outside your MessageList means constructor is referentially the same so React will not re-create <Message> till key is the same.
const LoginPage = ({ auth, doLogin, doLogout }) => (
<div>
<NavBar auth={auth}/>
<div className="row">
<div style={{display: 'flex', justifyContent: 'center'}} className="col-md-4 col-md-offset-4">
<LoginForm auth={auth} doLogin={doLogin} doLogout={doLogout}/>
</div>
</div>
</div>
);
// Connects state to the props of this component
const mapStateToProps = state => ({
auth: state.auth,
});
// Return a NEW component that is connected to the Store
export default connect(mapStateToProps, { doLogin, doLogout })(LoginPage);
Above is the code of the component called LoginPage.
It passes in the global state auth and two actions doLogin and doLogout. The LoginForm handles the user authentication, but I want to put another component called Dashboad and render it conditionally based on the auth's status.
However, I am not sure how to conditionally render in a pure component.
I could change into a non-pure component, but then I am not sure how to pass in auth, doLogin, and doLogout into a non-pure component.
Please help.
You can do almost everything you did in a non pure component in a pure component, except that PureComponent automatically does the shouldComponentUpdate by react itself to avoid unnecessary rerenders based on the shallow diffing of state and props.
If you want to conditionally render dashboard you can check the auth status from redux and use this.props.history.push("/dashboard") or <Redirect to="/dashboard"> to navigate to Dashboard component