React - Call parent method from within child component - reactjs

Apologies for the noob question but I'm reasonably new to React. I've seen a few similar questions on here to what I'm stuck with but I believe my requirements are slightly different.
I'm creating an Accordion component which is the parent of child AccordionItem components. I have given each AccordionItem its own state to keep track on whether the item is in an open or closed state. This works well and I now have a working accordion where each item can open or close when you click on the item's title.
However I now want to add functionality where if an AccordionItem is already in an open state it will close when another AccordionItem is selected. I believe I will need a state array or object within my Accordion.js which keeps track of currently selected AccordionItems which will then be updated on each click.
I'm struggling to work out how to pass a parent method down to the child component though.
My App.js currently looks like the below:
class App extends Component {
render() {
return (
<Accordion>
<AccordionItem title="Question One Title" itemid="question1" openOnLoad onChange={this.updateSelectedItems}>
<p>here is some text 1</p>
</AccordionItem>
<AccordionItem title="Question Two Title" itemid="question2" onChange={this.updateSelectedItems}>
<p>here is some text 2</p>
</AccordionItem>
<AccordionItem title="Question Three Title" itemid="question3" onChange={this.updateSelectedItems}>
<p>here is some text 3</p>
</AccordionItem>
</Accordion>
);
}
}
All the examples I've seen online show the parent having a prop which passes a reference of the chosen parent method down to the child component. However because my AccordionItems are defined in App.js rather than Accordion.js this.updateSelectedItems doesn't exist in this file.
I don't want to move the AccordionItems into the Accordion because then every Accordion would have the same data.
Thoughts?

Here's something I threw together. Working JSFiddle here: https://jsfiddle.net/gkysmsqz/6/
Accordion should maintain the active index for which accordion section should be shown. You can clone each child and add some extra props to them before render (active and toggleSection in the example below):
class App extends React.Component {
render() {
return (
<Accordion>
<AccordionItem title="Question One Title" itemid="question1" openOnLoad onChange={this.updateSelectedItems}>
<p>here is some text 1</p>
</AccordionItem>
<AccordionItem title="Question Two Title" itemid="question2" onChange={this.updateSelectedItems}>
<p>here is some text 2</p>
</AccordionItem>
<AccordionItem title="Question Three Title" itemid="question3" onChange={this.updateSelectedItems}>
<p>here is some text 3</p>
</AccordionItem>
</Accordion>
);
}
}
class Accordion extends React.Component {
state = {
activeIndex: 0
};
toggleSection = index => {
this.setState({
activeIndex: index
});
};
render() {
const rows = this.props.children.map( (child, i) => {
return React.cloneElement(child, { active: i === this.state.activeIndex, toggleSection: this.toggleSection.bind(this, i) });
});
return (
<div>
{rows}
</div>
);
}
}
const AccordionItem = props => (
<div>
<span onClick={props.toggleSection}>{props.title}</span>
<div className={props.active ? 'active accordion-item' : 'accordion-item'}>
{props.children}
</div>
</div>
);

I think you might find helpful this article https://reactjs.org/docs/composition-vs-inheritance.html.
In your case you might do something like this inside your Accordion component's render method:
render() {
return (
<div>
{
this.props.children.map(
child => {
child.passedMethod = this.updateSelectedItems;
return child;
}
)
}
</div>
);
}

