React Component changing props on internal event - javascript

I am creating a react component where I need to show hide a dom element depending on a props. Below you will find a basic idea of the problem. The code is simplified off course.
<UploadManager
show={show}
>
{content}
</UploadManager>
The Component
export default class UploadManager extends React.Component{
openDrawer(){
// change props.open ???
}
render(){
<Drawer
visible={this.props.show}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
}
}
Explanation
show props of the component UploadManager open/close a Drawer component
The parent component can pass the show props and show/hide the Drawer. I got this part working correctly.
Need help with: The component have a floating button. When Clicked it should open (show) the Drawer. Meaning it should set this.props.show to true.
I tried to maintain a internal state of the component which manages the show/hide property of the drawer but it becomes too buggy because of the props + state involvement.

Your answer is fine except you don't need the openDrawer in the UploadManager component, actually it could be a dummy functional component, here is a possible solution :
const UploadManager = ({show, onClick}) => (
<Drawer visible={show}>
<p>Content</p>
</Drawer>
<Button onClick={onClick} />
);
Then the parent component would be
onClickShowButton = () => {
this.setState({show: true});
}
render(){
return (
<UploadManager
show={this.state.show}
onClick={this.onClickShowButton}
>
{content}
</UploadManager>
);
}

Need to pass callback from the parent to change the prop. Below is the solution to this problem.
this.onClickShowButton() is a method of the parent.
Parent Component
onClickShowButton = () => {
this.setState({show: true});
}
render(){
return <UploadManager
show={this.state.show}
onClickShowButton={this.onClickShowButton}
>
{content}
</UploadManager>
}
On the component
Checks if the callback is a function, then executes it. Does the job perfectly!
export default class UploadManager extends React.Component{
openDrawer = () => {
if (typeof(this.props.onClickShowButton) === 'function') {
this.props['onClickShowButton']();
}
}
render(){
<Drawer
visible={this.props.show}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
}
}
If so it executes it. I don't know how correct this method is. If anyone have a better answer, I am open to it.

if the parent component just sets the initial state of your upload manager to shows or hides the drawer when it first renders then you can do this:
<UploadManager
show={show}
>
{content}
</UploadManager>
export default class UploadManager extends React.Component{
constructor(props){
super(props);
this.state = {
open: props.show
}
}
openDrawer(){
this.setState({open: true});
}
render(){
<Drawer
visible={this.state.open}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
}
}
in this way the initial state of your component would be the show property that its parent passes and then when the button gets clicked, this state gets updated and the drawer is being shown based on the component state. But if you want to control the show property that the parent is passing and you want to control your drawer with that, you should do this:
<UploadManager
show={show}
onUpdateShowState={()=>{this.setState({show: true})}}
>
{content}
</UploadManager>
export default class UploadManager extends React.Component{
openDrawer(){
this.props.onUpdateShowState()
}
render(){
<Drawer
visible={this.props.show}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
}
}
in the above code you are controling your drawer with the show property that gets passed to your component and you are updating it by calling another property that updates the parent state with this: this.setState({show: true}) -> so here this referes to your parent component where show has been defined and should be written inside the parent component of the uploadManager

Do something like this:
The Component:
export default class UploadManager extends React.Component {
constructor(props){
super(props);
this.state = {
openDrawer : props.show
}
}
componentDidUpdate(){
this.setState({
openDrawer: this.props.show
})
}
openDrawer = () =>{
this.setState({
openDrawer : !this.state.openDrawer
})
}
render() {
<div>
<Drawer
visible={this.state.openDrawer}
>
<p>Content</p>
</Drawer>
<Button onClick={this.openDrawer} />
</div>
}
}

Related

Changing the state in reactJs parent is not changing the props in react child components

