I have a parent component App and it has a child Content & Trigger. From the Content component, I have an array of content with radio button and a hidden button. The Trigger component has only one button. 🤔
My question is, how can I trigger a button click inside the Content component from the Trigger component depending on what the user selected on the Content component. 🤯
If my explanation is hard to understand, maybe my code does. Thank you. 🙏
CodeSandbox Sample
https://codesandbox.io/s/laughing-kirch-g2r10?fontsize=14&hidenavigation=1&theme=dark
App.js
import React from "react";
import Content from "./Content";
import Trigger from "./Trigger";
import "./styles.css";
class App extends React.PureComponent {
state = {
selectedOption: ""
};
onRadioButtonChange = name => {
this.setState({
selectedOption: name
});
};
onClickTriggerButton = e => {
const { selectedOption } = this.state;
e.preventDefault();
switch (selectedOption) {
case "opt1":
console.log("trigger click on option 1");
break;
case "opt2":
console.log("trigger click on option 2");
break;
case "opt3":
console.log("trigger click on option 3");
break;
default:
console.log("Select a method...");
}
};
render() {
return (
<div className="App">
<Content onRadioButtonChange={this.onRadioButtonChange} />
<Trigger onClickTriggerButton={this.onClickTriggerButton} />
</div>
);
}
}
export default App;
Content.js
import React from "react";
class Content extends React.PureComponent {
render() {
const { onRadioButtonChange } = this.props;
const tabs = [
{
name: "option_1_name",
content: (
<div className="Option-Method">
<input
type="radio"
name="group"
onChange={() => onRadioButtonChange("opt1")}
/>
Option Medthod 1 - Randon text...
<button
hidden
onClick={() => console.log("option 1 button clicked!")}
/>
</div>
)
},
{
name: "option_2_name",
content: (
<div className="Option-Method">
<input
type="radio"
name="group"
onChange={() => onRadioButtonChange("opt2")}
/>
Option Medthod 2 - Randon text...
<button
hidden
onClick={() => console.log("option 2 button clicked!")}
/>
</div>
)
},
{
name: "option_3_name",
content: (
<div className="Option-Method">
<input
type="radio"
name="group"
onChange={() => onRadioButtonChange("opt3")}
/>
Option Medthod 3 - Randon text...
<button
hidden
onClick={() => console.log("option 3 button clicked!")}
/>
</div>
)
}
];
return (
<form className="Content">
<fieldset id="group">
{tabs.map((attribute, index) => {
const key = `${attribute.name}-${index}`;
return (
<div key={key} className="Option">
{attribute.content}
</div>
);
})}
</fieldset>
</form>
);
}
}
export default Content;
Trigger.js
import React from "react";
class Trigger extends React.PureComponent {
render() {
const { onClickTriggerButton } = this.props;
return (
<div className="Trigger">
<button onClick={onClickTriggerButton}>Trigger Selected Option</button>
</div>
);
}
}
export default Trigger;
Pass ref to the Content component and for each hidden button check for the selectedOption if that matches set ref for that hidden button. Also wrap the Content component in React.forwardRef.
<button
hidden
ref={"opt1" === selectedOption ? ref : null}
onClick={() => console.log("option 1 button clicked!")}
/>
https://codesandbox.io/s/strange-hodgkin-jm84w?file=/src/Content.js:507-671
Related
I'm new to React. I'm trying to do simple validation of a form elements. I'm stuck at validating input data and setting the 'name_error' state as the error msg inside <p> tag. Leaving my code below
// App.js
import React, { Component } from 'react';
import ValidateName from './component/validation';
import Modal from './modal';
class Home extends Component {
state = {
show: false,
name: "",
name_error: ""
}
handleChanges = (e) => {
const name = e.target.name;
const value = e.target.value;
this.setState({[name] : value})
}
render() {
console.log(this)
return (
<div>
<div className='Main'>
{/* < Main /> */}
<button id='add' onClick={()=>{this.setState({show: true})}}>Add</button>
</div>
{this.state.show &&
<Modal>
<form>
<div className='modalContainer'>
<b className='title'>Register</b>
<button id='xmark' onClick={()=>{this.setState({show: false})}} >×</button>
<label for='name' >Name</label><br />
<input type="text" id='name' placeholder='Enter your name here' name="name" onChange={this.handleChanges}/><br />
< ValidateName content={this.state.name} />
<button type='submit'>Sign Up</button>
<button>Cancel</button>
</div>
</form>
</Modal>}
</div>
);
}
}
export default Home;
// Modal.js
import React, { Component } from 'react';
class Modal extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
export default Modal;
//validation.js
class ValidateName extends Component {
err1 ='Please enter any name';
err2 = 'Use only letters in this field';
render() {
const ren = this.props.content.length === 0 ? (<p>{this.err1}</p> ) :
(this.props.content.match(/[a-zA-Z]+$/) ? '' : <p>{this.err2}</p>)
return ren;
}
}
Please suggest an idea to set name_error as 'Please enter any name' or 'Use only letters in this field' when user enters wrong input
State should be like this,
constructor(props){
super(props);
this.state = {
show: false,
name: "",
name_error: ""
}
}
And Also the update state using callback. It would be helpful,
this.setState({[name] : value},() =>{console.log(this.state)})
I have two radios buttons but I want one to show another div if the user clicks yes. I'm new in React.
I have tried this code but the second does not hide the content of the first.
Break down
The user clicks yes,
Another div opens for more info.
The user clicks no,
The div that opened earlier closed.
I know how to toggle in JavaScript but I want to update based on the state.
import React, { Component } from "react";
class Radio extends Component {
constructor(props) {
super(props);
this.state = { clickedYes: false, clickedNo: false };
this.yesHandler = this.yesHandler.bind(this);
this.noHandler = this.noHandler.bind(this);
}
yesHandler() {
this.setState({
clickedYes: !this.state.clickedYes
});
}
noHandler() {
this.setState({
clickedNo: !this.state.clickedNo
});
}
render() {
const radioNo = this.state.clickedNo ?
// Hide div when true
: null;
const radioYes = this.state.clickedYes ? <h1>Yes</h1> : null;
return (
<>
<input
type="radio"
name="release"
id=""
clickedYes={this.state.clickedYes}
onClick={this.yesHandler}
/>
<input
type="radio"
name="release"
clickedNo={this.state.clickedNo}
onClick={this.noHandler}
id=""
/>
{radioYes}
{radioNo}
</>
);
}
}
export default Radio;
Here are two solutions.
With standard React
import React, { Component } from "react";
class Radio extends Component {
constructor(props) {
super(props);
this.state = { status: 0 }; // 0: no show, 1: show yes, 2: show no.
}
radioHandler = (status) => {
this.setState({ status });
};
render() {
const { status } = this.state;
return (
<>
<input type="radio" name="release" checked={status === 1} onClick={(e) => this.radioHandler(1)} />
<input type="radio" name="release" checked={status === 2} onClick={(e) => this.radioHandler(2)} />
{status === 1 && drawYesContent()}
{status === 2 && drawNoContent()}
</>
);
}
}
export default Radio;
With React Hook
import React from "react";
function Radio () {
const [status, setStatus] = React.useState(0) // 0: no show, 1: show yes, 2: show no.
const radioHandler = (status) => {
setStatus(status);
};
return (
<>
<input type="radio" name="release" checked={status === 1} onClick={(e) => radioHandler(1)} />
<input type="radio" name="release" checked={status === 2} onClick={(e) => radioHandler(2)} />
{status === 1 && drawYesContent()}
{status === 2 && drawNoContent()}
</>
);
}
export default Radio;
#bgaynor78 is right, but I would prefer something like the following, because you will not create an invisible dom node
{
this.state.clickedYes && (<div>This shows when the radioYes input is clicked</div>)
}
You could just simply add a style declaration to your div you want to show, then just hook that up to your state object.
<div style={{ display: this.state.clickedYes ? 'block' : 'none'}}>This shows when the radioYes input is clicked</div>
I am making a dashboard component which displays rendered previews and code for HTML snippets. Inside of the dashboard component I am mapping the array of snippets using .map. Each mapped snippet is going to have a delete function (already built) and an update function.
For the update function to work each snippet has it's own child modal component. I need to pass the ID of the snippet to the modal component where I can combine the ID with the new content before updating the database and state.
However, I'm making a mistake somewhere as I pass the ID as props to the modal.
.map used inside of my Dashboard.js Dashboard class component.
{this.state.snippets.map(snippet => (
<>
<div key={snippet._id} className="holder--pod">
<div className="content">
<div className="content__snippet-preview">
Snippet preview
</div>
<div className="content__body">
<h4>{snippet.name}</h4>
<p>{snippet.details}</p>
<p>{snippet._id}</p> //THIS WORKS
<pre>
<code>{snippet.content}</code>
</pre>
</div>
<div className="content__button">
<button onClick={this.handleDelete(snippet._id)}>
Delete
</button>
<button type="button" onClick={this.showModal}>
Open
</button>
</div>
</div>
</div>
<Modal
sid={snippet._id} //PASS ID HERE
show={this.state.show}
handleClose={this.hideModal}
></Modal>
</>
))}
This renders the snippets below (3 snippet pods, with their database ID included).
The open button opens the modal (Modal.js) below.
import React, { Component } from 'react'
import api from '../api'
export default class Modal extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
details: '',
content: '',
message: null,
}
}
handleInputChange = event => {
this.setState({
[event.target.name]: event.target.value,
})
}
handleClick = id => event => {
event.preventDefault()
console.log(id)
}
render() {
const { sid, show, handleClose } = this.props
console.log(sid)
const showHideClassName = show ? 'modal display-flex' : 'modal display-none'
return (
<div id="Modal" className={showHideClassName}>
<div id="modal-main">
<h4>Edit snippet {sid}</h4>
<form>
Name:{' '}
<input
type="text"
value={this.state.name}
name="name"
onChange={this.handleInputChange}
/>{' '}
<br />
Details:{' '}
<input
type="text"
value={this.state.details}
name="details"
onChange={this.handleInputChange}
/>{' '}
<br />
Content:{' '}
<textarea
value={this.state.content}
name="content"
cols="30"
rows="10"
onChange={this.handleInputChange}
/>{' '}
<br />
<button onClick={this.handleClick(sid)}>TEST ME</button>
</form>
<button onClick={handleClose}>Close</button>
{this.state.message && (
<div className="info">{this.state.message}</div>
)}
</div>
</div>
)
}
}
The console.log just under the render actually pastes the correct 3 ID's the console.
However, calling the ID (sid) within the Modal.js return will only show the last snippet ID, no matter which Modal I open. The same goes for pushing that ID to the handleClick function where I intend to combine the ID with an update package.
Solution below as initiated by HMR in the comments.
The problem was all the modals were showing and just the last one was visible.
Fixed by moving the modal out of the .map and instead updating the ID from within the .map to the state and passing the state ID to a new nested component within the modal.
Also switched to using dynamic CSS to show and hide the modal based on the state.
Dashboard.jsx
export default class Snippets extends Component {
constructor(props) {
super(props)
this.showModal = React.createRef()
this.state = {
snippets: [],
show: false,
sid: '',
}
}
handleDelete = id => event => {
event.preventDefault()
api
.deleteSnippet(id)
.then(result => {
console.log('DATA DELETED')
api.getSnippets().then(result => {
this.setState({ snippets: result })
console.log('CLIENT UPDATED')
})
})
.catch(err => this.setState({ message: err.toString() }))
}
handleModal = id => {
this.setState({ sid: id })
this.showModal.current.showModal()
}
//<div id="preview">{ReactHtmlParser(snippet.content)}</div>
render() {
return (
<>
<Modal ref={this.showModal} handleClose={this.hideModal}>
<ModalUpdate sid={this.state.sid} />
</Modal>
<div className="Dashboard">
<div className="wrapper">
<div className="container">
<div className="holder">
<div className="content">
<div className="content__body">
<h3>Dashboard</h3>
</div>
</div>
</div>
<div className="break"></div>
{this.state.snippets.map(snippet => (
<div key={snippet._id} className="holder--pod">
<div className="content">
<div className="content__snippet-preview">
Snippet preview
</div>
<div className="content__body">
<h4>{snippet.name}</h4>
<p>{snippet.details}</p>
<p>{snippet._id}</p>
<pre>
<code>{snippet.content}</code>
</pre>
</div>
<div className="content__button">
<button onClick={this.handleDelete(snippet._id)}>
Delete
</button>
<button
type="button"
onClick={() => this.handleModal(snippet._id)}
>
Open
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</>
)
}
Modal.jsx
import React, { Component } from 'react'
export default class Modal extends Component {
constructor(props) {
super(props)
this.state = {
show: false,
}
}
showModal = () => {
this.setState({ show: true })
}
hideModal = () => {
this.setState({ show: false })
}
render() {
return (
<div
id="Modal"
style={{ display: this.state.show === true ? 'flex' : 'none' }}
>
<div id="modal-main">
<h4>Edit snippet </h4>
{this.props.children}
<button onClick={() => this.hideModal()}>Close</button>
</div>
</div>
)
}
}
ModalUpdate.jsx
import React, { Component } from 'react'
export default class ModalUpdate extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
details: '',
content: '',
message: null,
}
}
// handleInputChange = event => {
// this.setState({
// [event.target.name]: event.target.value,
// })
// }
// handleClick = id => event => {
// event.preventDefault()
// console.log(id)
// }
render() {
return <h4>ID = {this.props.sid}</h4>
}
}
I am not sure about the handleDelete function,. but replacing the line should solve the issue probably
<button onClick={() => this.handleDelete(snippet._id)}>
One potential issue is the this.handleDelete(snippet._id) will fire immediately rather than onClick, so you will need to add an anonymous function in the event listener:
() => this.handleDelete(snippet._id)
instead of
this.handleDelete(snippet._id)
I'm using React and firebase to create a simplified slack and using MDl for styles. I'm trying to integrate a button that opens up a dialog box to get some user input, in this case the name of a new chat room, and then when submitted it will send the data to firebase and store it in the rooms array and display the new room name in the list. I have already set up the form to get user input and then I tried to refactor it to work in a dialog box and I seem stuck on how to get the dialog box to work. Here is my whole component:
import React, { Component } from 'react';
import './RoomList.css';
import dialogPolyfill from 'dialog-polyfill';
class RoomList extends Component {
constructor(props) {
super(props);
this.state = { rooms: [] };
this.roomsRef = this.props.firebase.database().ref('rooms');
this.handleChange = this.handleChange.bind(this);
this.createRoom = this.createRoom.bind(this);
}
handleChange(e){
this.setState({ name: e.target.value });
}
createRoom(e) {
e.preventDefault();
this.roomsRef.push({ name: this.state.name });
this.setState({ name: "" });
}
componentDidMount() {
this.roomsRef.on('child_added', snapshot => {
const room = snapshot.val();
room.key = snapshot.key;
this.setState({ rooms: this.state.rooms.concat( room ) });
})
}
dialogBox(e){
const dialogButton = document.getElementsByClassName('dialog-
button');
const dialog = document.getElementById('dialog');
if (! dialog.showModal) {
dialogPolyfill.registerDialog(dialog);
}
dialogButton.onClick( (e) => {
dialog.showModal();
});
dialog.document.getElementsByClassName('submit-close').onCLick(
(e) => {
dialog.close();
});
}
render() {
const roomlist = this.state.rooms.map( (room) =>
<span className="mdl-navigation__link" key={room.key}>
{room.name}</span>
);
const newListForm = (
<div id="form">
<button className="mdl-button mdl-js-button mdl-button--
raised mdl-js-ripple-effect dialog-button">Add room</button>
<dialog id="dialog" className="mdl-dialog">
<h3 className="mdl-dialog__title">Create Room name</h3>
<div className="mdl-dialog__content">
<p>Enter a room name</p>
</div>
<div className="mdl-dialog__actions">
<form onSubmit={this.createRoom}>
<div className="mdl-textfield mdl-js-textfield">
<input type="text" value={this.state.name}
className="mdl-textfield__input" id="rooms" onChange=
{this.handleChange} />
<label className="mdl-textfield__label"
htmlFor="rooms">Enter room Name...</label>
<button type="submit" className="mdl-button submit-
close">Submit</button>
<button type="button" className="mdl-button submit-
close">Close</button>
</div>
</form>
</div>
</dialog>
</div>
);
return (
<div className="layout mdl-layout mdl-js-layout mdl-layout--
fixed-drawer mdl-layout--fixed-header">
<header className="header mdl-layout__header mdl-color--
grey-100 mdl-color-text--grey-600">
<div className="mdl-layout__header-row">
<span className="mdl-layout-title">Bloc Chat</span>
<div className="mdl-layout-spacer"></div>
</div>
</header>
<div className="drawer mdl-layout__drawer mdl-color--blue-
grey-900 mdl-color-text--blue-grey-50">
<header className="drawer-header">
<span>{newListForm}</span>
</header>
<nav className="navigation mdl-navigation mdl-color--
blue-grey-800">
<div>{roomlist}</div>
<div className="mdl-layout-spacer"></div>
</nav>
</div>
</div>
);
}
}
export default RoomList;
Here is a simple sample on how to build a modal with the new portal API provided from React v16.xx
Working demo can be found here. Just use the dropdown to navigate to the simple portal demo. A snapshot of the full code base can be found on github.
Working Code
import React, { Component } from "react";
import { createPortal } from "react-dom";
import "./simple-portal.css";
export default class SimplePortal extends Component {
constructor() {
super();
this.state = {
list: [],
input: "",
showDialog: false
};
this._onChange = this._onChange.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}
_onChange(e) {
let input = e.target.value;
this.setState({ input });
}
_onSubmit(e) {
e.preventDefault();
let showDialog = false;
// Dont Mutate the State!!!
let list = this.state.list.slice();
list.push(this.state.input);
this.setState({ showDialog, list, input: "" });
}
render() {
const { showDialog, list, input } = this.state;
return (
<div className="container">
<div>
<button
className="btn"
onClick={e =>
this.setState({
showDialog: !showDialog
})
}
>
Add Item
</button>
</div>
{/* Render Items from List */}
<div>
<ul>
{list.map(item => {
return <li key={item}>{item}</li>;
})}
</ul>
</div>
{/* Show Modal - Renders Outside React Hierarchy Tree via Portal Pattern */}
{showDialog === true ? (
<DialogModal>
<div className="dialog-wrapper">
<h1>New List Item</h1>
<form onSubmit={this._onSubmit}>
<input type="text" value={input} onChange={this._onChange} />
</form>
</div>
</DialogModal>
) : null}
</div>
);
}
}
class DialogModal extends Component {
constructor() {
super();
this.body = document.getElementsByTagName("body")[0];
this.el = document.createElement("div");
this.el.id = "dialog-root";
}
componentDidMount() {
this.body.appendChild(this.el);
}
componentWillUnmount() {
this.body.removeChild(this.el);
}
render() {
return createPortal(this.props.children, this.el);
}
}
I don't see any event listeners on your buttons that would trigger a rendering of the modal. I would approach this by specifying an onClick event that would update the state which would render the modal/dialogue box.
Another solution, which may be the way you are thinking, is to have the state change to render the modal/dialogue box as visible from within the createRoom() function. Remember, updating state or getting new props will trigger a rendering of the component. You are trying to update the state to re-render your component with the modal/dialogue being shown.
Sorry if I misunderstood the question or your goal.
What implementation is needed to render different components from render method. As you can see below the idea is that Survey component receives an array which contains different components names (could be Input, CheckList, Dropdown, File). The array passes as property to Survey Component is generated properly depending of what button is clicked, but at the time to render different components is not working. I'm using JsComplete to test it.
const Dropdown = () =>{
return(
<div>
<select>
<option value="initial" selected>Select...</option>
<option value="Option ">Option 1</option>
<option value="Option ">Option 2</option>
</select>
</div>
)
}
const Checklist = () =>{
return(
<div>
<h4>Question name</h4>
<label>
Option 1:
<input
name="pl"
type="checkbox" />
</label>
<label>
Option 2:
<input
name="tz"
type="checkbox" />
</label>
</div>
)
}
const Input = () =>{
return(
<div>
<label>
Question name:
<input
name="input"
type="text" />
</label>
</div>
)
}
const File = () =>{
return(
<div>
<label>
Upload:
<input
name="file"
type="file" />
</label>
</div>
)
}
class Survey extends React.Component {
constructor(props){
super(props);
}
render(){
var ChildName ;
for (var i = 0; i < this.props.components.length; i++) {
log("Log:" + this.props.components[i]);
ChildName = this.props.components[i];
return <ChildName />;
}
return (
false
)
}
}
class Form extends React.Component {
handleSubmit = (name) => {
this.props.onSubmit(name);
};
render() {
return (
<div id="components">
<button onClick={()=>this.handleSubmit("Input")} name="Input">Input</button>
<button onClick={()=>this.handleSubmit("Checklist")} name="Checklist">Checkbox</button>
<button onClick={()=>this.handleSubmit("Dropdown")} name="Dropdown">Dropdown</button>
<button onClick={()=>this.handleSubmit("File")} name="File">File</button>
<div id="new-question">
</div>
</div>
)
}
}
class App extends React.Component {
state = {
components: []
};
addNewElement = (element) => {
this.setState(prevState => ({
components: prevState.components.concat(element)
}));
};
render() {
return (
<div>
<Form onSubmit={this.addNewElement} />
<Survey components={this.state.components} />
</div>
);
}
}
ReactDOM.render(<App />, mountNode);
Try this. Dont pass string in handleSubmit method. Instead pass component itself like this:
class Form extends React.Component {
handleSubmit = (name) => {
this.props.onSubmit(name);
};
render() {
return (
<div id="components">
<button onClick={()=>this.handleSubmit(Input)} name="Input">Input</button>
<button onClick={()=>this.handleSubmit(Checklist)} name="Checklist">Checkbox</button>
<button onClick={()=>this.handleSubmit(Dropdown)} name="Dropdown">Dropdown</button>
<button onClick={()=>this.handleSubmit(File)} name="File">File</button>
<div id="new-question">
</div>
</div>
)
}
}
Also in you survey component return the elements like this
class Survey extends React.Component {
constructor(props) {
super(props);
}
render() {
if (this.props.components.length === 0) {
return null;
}
const renderCommpos = this.props.components.map((Elem, index) => {
return <Elem key={index} />
});
return (
<div>
{renderCommpos}
</div>
);
}
}
Also notice the Elem in map function. When it comes to react component jsx needs the first letter capital. So doesn't matter what variable you keep at place of Elem, you should always keep the first letter capital.
The render method for your survey component should be like this :
render(){
const { components } = this.props;
return (
<div>
{
components.map((c, index) => {
return (
<div key={`one-of-components-${index}`}>
{c}
</div>
);
})
}
</div>
);
}
Now it will return all the components in the props.
Try this out.
const Survey = ({ components }) => {
const Components = components.map(
( component, index ) => {
return (
<div key={ index }>
{ component }
</div>
);
}
);
return (
<div>
{ Components }
</div>
);
};
In your for loop you're returning from the function on the first component. Add them to an array and then return the array. On another note, I used a functional stateless component here. I don't see the need for the class overhead.