If Accordion is keeping track of the AccordionItem components, then you should just let Accordion handle the rendering of AccordionItems.
App.js
class App extends Component {
render() {
return
<Accordion />;
}
Accordion.js
updateItems = () => ...
render() {
return (
<div>
<AccordionItem title="Question One Title" itemid="question1" openOnLoad onChange={this.updateSelectedItems}>
<p>here is some text 1</p>
</AccordionItem>
<AccordionItem title="Question Two Title" itemid="question2" onChange={this.updateSelectedItems}>
<p>here is some text 2</p>
</AccordionItem>
<AccordionItem title="Question Three Title" itemid="question3" onChange={this.updateSelectedItems}>
<p>here is some text 3</p>
</AccordionItem>
</div>
);
}

Go by the Reactjs basics, maintain your state in Parent (Accordion) and pass that state to all child (AccordionItem). Also, you need to pass an event handler (which will update parent state), it will be called when any child (AccordionItem) is selected.

Related

Show / Hide element by data attribute (react=

How can i show/hide elements in a list using react? I have buttons with data-attribute
and i want when click show elements with this classname and hide the others.
example here:
class ModelosItems extends React.Component {
handleCheck(e) {
console.log(e.currentTarget.dataset.gama );
}
render() {
return (
<section className="section">
<div className="container">
<h2 className="title is-size-4 has-text-centered is-uppercase has-text-weight-bold">Gama kia</h2>
<div className="tabs-container">
<div className="fade"></div>
<div className="tabs">
<ul>
<li className="is-active">Gama Completa</li>
<li>Cidatidos e Familiares</li>
<li>Suv e Crossover</li>
</ul>
</div>
</div>
<ModelsList />
</div>
</section>
);
}
};
export default ModelosItems;
Thank you!
For hide the other datas and just show the special data, you have to filter your list. After, you can for example, add a CSS class whose this class hide the other datas, and you'll have just your special data showed.
A tutorial : https://www.youtube.com/watch?v=HqQ-kOchqHM
Utilize local state for this. Initialize it with the first value (probably todos) and update it when you select another type correspondingly.
Next, pass this state data attribute to ModelsList component as a prop and simply filter the items within the list.
Introduce state to ModelosItems
constructor(props) {
super(props);
this.state = {currentGama: null};
}
on click update state
handleCheck(e) {
this.setState({
currentGama: e.currentTarget.dataset.gama
});
}
Add new prop to ModelosItems gama and feed currentGama state value to it
<ModelsList gama={this.state.gama} />
In your ModelList component, I'm guessing here
cars.filter(car => car.gama === this.props.gama).map(...
this will result in rendering only cars that have right gama. This does not use classnames to hide cars. But I think this is what you need.

How to change the state in conditional - React

I need to change the state of sibling components in my React App.
I use state and setstate
I need to change the state of sibling components. When loading the page, there must exist (visible in the page) <BkUser /> and when clicking "button id =" ds-visual "it must be deleted (<BkUser /> mustn't exist) and there must exist <BkDescanso />.
When you click on <BkSleep /> (in the div parent) you should remove <BkDescanso /> and show <BkUser />
This is the web.
There should never be <BkUser/> and <BkSleep> at the same time. <Bkuser /> is the blue block and <BkDescanso /> is the red block
This is my code:
Edit: I edit my original code because I fix the problem. This is the final OK Code. In the end the most important thing was the state conditional
{
this.state.usuario ? (<BkUser handleClick = {this.handleClick} usuario={this.state.usuario}/>): (<BkDescanso handleClick = {this.handleClick} usuario={this.state.usuario}/>)}
import React, { Component } from 'react';
class Header extends Component {
constructor(props) {
super(props);
this.state = {
usuario: true,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
usuario: !state.usuario
}));
//alert("Works button");
}
render(){
return (
<header className="header">
<div className="configuracion">
{
this.state.usuario
? (
<BkUser handleClick = {this.handleClick} usuario={this.state.usuario}/>
)
: (
<BkDescanso handleClick = {this.handleClick} usuario={this.state.usuario}/>
)}
<div className="content-btn">
<button id="config" className='btn btn--rounded'><span className="ico-configuracion"></span></button>
<button id="salir" className='btn btn--rounded'><span className="ico-exit"></span></button>
</div>
</div>
</header>
);
}
}
class BkUser extends Component{
render(){
return ((
<div className='usuario'>
<img src="../img//usuario.svg" alt="Imagen usuario"/>
<div className="content-usuario">
<span id="nm-usuario" className="h4">Hermione Jane Granger</span>
<span id="tp-usuario" className="h5">Supervisor</span>
</div>
<div className="content-descansos">
<div className="botones">
<button id="ds-visual" className='btn btn--rounded' onClick={this.props.handleClick}><span className="ico-visual"></span></button>
<button id="ds-admin" className='btn btn--rounded'><span className="ico-tiempo-administrativo"></span></button>
<button id="ds-otros" className='btn btn--rounded'><span className="ico-descanso"></span></button>
</div>
<div className="ds-actual">
<span id="ds-tipo">Administrativo</span>
<span id="ds-tiempo">00:08:47</span>
</div>
</div>
</div>
));
}
}
class BkDescanso extends Component {
render(){
return ((
<div className='usuario descanso' onClick={this.props.handleClick}>
<h3>Finalizar descanso</h3>
</div>
));
}
}
export default Header;
Right now handleClick works but always exist BkUser and BkDescanso. I need only one to exist. If you click on id = "ds-visual" the bkUser block should disappear and BkDescanso appear. Then if you click on div className = 'user rest' in BkUser there should only be BkDescanso.
I think that it is not able to know when it is true and when it is false to show or hide
Thanks a lot for the help.
You're missing two things:
First you have to pass the handleClick function to the BkUser component, and then you have to call it via this.props.handleClick.
...
<BkUser handleClick={this.handleClick} usuario={this.state.usuario} />
....
<button
id="ds-visual"
className="btn btn--rounded"
onClick={this.props.handleClick}
>
ds-visual
<span className="ico-visual" />
</button>
CodeSandbox here.
Read more here.
You can change the state of the siblings by passing a function from the parent via props into them.
In the end your siblings are the children of their parent.
You can read this articles on how to change the state of child components.
React js change child component's state from parent component
https://medium.freecodecamp.org/react-changing-state-of-child-component-from-parent-8ab547436271
An other thing you could look into would be React Redux.

Button in li element to display more details

I would like to display the modal window with more details when I click on record. I'm using OfficeUI.
My parent component:
public render() {
{
return (
<div>
{this.props.items
.map((item: IListItem, i: number): JSX.Element => <ul className="list-group">
<li className="list-group-item">
<div className={styles.text}>
<p>{item.Id}</p>
</div>
<DefaultButton onClick={this.showModalEvent} text="Open Modal" />
{this.state.showPopup
? <ModalPopUpItem item={item}
showModalState={this.state.showPopup}
showModal={this.showModalEvent}/> : null
}
</li>
</ul>)}
</div>
);
}
private showModalEvent = (): void => {
this.setState({ showPopup: !this.state.showPopup });
}
My child:
export class ModalPopUpItem extends React.Component<IModalProps> {
public render() {
return (
<Modal
isOpen={this.props.showModalState}
onDismiss={this.props.showModal}
isBlocking={false}
containerClassName="ms-modalExample-container">
<div className="ms-modalExample-header">
<span>{this.props.item.date}</span>
</div>
</Modal>
);
}
}
When I click my DeafultButton on parent component it invokes and displays Modal for every item, how can I limit this to only one current clicked item. I tried with i: number, but I couldn't figure it out.
Since you are using single state for every child, so once you change this.state.showPopup to true, every modal will showed up. So you might can change the showModalEvent method.
private showModalEvent = (id: number): void => {
this.setState({ showPopupId: id });
and the render will be look like
return (
<div>
{this.props.items
.map((item: IListItem, i: number): JSX.Element => <ul className="list-group">
<li className="list-group-item">
<div className={styles.text}>
<p>{item.Id}</p>
</div>
<DefaultButton onClick={() => this.showModalEvent(item.Id)} text="Open Modal" />
{this.state.showPopupId === item.Id
? <ModalPopUpItem item={item}
showModalState={this.state.showPopup}
showModal={this.showModalEvent} /> : null
}
</li>
</ul>)}
</div>
);
Since this method only store one id at a time, this mean only one modal will showed up. If you want to show more modal at the time, you could change it to array or something.
Your parent component only has one flag to store the showPopup. This means that when you click on one of your buttons you set this flag for the whole parent component and that means that the whole list of your child components is evaluated and this.state.showPopup will be true.
You need to find a way to limit the effect of clicking the button to the item the button was clicked on.
You could, for example, not set the showPopup flag on the parent component, but on the item.
This could work, but you would have to revisit the way you include the ModalPopUpItem.
private showModalEvent = (item): void => {
item.showPopup = !item.showPopup;
}

How to display nested HTML content with {this.props.children}?

I'm trying to display standard HTML content that is nested within a React modal component. I thought using {this.props.children} within the component would allow the nested HTML to be displayed, but the paragraph doesn't appear within the modal when it's open. I want to keep the content within the HTML file and out of the React Component.
A codepen of the issue is here.
HTML:
<div id="testModal">
<p>Content to be displayed within modal</p>
</div>
JS:
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false
};
// bind functions
this.closeModal = this.closeModal.bind(this);
this.openModal = this.openModal.bind(this);
}
// close modal
closeModal() {
this.setState({isModalOpen: false});
}
// open modal
openModal() {
this.setState({isModalOpen: true});
}
render() {
return (
<React.Fragment>
<div className="link" onClick={this.openModal}>Click Me</div>
<div id="modal" className="modal__outter" style={{
display: this.state.isModalOpen
? 'block'
: 'none'
}}>
<div className="modal__overlay" onClick={this.closeModal}></div>
<div onClick={this.state.closeModal}></div>
<div className="modal__container">
<div className="modal__header">
<button onClick={this.closeModal} className="link">X</button>
</div>
<div className="modal__content">
{this.props.children}
</div>
<div className="modal__footer">
<button className="link" onClick={this.closeModal}>Close</button>
</div>
</div>
</div>
</React.Fragment>
);
}
}
ReactDOM.render(
<Modal />,
document.getElementById('testModal')
);
this.props.children refers to component passed in from your React code, not from HTML file.
So pass the modal content in ReacDOM.render
ReactDOM.render(
<Modal>
<p>Content to be displayed within modal</p>
</Modal>,
document.getElementById('testModal')
);
Here is the forked CodePen.
Answer to the comment is there a way to pass the content in from the HTML file
Yes, you can but you still need to pass the content somehow.
I've used document.getElementById to get the modal content, and passed it to <Modal> as a child.
(If you are planning to use this.props.children that is. Or else you can just get the content from HTML with document.get/querySelect...)
Here is the updated CodePen.

