Pass data through levels - open Modal dialog at the end - reactjs

I've been pounding my head on this for many hours and could use some help.
Fundamentally, what I am trying to do is that I have two layers of components - the last of which is supposed to open up a Modal Dialog when you click it. Because of React's idea that a component should only change its own state, I want to propagate that data up and set that variable.
Here is the layout. The FirstLevel.jsx file is the top of my hierarchy. It is followed by SecondLevel.jsx and ThirdLevel.jsx which is where the actual text is clicked.
I don't know about the syntax on anything. Not sure if onUserInput is the right attribute to use or handleUserClick is a built-in thing or a user-defined thing. The idea here is that I am trying to propagate the callback function handleUserClick down into the SecondLevel. Is this right so far?
FirstLevel.jsx
export default class FirstLevel extends React.Component {
constructor(props) {
super(props);
this.state = {
dialogActive: ''
};
this.handleUserClick = this.handleUserClick.bind(this);
}
handleUserClick(dialogActive) {
this.setState({
dialogActive: dialogActive
});
}
render() {
<SecondLevel onUserInput={this.handleUserClick}/>
}
Now, on the SecondLevel, I propagate the callback function even further down into the ThirdLevel. Is this the right way to do it so far?
SecondLevel.jsx
render () {
//other logic and tags before this
<ThirdLevel onUserInput={this.props.onUserInput}/>
}
Now this level is where all hell breaks loose and I have no idea what I am doing. On the click, I want to set the dialogActive variable that was propagated down and then let that float back up. I still don't know if onUserInput is the right thing to do or if the parameter is even correct. Everything is very hazy because it was just gotten by following tutorials and doing lots of Googling and throwing in bits and pieces from everywhere.
ThirdLevel.jsx
export default class ThirdLevel extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.onUserInput(
this.dialogActive.value
);
//show Modal dialog somehow
}
render() {
return <text ref={(input) => this.dialogActive = true} onClick={this.handleClick}> {this.props.value}</text>;
}
Finally, I want to show some modal dialog. Clicking the text needs to reveal a modal dialog. The modal dialog is in another component called MyModal.jsx
In the ThirdLevel, I've tried importing MyModal and tried calling the showModal function. Didn't work. Then, I tried doing some React.createElement(MyModal) stuff and rendering it but that didn't work. All kind of other things that I forgot and just trying stuff until it works but it didn't. What am I doing wrong?
MyModal.jsx
export default class MyModal extends React.Component {
constructor(props) {
super(props);
this.state = {show: false};
this.showModal = this.showModal.bind(this);
this.hideModal = this.hideModal.bind(this);
}
showModal() {
this.setState({show: true});
}
hideModal() {
this.setState({show: false});
}
render() {
return (
<Modal
{...this.props}
show={this.state.show}
onHide={this.hideModal}
dialogClassName={styles.largeDialogBox}
>
//more modal stuff here
);
}
}
Big picture: trying to propagate a click action back up to the top of the hierarchy to set some state and that click action needs to open a modal dialog. Any help would be greatly appreciated.
Edit
Do I do something like this in my ThirdLevel?
handleClick() {
this.props.onUserInput(
this.dialogActive.value
);
//show Modal dialog somehow
var newmodal = new MyModal(this.props);
React.render(React.createElement(newModal));
}
render() {
return <text onClick={this.handleClick}> {this.props.value}</text>;
}
Edit 2
My ThirdLevel render function returns this:
<div>
<MyModal isDialogActive={this.props.dialogActive} onHideModal={this.props.onUserInput}/>
<tspan onClick={this.handleClick}> {this.props.value} </tspan>
</div>
When that gets passed back up into the SecondLevel, it becomes:
<text>
<div>
<MyModal isDialogActive={this.props.dialogActive} onHideModal={this.props.onUserInput}/>
<tspan onClick={this.handleClick}> {this.props.value} </tspan>
</div>
</text>
It's weird to wrap things in the div but that's the only way to make the render work. Even though the resulting DOM has all the tags there, none of the actual tspans are showing.

I believe this will get you on the right path.
I would suggest refactoring the name of some of your functions as it does get a bit confusing. handleUserClick then onUserInput etc. But you've already mentioned that in your OP.
// First Level
export default class FirstLevel extends React.Component {
constructor(props) {
super(props);
this.state = {
dialogActive: false
};
this.handleUserClick = this.handleUserClick.bind(this);
}
handleUserClick(dialogActive) {
this.setState({
dialogActive: dialogActive
});
}
render() {
<SecondLevel onUserInput={this.handleUserClick}/>
}
}
// Second Level
...
render () {
//other logic and tags before this
<ThirdLevel onUserInput={this.props.onUserInput}/>
}
...
// Third Level
export default class ThirdLevel extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.onUserInput(true);
}
render() {
return (
<div>
<MyModal isDialogActive={this.props.dialogActive} onHideModal={this.props.onUserInput} />
<button onClick={this.handleClick}>Show Modal</button>
</div>
)
}
}
// Modal
export default class MyModal extends React.Component {
constructor(props) {
super(props);
this.hideModal = this.hideModal.bind(this);
}
hideModal() {
this.props.onUserInput(false);
}
render() {
return (
<Modal
{...this.props}
show={this.props.isDialogActive}
onHide={this.hideModal}
dialogClassName={styles.largeDialogBox}
>
//more modal stuff here
);
}
}
However I would be asking why you need this logic in the FirstLevel and it cannot be further down the component tree.