I have a Parent React Component and which have 3 Child components i am changing the state in parent component but changing the state in parent is not changing the props in child components.I am passing the state from parent to child components but the props are not changing inside the child components.
My parent component
class Parent extends Component {
state = {
menuCategoryId:'',
}
handelOnClickRefundMenu = () => {
this.setState({menuCategoryId:''});
}
render() {
return (
<FoodMenu
menuCategories={menuCategories}
{...this.state}
/>
)
}
}
export default Parent;
Child 1 Component
class FoodMenu extends Component {
render() {
return (
<MenuCategories
MenuCategories={menuCategories.MenuCategories}
selectedMenuCategoryId={this.props.menuCategoryId}
/>
);
}
}
export default Child1;
Child 2 component
class MenuCategories extends React.Component{
render(){
const MenuCategories = this.props.MenuCategories;
const selectedMenuCategoryId = this.props.selectedMenuCategoryId;
const renderCategories = (MenuCategories) => (
MenuCategories ?
MenuCategories.map(card=>(
<MenuCategory
key={card._id}
{...card}
handleOnClickMenuCategory={this.props.handleOnClickMenuCategory}
selectedMenuCategoryId={this.props.selectedMenuCategoryId}
// propData={...this.props}
/>
))
:null
)
return (
<Fragment>
<div id="" className="food-menus-menu w-100">
<div className="row">
<OwlCarousel
className="owl-theme"
loop={true}
items={9}
autoplay={false}
autoplayTimeout={3000}
autoplayHoverPause={false}
nav={true}
navElement={'div'}
navText={[`<img src=${seventheenPng}>`,`<img src=${eitheenPng}>`]}
dots={false}
responsive={{
0:{
items:4
},
600:{
items:3
},
1000:{
items:7
}
}}
>
{MenuCategories ?
MenuCategories.length === 0 ?
<div className="no_result">
Sorry, no results
</div>
:null
:null}
{ renderCategories(MenuCategories)}
</OwlCarousel>
</div>
</div>
</Fragment>
)
}
};
export default MenuCategories;
Child 3 Component
class MenuCategory extends Component {
render() {
const props = this.props;
console.log('The values of the props are not changing here')
console.log(props.selectedMenuCategoryId)
return (
<div className={`colCategory item`} onClick={()=>props.handleOnClickMenuCategory(props)}>
<button
className={`btn btn-primary w-100 py-2 d-inline-flex align-items-center justify-content-center ${props.selectedMenuCategoryId === props._id ? 'activeMenuCategory' : ''}`}>
{props.name}
</button>
</div>
);
}
}
export default MenuCategory;
The value of props "props.selectedMenuCategoryId" in my last component which is inside the Map function MenuCategory is not changing when i change the state in my Parent Class function handelOnClickRefundMenu
The Map function is inside Child Component 2 MenuCategories .
Kindly help me on this please.
Thanks in advance.
All the answers about forcing re-renders using lifecycle methods are wrong. If you pass the props down correctly and they change, your child components should re-render automatically.
To demonstrate this, here's a quick'n'dirty sandbox that has a parent and two children that passes a prop down as you require.
I don't know exactly what's wrong with your code (a self-contained example that we can run and debug would help here), but I suggest paring it back to a simpler case that you can get working and then build up from there.
eta: Are you sure the problem isn't to do with your click handler? You're not passing it in to FoodMenu or MenuCategories.
Why are you naming your prop and variables the same as your component? It’s really hard to read and is probably causing confusion as to whether you’re referencing the component or the prop/variable. Use camel case
Naming your prop MenuCategories inside the component MenuCategories is not only bad practice, but could solve the issue if named menuCategories instead.

more efficiency way for performance in React Component Mounting

I've made many Modals in React.
I found 2 ways of making Modal.
The first one is like this
class Modal extends React.Component {
componentDidMount(){ console.log('DidMount') };
componentDidUpdate(){ console.log('DidUpdate') };
componentWillUnmount(){ console.log('WillUnmount') };
render(){
return (
<React.Fragment>
<div className="overlay" />
<div className="Modal>
This is Modal.
</div>
</React.Fragment>
)
}
}
class App extends React.Component {
state = {
isModalOpen: false
}
toggleModal = () => this.setState({ isModalOpen: !this.state.isModalOpen })
render(){
return (
<div className="App">
<button onClick={this.toggleModal}>Toggle</button>
{ this.state.isModalOpen ? <Modal /> : null }
</div>
)
}
}
This one repeats componentDidMount()&componentWillUnmount() when the state changed.
Let's see the other one.
class Modal extends React.Component {
componentDidMount(){ console.log('DidMount') };
componentDidUpdate(){ console.log('DidUpdate') };
componentWillUnmount(){ console.log('WillUnmount') };
render(){
return (
<React.Fragment>
{ props.isOpen ? <div className="overlay" /> : null }
{ props.isOpen ? <div className="Modal">This is Modal</div> : null }
</React.Fragment>
)
}
}
class App extends React.Component {
state = {
isModalOpen: false
}
toggleModal = () => this.setState({ isModalOpen: !this.state.isModalOpen })
render(){
return (
<div className="App">
<button onClick={this.toggleModal}>Toggle</button>
<Modal isOpen={this.state.isModalOpen} />
</div>
)
}
}
This one would not call componentWillUnmount().
It would call componentDidUpdate() when the state changed.
I wonder which one is a better way for the performance or something else.
Thank you in advance.
React.Fragment is a little bit fast and uses less memory because it doesn't need to create an extra DOM node.
With that being said unless your application is big and complex I wouldn't worry about it. I'm not exactly sure what wrapping the modal div in a React.Fragment is achieving.
You question is a bit confusing but I will attempt to clarify a few things.
Regarding your comment: This one would not call componentWillUnmount(). It will not call the the cWU() method because you are always rendering it by using this <Modal isOpen={this.state.isModalOpen} /> within your render. Now wether it appears or not based on your isOpen prop it's a different issue. On the other hand if you had something like {isThisPropertyTrue ? <Modal isOpen={this.state.isModalOpen} /> : null}, and your isThisPropertyTrue was toggling from true to false, then you would notice the console.log in your unmount hook.
This method { this.state.isModalOpen ? <Modal /> : null } has a better performance since the modal is render upon request.