Calling a function for ALL child components

I have 3 components. They parent layout, a select box, and a panel this is generated x times from some data.
<Layout>
<Dropdown>
<Panel>
<Panel>
<Panel>
I'm trying to make it so when the select value changes, the contents of each panel changes. The changes are made by doing some math between the new select value, and data that is stored in the panel component. Each panel has different data.
Layout.js
updateTrueCost(selected){
this.refs.panel.setTrueCost
}
render(){
return (
<div>
<div class="row">
Show me the true cost in
<CurrencyDrop currencyChange = {(e) => this.updateTrueCost(e)} data = {this.state.data} />
</div>
<div class="row">
{this.state.data.map((item, index) => (
<Panel ref="panel" key = {index} paneldata= {item} />
))}
</div>
</div>
);
}
Panel.js
setTrueCost(selected){
//Do some math
this.setState({truecost: mathresult})
}
render(){
return(
<div>
{this.state.truecost}
</div>
)
}
CurrencyDrop.js
onHandelChange(e){
this.props.currencyChange(e);
}
render(){
return(
<Select
onChange={this.onHandelChange.bind(this)}
options={options} />
)
}
The current result is only the last panel updates when the select changes. I'm guessing I'm doing something wrong with the ref handling, but I must not be searching the right terms because I can't find any related questions.
Instead of calling ref's method use React build-in lifecycle methods.
class Panel extends React.Component {
componentWillReceiveProps (newProps) {
// compare old and new data
// make some magic if data updates
if (this.props.panelData !== newProps.panelData) {
this.setState({trueCost: someMath()});
}
}
render () {
return <div>{this.state.trueCost}</div>
}
}
Then just change input props and all data will be updated automatically.

Resources