Related

props in not passing value to Child Component in React

I am developing a simple food app.
Firstly, it will show dishDetails in MenuComponent
and onClick it will pass Id of a selected dish to a function named as
getDish(dishdetail)
Here i want to send props or state to my CartComponent where it will show details of selected Dish.
Problem-1
Props is not passing to Cart (undefined value)
but dishdetail name,id is showing if i do console.log in MenuComponent
How i can pass props/state to Cart kindly guide me.
//Here im binding my function
class Menu extends Component {
constructor(props) {
super(props);
this.getDish = this.getDish.bind(this);
}
//This is my getDish function(in which i want to send props to Cart)
getDish(dishDetail) {
return (
<React.Fragment>
<Cart dishdetail={dishDetail}/> **//undefined in Cart**
{console.log({dishDetail.name})} **//it is working perfectly**
</React.Fragment>
);
}
Working Fine
From Where I am sending data onClick function
<button
onClick={() => this.getDish(this.props.dishes[index])}
></button>
All components should be rendered from render method. And there behaviour can be controlled using state.
// class Menu
constructor(props) {
super(props);
this.state = {
dishDetail: null
};
this.getDish = this.getDish.bind(this);
}
getDish(selectedDish) {
this.setState({
dishDetail: selectedDish
});
}
render() {
return (
<>
<button onClick={() => this.getDish(this.props.dishes[index]])}>Click Me</button>
{/*Cart is called from render and value passed from state*/}
<Cart dishdetail={this.state.dishDetail}/>
</>
);
}
And your cart class will be re-rendered with your new data
class Cart extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('cart', this.props);
return (
<div>You added {this.props.dishdetail} to cart</div>
);
}
}

React.createRef(); always null

I tried looking at other posts but, can't seem to find a solution. Here is my code:
export default class DrumPad extends React.Component {
constructor(props) {
super(props);
this.audio = React.createRef();
this.testRef = React.createRef();
console.log(this.testRef.current);
}
render() {
return (
<div
ref={this.testRef}
className="drum-pad"
id={this.props.id}
>
<h1>{this.props.keyTrigger}</h1>
<audio
ref={this.audio}
src={this.props.sourceUrl}
className="clip"
id={this.props.keyTrigger}
></audio>
</div>
);
}
}
I'm doing the Drum Machine Project in freeCodeCamp. I'm currently stuck here. I'm trying to trigger the audio with a click, and from my research it seems that I need to use ref to make this work. But I'm stuck. I keep getting null when I console.log() the ref.current.

Child not updating after Parent State changed

I am quite new with React and I have problem bellow
I have a parent component like this:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {count:1};
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
return false;
}
setCount = () => {
this.setState({
count: 2
});
};
render() {
const {name, running, onRun, onStop} = this.props;
return (
<div>
<Test count={this.state.count}/>
<p><a href="#" onClick={this.setCount}>SetCount</a></p>
</div>
);
}
}
And here is Test component
class Test extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
return true;
}
render() {
const {count} = this.props;
return (
<div>
{console.log("Counting")}
<p>{count}</p>
</div>
);
}
}
I have method "shouldComponentUpdate" returns "false" in Parent component because I don't want to re-render it.
My understanding is React know which part of DOM need to be re-rendered. And in this case, the state of Parent changes will re-render "Test" component
But when I run above code, "Test" component does not redender.
Is there anything wrong in my code?
Thanks a lot for your help
You need to return true from your parent's shouldComponentUpdate method.
If you return false, after the initial render it won't update, even if you call a function that calls setState.
Is the refresh of the whole page are you talking about? If thats the case, probably you wanna change your <a> tag to button or use e.preventDefault();.
If not, I am not sure if that is possible. If you setState in the parent, it will rerender parent as well as the children. If you dont want to render the parent then you have to manage individual state management in the child level.
For example,
class Parent extends React.Component {
constructor(props) {
super(props);
}
render() {
const {name, running, onRun, onStop} = this.props;
return (
<div>
<Test/>
</div>
);
}
}
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {count:1};
}
setCount = (e) => {
e.preventDefault();
this.setState({
count: 2
});
};
render() {
const {count} = this.state;
return (
<div>
{console.log("Counting")}
<p>{count}</p>
<p><a href="#" onClick={this.setCount}>SetCount</a></p>
</div>
);
}
}

How to onclick method set for only one input?