How to use MDCRipple.attachTo on multiple buttons in React Component

I have a simple React component that renders multiple buttons from an array in my props. I'm applying the ripple on DidMount, however, it's only attaching on the first button, the rest are being ignored. It looks like the attachTo only takes the first element. Is there another way to attach to all the buttons on didmount?
class NavBar extends Component {
constructor(props) {
super(props);
this.state = {
links
};
}
componentDidMount() {
MDCRipple.attachTo(document.querySelector('.mdc-button'));
}
render() {
return (
<section>
{this.state.links.map((link, i) => {
return (
<StyledLink key={i} to={link.url}>
<StyledButton className="mdc-button">
<StyledIcon className="material-icons">{link.icon}</StyledIcon>
<StyledTypography className="mdc-typography--caption">
{link.title}
</StyledTypography>
</StyledButton>
</StyledLink>
);
})}
</section>
);
}
}
Final markup
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj mdc-ripple-upgraded" style="--mdc-ripple-fg-size:57.599999999999994px; --mdc-ripple-fg-scale:2.1766951530355496; --mdc-ripple-fg-translate-start:-7.799999999999997px, 19.200000000000003px; --mdc-ripple-fg-translate-end:3.200000000000003px, 19.200000000000003px;">
...content
</button>
</a>
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj">
...content
</button>
</a>
Updated
I was able to find a way to use the attachTo with each button, but it still seems like there's a better way.
I changed by componentDidMount() to:
componentDidMount() {
this.state.links.forEach((link) => {
MDCRipple.attachTo(document.getElementById(`button-navbar-${link.id}`));
});
}
and then changed my render to
<StyledButton id={`button-navbar-${link.id}`} className="mdc-button">
Is there a way to do this without having to iterate through the array?
The react way to do this is to write component that injects the necessary logic.
class RippleButton extends Component {
const handleRef = elem => MDCRipple.attachTo(elem);
render() {
return (
<StyledButton {...this.props} ref={this.handleRef} />
);
}
}
Then render that component instead of your original StyledButton component and it will call the MDCRipple.attachTo() itself with its ref.
Depending on how the StyledButton is implemented you may need to use another prop to get the ref to the underlying DOM element. You did not provide enough of your code to exactly know this.

How to make a react component that can be controled from outside?

I'm trying to make a Dialog component using React and Material-UI.
For now, I have the traditional behavior of the Material-UI dialog with a button inside the class that can open the dialog. But the behavior that i want for this component is the same that the Material-UI dialog have.
So I want to change his state and re-render the component calling it like this:
var open = false;
<AuthDialog open={open} />
And using an outside button that change the open variable to true.
This is my Code:
class AuthDialog extends React.Component {
constructor(props, context){
super(props, context);
this.state = {
open: false,
};
};
handleRequestOpen = () => {
this.setState({
open: true,
});
};
handleRequestClose = () => {
this.setState({
open: false,
});
};
render() {
return (
<div>
<RaisedButton children="login" onTouchTap={ this.handleRequestOpen } />
<Dialog open={this.state.open} onRequestClose={this.handleRequestClose}>
// Content
</Dialog>
</div>
);
}
}
I'm a beginer with JS, so if my code isn't right somewhere I will appreciate some comments too.
Edit: This is my code now after the modification:
class AuthDialog extends React.Component {
constructor(props, context){
super(props, context);
this.state = {
// State of authBody
};
};
render() {
const { onRequestClose, open } = this.props;
return (
<Dialog open={open} onRequestClose={onRequestClose}>
// Body of the dialog
</Dialog>
);
}
}
And in the parent:
closeAuthDialog = () => {
this.setState({
openAuthDialog: false,
});
}
openAuthDialog = () => {
this.setState({
openAuthDialog: true,
});
}
<RaisedButton children="login" onTouchTap={ this.openAuthDialog } />
<AuthDialog open={this.state.openAuthDialog} onRequestClose={this.closeAuthDialog} handleLoginSuccess={ this.handleLoginSuccess } />
In order to achieve that, you need to use the props instead of the local state.
class AuthDialog extends React.PureComponent {
static propTypes = {
open: React.PropTypes.bool.isRequired,
onClose: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired,
};
render() {
const { onClose, onOpen, open } = this.props;
return (
<div>
<RaisedButton children="login" onTouchTap={onOpen} />
<Dialog open={open} onRequestClose={onClose}>
// Content
</Dialog>
</div>
);
}
}
and then from the parent class you need to send the two function and the open prop.
<AuthDialog open={open} onOpen={this.toggleOpenToTrue} onClose={this.toggleOpenToFalse} />
Just get attributes you pass from this.props like this:
...
render() {
const { open, onRaisedButtonClick } = this.props;
return (
<div>
<RaisedButton children="login" onTouchTap={ onRaisedButtonClick } />
<Dialog open={open} onRequestClose={this.handleRequestClose}>
// Content
</Dialog>
</div>
);
}
...
Also, when you have stateless component with no lifecycle methods, you can use functional component; it's much simpler and readable:
const AuthDialog = ({ open, onRaisedButtonClick }) => (
<RaisedButton children="login" onTouchTap={ onRaisedButtonClick } />
<Dialog open={open} onRequestClose={this.handleRequestClose}>
// Content
</Dialog>
)
I am not sure what you're exactly looking for. But from what I have understood, you want to use your AuthDialog component like this;
<AuthDialog open={open} />
And when the <RaisedButton/> is clicked, you need to show the <Dialog /> and also to change the 'open' state/var on the parent as well.
In this case you're gonna wanna go for Flux or Redux to manage your app state. Since two-way binding in React is a bad practice that will be your best shot at achieving this.

