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

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 }

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.

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.

Scroll position in template React

I have a problem with scroll in React. How can I use scrolling to element in Template? I'd like to scroll to one of the elements in this.props.children by using navigation of Header.
class Template extends React.Component {
handleClick = () => {
const tesNode = ReactDOM.findDOMNode(this.refs.test);
window.scrollTo(0, tesNode.offsetTop);
};
render() {
return (
<div>
<Header click={this.handleClick} ></Header>
{this.props.children}
<Footer></Footer>
</div>
)
}
}
I've made some example here https://codesandbox.io/s/48k35w1107, check this out.

(ReactJS) Function not passing to child component

I am trying to pass props and functions from a parent to a child component in React. However, when I try to call the function in the child component, I receive the error: "Uncaught TypeError: Cannot read property 'bind' of undefined".
This would suggest that the function created in the parent element is not being accessed by the child component. Am I missing a step in my code that would allow me to reference a parent component function from a child component?
I am defining states as props in my parent component, for use in conditional rendering in my child component. I am also defining functions in the parent component, which I am trying to call in my child component.
Provided below is my code:
Note: The flow is supposed to work as follows: Click Sign Up button > Call SignUpClick function > render "SignUp" component (based on the conditional rendering logic outlined in the child component)
The same flow concept would apply if someone clicked the Sign In button.
Parent Component
export default class Parent extends Component{
constructor(props) {
super(props);
this.state = {
SignUpClicked: false,
SignInClicked: false,
};
this.SignUpClick = this.SignUpClick.bind(this);
this.SignInClick = this.SignInClick.bind(this);
}
SignUpClick() {
this.setState({
SignUpClicked: true,
});
}
SignInClick() {
this.setState({
SignInClicked: true,
});
}
render () {
return (
<div>
<Child />
</div>
)
}
}
Child Component
export default class Child extends Component {
render () {
if (this.props.SignUpClicked) {
return(
<SignUp />
) } else if (this.props.SignInClicked) {
return (
<SignIn />
)
} else {
return (
<div>
<div>
<Button onClick={this.SignUpClick.bind(this)}> Sign Up </Button>
</div>
<div>
<Button onClick={this.SignInClick.bind(this)}>Sign In</Button>
</div>
</div>
)
}
}
}
Change the render method in parent class to pass SignUpClick and SignInClick to child.
return (
<div>
<Child SignInClick={this.SignInClick} SignUpClick={this.SignUpClick}/>
</div>
)
Also, in the child class, access the methods as this.props.SignUpClick and this.props.SignInClick
If you think about it, where would the child component grab the references for those functions ? The parent needs to give the child access to those methods, and how does a parent pass data to a child, with props! In your case you are not passing any prop at all to the child, so here is how your parent render method should look like:
render () {
return (
<div>
<Child
signUpClicked={this.state.SignUpClicked}
signInClicked={this.state.SignInClicked}
onSignUpClick={this.onSignUpClick}
onSignInClick={this.onSignInClick}
/>
</div>
);
}
This is how a parent communicates with a child passing down props that are now accessible with this.props. At this point your Child component render method would look like this:
render () {
const {
signUpClicked,
signInClicked,
onSignUpClick,
onSignInClick,
} = this.props;
if (signUpClicked) {
return(<SignUp />);
} else if (signInClicked) {
return (<SignIn />);
} else {
return (
<div>
<Button onClick={onSignUpClick}> Sign Up </Button>
<Button onClick={onSignInClick}>Sign In</Button>
</div>
)
}
}
I used destructuring to help with readability. Hope this helps!

How to use props or state to render or not render 1 out of these 5 forms on click?