First please click for SS.
Right now I have 2 input which has value credit-card and paypal.
I set an onClick event for CreditCard to provide card informations.It works fine but problem is:
Card details doesn`t disappear when I click paypal input. It works just if I click CreditCart input again. I want to make it disappear even I click paypal input. I mean card details should seem only by clicking Credit Card input.
class CreditCart extends React.Component {
constructor(props) {
super(props);
this.state = {show:false};
this.handleClick = this.handleClick.bind(this);
}
handleClick () {
this.setState({ show : !this.state.show})
}
render () {
return (
//2 input here credir-cart has onClick
{this.state.show && <CreditCart/>
}
Second component which includes cart information part:
class CreditCart extends React.Component {
constructor(props) {
super(props);
}
render () {
// information part
}
Your handleClick method is wrong. The setState method is asynchronous and it will try to execute it in batches which means that the previous value might not be updated yet.
Change it to
handleClick() {
this.setState(function (prevState, props) {
return {
show: !prevState.show
};
});
}
See State Updates May Be Asynchronous
I think something like the following should work for you...
class SomeComponent extends Component {
constructor() {
this.state = {
toggle: false
};
}
render() {
return (
<div>
{this.state.toggle
? <CreditCardComponent />
: <PaypalComponent />}
<button
onClick={e => this.setState({ toggle: !this.state.toggle })}
>
Toggle
</button>
</div>
);
}
}

React.js, why my class component doesn't rerender the element, but functional component can work?

I'm new to react.js, just follow the tutorial. Here is my code. At first, i tried to use the class Component 'Greeting' to let it show different words after
clicked the button, but i don't know what's wrong, it doesn't rerender the element, and the construtor() method of Greeting only called once. The commented out code functional Component 'Greeting' works well. Not sure what's the difference :(
class GreetingGuest extends React.Component {
render() {
return (
<h3>hello Guest, Click login button !!! </h3>
);
}
}
class GreetingUser extends React.Component {
render() {
return (
<h3>You have logged in, welcome !!!</h3>
);
}
}
class Greeting extends React.Component {
constructor(props) {
super(props);
console.log('Greeting.state.is_logon = ', props.is_logon);
this.state = {is_logon: props.is_logon};
}
render() {
let welcome_msg = null;
if (this.state.is_logon) {
welcome_msg = <GreetingUser />;
}else {
welcome_msg = <GreetingGuest />;
}
return welcome_msg;
}
}
//function Greeting(props) {
// const is_logon = props.is_logon;
// if (is_logon) {
// return <GreetingUser />;
// }
// return <GreetingGuest />;
//}
class LoginComponent extends React.Component {
constructor(props) {
super(props);
this.state = {is_logon: false};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
is_logon: !prevState.is_logon
}));
}
render() {
let button = null;
let greeting = null;
if (this.state.is_logon) {
button = (
<button onClick={this.handleClick}>Logout</button>
);
greeting = <Greeting is_logon={this.state.is_logon} />
}else {
button = (
<button onClick={this.handleClick}>Login</button>
);
greeting = <Greeting is_logon={this.state.is_logon} />
}
return (
<div>
{greeting}
{button}
</div>
);
}
}
ReactDOM.render(
<LoginComponent />,
document.getElementById('Login')
)
HTML:
<html>
<body>
<div id="Login"></div>
</body>
<html>
The reason the class component doesn't re render, is because you have stored the logged_in prop in state from the constructor, and the constructor is only called once. Also state can only be modified from within the component.
To fix this you have 2 options;
Use componentWillReceiveProps, and update the local state with the new logged_in prop.
componentWillReceiveProps(nextProps) {
if (nextProps.logged_in !== this.state.logged_in) {
this.setState({ logged_in: nextProps.logged_in });
}
}
Or; do not use state but use the prop directly.
render() {
let welcome_msg = null;
if (this.props.is_logon) {
welcome_msg = <GreetingUser />;
}else {
welcome_msg = <GreetingGuest />;
}
return welcome_msg;
}
Where I think you should use the latter, since the parent component already maintains state.
Well to be honest the answer which I posted previously was wrong. It was because the way you posted the question telling that everything works fine when function based component is added. Then I created a project using your code and figured out few issues in your code.
Firstly you are maintaining state locally outside redux here. You are passing down the state of the login from the parent LoginComponent to the child component called Greeting like this.
greeting = <Greeting is_logon={this.state.is_logon} />
This gets passed as a props to the child component which is Greeting in this case. Remember React uses one way data flow.
Now from that child component you can access the data using this.props as shown below. You don't need to maintain any local state what so ever there.
Do the following changes in your Greeting component.
constructor(props) {
super(props);
}
Then make sure you access the values from this.props instead of any local state object like this.
render() {
let welcome_msg = null;
if (this.props.is_logon) {
welcome_msg = <GreetingUser />;
}else {
welcome_msg = <GreetingGuest />;
}
return welcome_msg;
}
}
This solved the issue. Happy Coding !

Resources