Conditional rendering when using Redux (pure component and non-pure component)? - reactjs

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

Related

Can not use component using hooks inside redux modal

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;

How to pass data down to a component property in React?

Many React components allow passing custom components as props, in order to customize certain parts of the UI. A popular example would be react-select, which allows to specify custom replacements for all its individual sub components.
In this example, I'm using a modal dialog component that allows specifying custom footer component in its components prop:
const Footer = () => <button>Close</button>;
const MyModal = ({ onClose, closeLabel }) => <Modal components={{ footer: Footer }}/>;
I would like to pass certain data down to the custom component. In this particular example, I would like to pass down the onClose and closeLabel props to the Footer component. What I could do is to declare the Footer component inline:
const MyModal = ({ onClose, closeLabel }) => (
<Modal
components={{ footer: () => <button onClick={onClose}>{closeLabel}</button> }}
/>
);
The problem with this approach is that every time MyModal is rendered, a new footer component is created, which causes React to completely recreate the footer DOM. In this simple example that wouldn't be a big problem, but in more complex scenarios it would make things slow and also cause the component to lose its state. Wrapping the footer component in useCallback() would partially solve the problem, but only as long as none of the values passed down into the footer component change.
I could use context to pass down the value, but that seems like a really complicated solution for a really simple problem. Are there any alternatives?
you can do something similar like this:
export default function App() {
return (
<MyModal>
<Footer /> // this will get the new props
</MyModal>
);
}
const Footer = ({ onClose, closeLabel }) => (
<button onClick={onClose}>{closeLabel}</button>
);
const MyModal = ({
onClose,
closeLabel,
children // here any child you pass to this component will have both onClose and closeLabel as props
}) => {
return (
<div>
{React.Children.map(children, (child) => {
return React.cloneElement(child, {
onClose,
closeLabel
});
})}
</div>
);
};
but anyways there is something not clear there in your code, if you are getting the onClose and closeLabel as props inside MyModal why just don't pass them to the Footer from where are you getting them? and if you don't have control over Modal component then you can't do anything else than giving it inline

Can two React distinct and mounted components communicate with each other using props?

A simple example for explaining my question:
ButtonComponent is a component mounted on <div id="btn-container"> element
ModalComponent is a component mounted on <div id="modal-container"> element
So two components mounted on different elements in different places in the page, not sharing a common React element.
ModalComponent receive a prop named isOpen to trigger its visibility:
<ModalComponent isOpened={isOpened} />
The ButtonComponent should, in some way, pass the prop isOpen to the ModalComponent without sharing the same "root" component. Is this even possible?
Of course the "normal way" to do this would be a ButtonPlusModalComponent like the following:
export const ButtonPlusModalComponent = () => {
const [isOpened, setIsOpened] = useState(false);
return (
<>
<ButtonComponent onClick={() => setIsOpened(true)} />
<ModalComponent isOpened={isOpened} />
</>
);
};
I'm using React with a regular PHP application, so... not a complete "full" React application, only some parts and portions of the page are React components, no router at all.
So, I have that button somewhere in the page. That button should open the modal component, which is a... React component placed elsewhere in the page.
EDIT: explain why the question.
You must both component to share a common parent, thats how React works as Data Flows Down.
If you can, couple them into the same tree and use common solutions like Context API.
But, you describing a situation where the components are decoupled, so in order to have a common parent and mount a component into another HTML element, you need to use React.Portal.
Therefore you need one component to mount the other using Portals:
<body>
<div id="modal-container"></div>
<div id="btn-container"></div>
</body>
const Modal = ({ open }) => open && <>Modal</>;
const App = () => {
const [open, toggleOpen] = useReducer((p) => !p, true);
return (
<>
<button onClick={toggleOpen}>toggle</button>
{ReactDOM.createPortal(
<Modal open={open} />,
document.getElementById("btn-container")
)}
</>
);
};
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("modal-container")
);

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)
})

Meteor + React + createcontainer

Using react with meteor here, I have main component called App, and it wraps page layout (Header, Sidebar, Right-sidebar).
export default class App extends Component {
render() {
return (
<div>
<nav className="navigation">
<Header />
<Sidebar />
</nav>
<div className="content">
<Subnavbar />
<div className="container">
{this.props.children}
</div>
</div>
<Rightsidebar />
</div>
);
}
};
I'm trying to setup authentication system using Meteor's built in auth system. using "accounts-password" package.
To my knowldge, I need to use createContainer from 'meteor/react-meteor-data' to inject auth params to components.
Similar to this example:
import { createContainer } from 'meteor/react-meteor-data';
import MainPage from '../pages/MainPage.jsx'
export default MainContainer = createContainer(({params}) => {
const currentUser = Meteor.user();
return {
currentUser,
};
}, MainPage);
However in the above example, it only injects the parms to a single component, how can I go about injecting auth info to all components in my app (Header, Sidebars ..etc)
Your help is highly appreciated.
Thank you
If you wrap App in createContainer, then App will have a prop currentUser. It can then be the responsibility of App to pass the currentUser prop to all of your components. If you find yourself passing around currentUser far too much, then you can wrap only the components that need currentUser in createContainer.
In that case you would have HeaderContainer, SidebarContainer, etc, each being wrapped with createContainer.

Resources