I'm looking for advice on how to render one out of five forms in React on the click on one of their five related buttons. I've got it working actually for one form, but I'm pretty sure this is not how things should be done in React.
There is a sidebar with 5 buttons and a content area that displays some content. One of the forms should appear in the content area after a click on their respective buttons in the sidebar. Only one form should be displayed at a time. Both the sidebar and the content area are functions inside of a class called GraphArea that renders them.
AddNodes (one of five buttons in the sidebar):
const AddNodes = ({ showAdd }) => (
<li role="presentation" className="list-inline">
<a href="" className="nav-link" onClick={showAdd}>
<i className="fa fa-plus" aria-hidden="true"/> Add
</a>
</li>
);
export default AddNodes;
The problem is, there are five forms and buttons... There's no way one could reasonably continue like this.
I'm using Apollo link state. All 5 five forms won't be rendered by default, so they've got a default value of false. The values are written to the cache. Apollo queries and mutates a default value in the cache to true on click.
It would mean a lot to me if you could show me the right way here. I'm missing that 'click' of how things work in React and this would be a great oppertunity for it. This is the sidebar's and content area's parent full component:
GraphArea: (parent)
class GraphArea extends Component {
render() {
const { updateEditGraph, editGraph: { mode } } = this.props;
const showAdd = (e) => {
updateEditGraph(
{
variables: {
index: 'mode',
value: 'addNode'
}
});
e.preventDefault()
};
const showLink = (e) => {
updateEditGraph(
{
variables: {
index: 'mode',
value: 'addLink'
}
});
e.preventDefault()
};
return (
<div className="item">
<GraphSidebar showAdd={showAdd} showLink={showLink}/>
<GraphContent/>
</div>
)
}
}
export default compose(
graphql(updateEditGraph, {name: 'updateEditGraph'}),
graphql(getEditGraph, {
props: ({data: {editGraph}}) => ({
editGraph
})
})
)(GraphArea);
GraphContent:
class GraphContent extends Component {
render() {
let content;
if (this.props.editGraph.mode === 'addNode') {
content = <AddNodesForm/>
} else if (this.props.editGraph.mode === 'addLink') {
content = <LinkNodesForm/>
} else {
content = null;
}
return (
<div className="content">
{content}
<Graph/>
</div>
);
}
}
export default compose(
graphql(getEditGraph, {
props: ({data: {editGraph}}) => ({
editGraph
})
})
)(GraphContent);
GraphSidebar:
const GraphSidebar = ({ showAdd, showLink }) => (
<div className="avatars">
<ul>
<AddNodes showAdd={showAdd} />
<SequenceNodes />
<EditNodes />
<LinkNodes showLink={showLink} />
<DeleteNodes />
</ul>
</div>
);
I will answer what should happen in principle.
You say you have a top level component GraphArea that renders both the component for selecting a form, and the container of the forms:
<div>
<GraphSidebar onClickName={handleClick}/>
<GraphContent/>
</div>
GraphSidebar and GraphContent are related in such a way that GraphSidebar changes the state that should be presented on GraphContent, that is they relate to the same state. Because of that their common parent (GraphArea) should hold that state. You then pass the state to the GraphContent which contains the forms.
Make GraphArea a class:
class GraphArea extends React.Component {
constructor(){
super();
this.state = {formId: 1};
}
handleClick(formId) {
this.setState({fromId: formId});
}
render(){
return (
<div>
<GraphSidebar onClickName={handleClick}/>
<GraphContent formId={this.state.formId}/>
</div>
)
}
}
Also I think you say GraphContent doesn't directly render the forms but renders some other component that renders the form. You pass formId as property through the nested components all the way to the component that selects which form to render. For example if GraphContent renders Graph component that renders (selects) the forms you do like this:
<div className="content">
<Graph formId={this.props.formId}/>
</div>
or just
<div className="content">
<Graph formId={formId}/>
</div>
if GraphContent is not a class but a function like
GraphContent = ({formId}) => {
....
}
Let's say Graph is finally the component that renders (selects) the forms then you can use:
Graph = ({formId}) => {
if (formId === 1) {
return(<div>...code for form1</div>)
} else if (formId === 2) {
return(<div>...code for form2</div>)
} ...
}

Categories

Resources