How to get a child component to control the parent in react?

I'm building a Modal component and want to make sure that it would easily fit a variety of different possible modal designs.
In my case it would have optional header, content and footer. I don't have an issue with this so far, the code looks like:
class ModalHeader extends React.Component {
render () {
const children = React.Children.map(this.props.children, _.identity);
return (
<div className="modal-header">
{children}
</div>
);
}
}
class ModalFooter extends React.Component {
render () {
const children = React.Children.map(this.props.children, _.identity);
return (
<div className="modal-footer">
{children}
</div>
);
}
}
class ModalContent extends React.Component {
render () {
const children = React.Children.map(this.props.children, _.identity);
return (
<div className="modal-content">
{children}
</div>
);
}
}
class Modal extends React.Component {
render () {
var header, footer, content = null;
React.Children.map(this.props.children, function(child){
if (child.type === ModalHeader) {
header = child;
}
if (child.type === ModalFooter) {
footer = child;
}
if (child.type === ModalContent) {
content = child;
}
});
return (
<div>
{header}
{content}
{footer}
</div>
);
}
}
Now comes the issue of closing the modal. I want it to be possible to close the modal from clicking an element that would be in any of the sub components, no matter, to the left or the right or anything and potentially nested deeply in the markup specific to that component.
Having a component that would wrap a piece of markup be it an X, a close icon or a button, making sure that when that element is clicked, the whole modal is closed.
class ModalCloser extends React.Component {
render () {
const children = React.Children.map(this.props.children, _.identity);
return (
<div onClick={this.close} className="modal-closer">
{children}
</div>
);
}
close () {
// no idea what goes here!!
}
}
There doesn't seem to be in React any easy way for a child component to communicate with the parent.
I would be fine with passing a prop to that closer element that would be the callback function to close the main modal, but at the place where I would be defining the modal, I don't see any way that it would be available:
<Modal>
<ModalHeader>
<div>
<header>
<h1>Title!!</h1>
<div class="float-right">
<ModalCloser closeHandler={???}>
X
</ModalCloser>
</div>
</header>
</div>
</ModalHeader>
<ModalContent>
...
</ModalContent>
</Modal>
Alternatively, I see that I could recursively traverse all the descendants and maybe do something to all descendants of type ModalCloser but I also don't really see a way to do that available.
What would be a good solution allowing to pass such a sub-component as a child or descendant to keep the layout flexibility while giving it the possibility to close the modal in such a case?
Pass a callback from the parent to the child and on close modal, call the callback in the child.
Here is a simpler version of your code, where modalClose is the callback that's being passed to the parent from the child. You can also test it in jsfiddle.
class ModalCloser extends React.Component {
render () {
return (
<div onClick={this.props.close}>
{this.props.children}
</div>
);
}
}
class Main extends React.Component {
modalClose() {
alert('closing')
}
render() {
return (<ModalCloser close={this.modalClose}>X</ModalCloser>);
}
}
ReactDOM.render(
<Main />,
document.getElementById('container')
);
In your jsfiddle you have defined the callback in the render method, therefore it was executed on every rerender of the component. I would strongly recommend you to read Thinking in React multiple times - it will answer all your questions.
Pseudocode
<Parent>
modalClose () {
console.log('modal closed...')
}
<Child modalClose={modalClose} />
<Parent/>
Now in your < Child /> whenever modal closes:
closeHandler = { this.props.modalClose }

Categories

Resources