Tie an action to a state change in pure React - javascript

Learning React so might be a bit nooby question. Consider this code:
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle(on) {
this.setState({
isOn: on
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: false
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
});
this.props.handleToggle(on);
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
As you can see currently when the button inside the Container is clicked handleButtonToggle() is fired which changes the state of the button itself and then calls a function to change the state of the parent Container. Is this the React way to do it? At the moment Container state is changed when the function handleButtonToggle is fired. Ideally I would want Container state to be dependent on MyButton state directly (cuz maybe in future there will be ways to set button state other than through handleButtonToggle, and I don't want to manually call this.props.handleToggle every time the state changes.). In other words is there a way to do something like this.props.handleToggle(this.state.pressed) in the button component when its state changes.
Codepen

After reviewing the code, a better way to write the Button component is to make it a controlled component. If the container component requires to maintain the pressed state, a controlled button component would receive the pressed state from the container component as props.
<MyButton pressed={this.state.pressed} onToggle={this.handleToggle} />
And the render method of the Button component should be:
render() {
return (
<div>
<button onClick={this.props.onToggle}>
{this.props.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
The actual button toggle will be done in the handleToggle method of the container component:
handleButtonToggle() {
let { pressed } = this.state;
pressed = !pressed;
this.setState({
pressed
});
}

You can either pass a callback as the second argument to setState or implement componentDidUpdate on your component to wait for state changes. The smallest change would be the former:
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
}, () => {
this.props.handleToggle(on);
});
}
or with componentDidUpdate:
componentDidUpdate(prevProps, prevState) {
if (prevState.on !== this.state.on) {
this.props.handleToggle(this.state.on);
}
}

Here is a working codepen: https://codepen.io/damien-monni/pen/XRwewV.
I tried to keep as much as I can of your code so you can focus on really needed changes.
You need to create a controlled component. The state of your button will be stored in the container and pass by props to the child MyButton component.
/*
* A simple React component
*/
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle() {
this.setState({
isOn: !this.state.isOn
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton pressed={this.state.isOn} handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: this.props.pressed
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
this.props.handleToggle();
}
componentWillReceiveProps(nextProps) {
if (nextProps.pressed !== this.props.pressed) {
this.setState({ pressed: nextProps.pressed });
}
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
You need to do it because you want to share a state between two of your components. This is documented here.
I let you look at the codepen and ask your questions about it. ;)

Related

React Update Child's state from Parents Component

When chartValue: true an object appears on the chart.
class Chart extends Component {
constructor(props) {
super(props);
this.state = {
chartValue: false,
};
}
componentWillReceiveProps(nextProps) {
this.setState({
chartValue: nextProps.chartValue
});
}
render() {
return (
<div>{this.state.chartValue}</div>
);
}
}
I want to change the value to true via a button in my app component.
class App extends Component {
constructor(props) {
super(props);
this.state = ({
chartValue: false,
});
}
callChartValue() {
this.setState({
chartValue: true
})
}
render() {
return (
<div>
<button onClick={this.callChartValue}>call</button>
<Chart chartValue={this.state.chartValue} />
</div>
)}
}
}
It seems like changing the state in my App.js doesnt translate to a change in the state of the chart component. I've tried without using componentWillReceiveProps but it hasn't worked either.
You have two main issues in your code:
1. You should bind your methods
Otherwise, you won't have access to this inside them.
constructor(props) {
...
// Add this to your constructor
this.callChartValue = this.callChartValue.bind(this);
...
}
2. You shouldn't use strings for referencing the methods
render() {
return (
...
// Wrong
<button onClick="callChartValue">call</button>
// Right
<button onClick={this.callChartValue}>call</button>
...
)}

unable to find a React.Component by id

I have a React.Component with render() declared this way:
render(){
return <div>
<button id="butt" onClick={()=> $("#noti").change("test") }>click me</button>
<Notification id="noti" onMounted={() => console.log("test")}/>
</div>
}
And this is my Notification class:
class Notification extends React.Component {
constructor(props) {
super(props)
this.state = {
message: "place holder",
visible: false
}
}
show(message, duration){
console.log("show")
this.setState({visible: true, message})
setTimeout(() => {
this.setState({visible: false})
}, duration)
}
change(message){
this.setState({message})
}
render() {
const {visible, message} = this.state
return <div>
{visible ? message : ""}
</div>
}
}
As the class name suggests, I am trying to create a simple notification with message. And I want to simply display the notification by calling noti.show(message, duration).
However, when I try to find noti by doing window.noti, $("#noti") and document.findElementById("noti"), they all give me undefined, while noti is displayed properly. And I can find the butt using the code to find noti.
How should I find the noti? I am new to front end so please be a little bit more specific on explaining.
It's not a good idea using JQuery library with Reactjs. instead you can find a appropriate react library for notification or anything else.
Also In React we use ref to to access DOM nodes.
Something like this:
constructor(props) {
super(props);
this.noti = React.createRef();
}
...
<Notification ref={this.noti} onMounted={() => console.log("test")}/>
more info: https://reactjs.org/docs/refs-and-the-dom.html
I have hardcoded the id to 'noti' in the render method. You can also use the prop id in the Notification component.I have remodelled the component so that you can achieve the intended functionality through React way.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
messageContent: 'placeholder'
}
}
setMessage = (data) => {
this.setState({messageContent : data});
}
render() {
return (
<div className="App">
<button id='butt' onClick= {() => this.setMessage('test')} />
<Notification message = {this.state.messageContent} />
</div>
);
}
}
class Notification extends React.Component {
render () {
const {message} = this.props;
return (
<div id='noti'>
{message}
</div>
)
}
}
Before beginning: Using id/class to reach DOM nodes is not suggested in React.js, you need to use Ref's. Read more at here.
In your first render method, you give id property to Notification component.
In react.js,
if you pass a property to some component, it becomes a props of that
component. (read more here)
After you give the id to Notification, you need to take and use that specific props in your Notification component.
You see that you inserted a code line super(props) in constructor of Notification? That means, take all the props from super (upper) class and inherit them in this class.
Since id is HTML tag, you can use it like:
class Notification extends React.Component {
constructor(props) {
// inherit all props from upper class
super(props);
this.state = {
message: "place holder",
visible: false,
// you can reach all props with using this.props
// we took id props and assign it to some property in component state
id: this.props.id
}
}
show(message, duration){
// code..
}
change(message){
// code..
}
render() {
const {visible, message, id} = this.state
// give that id to div tag
return <div id={id}>
{message}
</div>
}
}
You can't pass id/class to a React Component as you would declare them in your normal HTML. any property when passed to a React Component becomes a props of that component which you have to use in the component class/function.
render() {
const {visible, message} = this.state
// give your id props to div tag as id attr
return <div id={this.props.id}>
{message}
</div>
}
This answer does not provide the exact answer about selecting a component as you want. I'm providing this answer so you can see other alternatives (more React way maybe) and improve it according to your needs.
class App extends React.Component {
state = {
isNotiVisible: false
};
handleClick = () => this.setState({ isNotiVisible: true });
render() {
return (
<div>
<button onClick={this.handleClick}>Show Noti</button>
{this.state.isNotiVisible && (
<Noti duration={2000} message="This is a simple notification." />
)}
</div>
);
}
}
class Noti extends React.Component {
state = {
visible: true
};
componentDidMount() {
setTimeout(() => this.setState({ visible: false }), this.props.duration);
}
render() {
return this.state.visible && <div>{this.props.message}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />

React child component not re-rendering on state change

I realize questions like this have been asked before but from reading several Q&As here it seems like in a lot of cases people are recommending using componentWillUpdate but from my (very) basic understanding of React, if I setState() won't child components re-render if they are affected?
This is my App component (showing the State being set, the function to update the state handleClick, the Display component (which shows the current input from state) and a Button component which shows a number and is passed the function handleClick:
this.State = {
calcValue: 0
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(val) {
this.setState({ calcValue: val })
}
render() {
return(
<div class="calcBody">
<Display currentValue={this.State.calcValue} />
<h1>Calculator</h1>
<div class="numPad">
<Button btn="num col1" operator={1} handleClick={this.handleClick.bind(this)} />
This is the Button component:
class Button extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
/*the button when clicked takes the handleClick function and passes it props based on whatever number is pressed */
<button onClick={() => this.props.handleClick(this.props.operator)}>
<div class={this.props.btn}>{this.props.operator}</div>
</button>
)
}
}
Lastly, this is the Display component:
class Display extends React.Component {
constructor(props){
super(props);
this.props = {
currentValue: this.props.currentValue
}
}
render() {
return(
<h1>{this.props.currentValue}</h1>
);
}
}
I'm wondering why this does not update when handleClick(val) is called?
You're defining state as this.State which is incorrect it should be lowercased: this.state:
this.state = {
calcValue: 0
}
Also, this line:
this.props = {
currentValue: this.props.currentValue
}
doesn't have much sense, as props are passed outside, component shouldn't change them.

React how to track event through components

I need ideas about how to solve the following problem in Reactjs:
TextEditor -> Toolbar -> PickColorBox
I have component's relationship (parent -> children) as those listed above.
The problem: The component Toolbar has the state showPickColorBox: false that represents whether the component PickColorBox is visible or not. This state is fired by a button's click event. What I need is the button be capable of toggle the boolean value to the correct state, that is, true if the component is visible, false if it's not. But every time the button event is fired the Toolbar's constructor is called and the state showPickColorBox is set to false, even if the PickColorBox component is visible.
My solution: What I need is to track whether the component PickColorBox is visible or not to set the state to the proper value. Reading some codes I saw examples where a class is instantiated in the root component to keep tracking the events. I try to reproduce the example but it didn't work, I wasn't able to passing as props the function inside the class. I'm even not sure whether it's possible to instantiated a class in javascript, so I need some guide here.
Any help on how to solve, any other solution to this is really appreciated!
I change the code bellow to simplify, so typos in the code isn't the problem, unless is about the instantiated class and how to do it. Another thing, just to clarify the code, I'm using Draftjs framework where the component Editor, the states EditorState and onChange are inheritance.
Root component
export default class TextEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
this.onChange = (editorState) => this.setState({editorState});
this.modalHandler = new ModalHandler();
}
render() {
const {editorState} = this.state;
return (
<div className={'editor-root'} >
<div className={'editor-toolbar'}>
<Toolbar
editorState={editorState}
onChange={this.onChange}
modalHandler={this.modalHandler}
/>
</div>
<div className={'editor-textarea'} >
<Editor
editorState={editorState}
onChange={this.onChange}
/>
</div>
</div>
);
}
}
ModalHandler class
export default class ModalHandler {
let boolShowComponent = false;
toogleShowComponent = (): boolean => {
return !boolShowComponent;
};
}
Toolbar Component
export default class Toolbar extends React.Component {
constructor(props) {
super(props);
this.state = {
showPickColorBox: false
};
}
_onPickColorClick() {
let bool = this.props.modalHandler.toogleShowComponent()
this.setState({
showPickColorBox: bool,
});
}
render() {
return (
<div className={'ToolbarEditor'} >
{this._onPickColorClick.bind(this)}>PickColor</button>
{
this.state.showPickColorBox ?
<PickColorBox
editorState={this.props.editorState}
onChange={this.props.onChange}
/> :
null
}
</div>
);
}
}
Instead of handling Toolbar using showPickColorBox, try to define the same in TextEditor and pass it as props. Now, to update showPickColorBox, define a method in TextEditor and pass it as props.
export default class TextEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
//define here
showPickColorBox: false
};
this.onChange = (editorState) => this.setState({editorState});
this.modalHandler = new ModalHandler();
}
//definehere
_onPickColorClick(bool) {
this.setState({
showPickColorBox: bool,
});
}
render() {
const {editorState} = this.state;
return (
<div className={'editor-root'} >
<div className={'editor-toolbar'}>
<Toolbar
editorState={editorState}
onChange={this.onChange}
modalHandler={this.modalHandler}
//pass here
_onPickColorClick={this._onPickColorClick}
/>
</div>
<div className={'editor-textarea'} >
<Editor
editorState={editorState}
onChange={this.onChange}
/>
</div>
</div>
);
}
}
Now call from toolbar:
export default class Toolbar extends React.Component {
constructor(props) {
super(props);
}
_onPickColorClick() {
let bool = this.props.modalHandler.toogleShowComponent();
//use here
this.props._onPickColorClick(bool);
}
render() {
return (
<div className={'ToolbarEditor'} >
{this._onPickColorClick.bind(this)}>PickColor</button>
{
this.state.showPickColorBox ?
<PickColorBox
editorState={this.props.editorState}
onChange={this.props.onChange}
/> :
null
}
</div>
);
}
}
I find the solution to what I was looking for. It happens that I forgot to createing a properly EventHandler class to track the events fired in children component. So here is the solution:
Root component
export default class TextEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
this.onChange = (editorState) => this.setState({editorState});
/* this name makes more sense instead of modalHandler */
this.toolbarEventHandler = new ToolbarEventHandler();
}
render() {
const {editorState} = this.state;
return (
<div className={'editor-root'} >
<div className={'editor-toolbar'}>
<Toolbar
editorState={editorState}
onChange={this.onChange}
toolbarEventHandler={this.toolbarEventHandler}
/>
</div>
<div className={'editor-textarea'} >
<Editor
editorState={editorState}
onChange={this.onChange}
/>
</div>
</div>
);
}
}
ToolbarEventHandler (ModalHandler)
/*I will change this class to hold more than one event */
/* Probably change to an array */
export default class ToolbarEventHandler {
constructor() {
this.boolShowComponent = false;
}
get boolShowComponent() {
return this.boolShowComponent;
}
set boolShowComponent(bool){
this.boolShowComponent = bool;
}
}
Toolbar
export default class Toolbar extends React.Component {
constructor(props) {
super(props);
this.state = {
showPickColorBox: false
};
}
_onPickColorClick() {
this.props.toolbarEventHandler.boolShowComponent = !this.props.toolbarEventHandler.boolShowComponent;
this.setState({
showComponent: this.props.toolbarEventHandler.boolShowComponent,
});
}
render() {
return (
<div className={'ToolbarEditor'} >
{this._onPickColorClick.bind(this)}>PickColor</button>
{
this.state.showPickColorBox ?
<PickColorBox
editorState={this.props.editorState}
onChange={this.props.onChange}
/> :
null
}
</div>
);
}
}

Pass a trigger function to two components

Here is what i want to do:
render() {
return (
<div>
<Child onTrigger={xxx} />
<button onClick={xxx} />
</div>
)
}
When button is clicked, I want something to happen in Child. How do I do this? It cannot just be a boolean because it should be a trigger and be called multiple times.
You got two options
1º Add a counter to your parent state and increment it everytime you click the button.
2º Add a ref to your child and trigger the function using this.refs.child.yourFunctionHere()
Something like this
class Parent extends React.Component{
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.handleClickWithRef = this.handeClickWithRef.bind(this);
this.state = {
triggerEvent: 0
};
}
handleClick(e) {
this.setState({
triggerEvent: this.state.triggerEvent+1
});
}
handeClickWithRef(e){
this.refs.child.functionA();
}
render() {
return <div>
<Child ref="child" onTrigger={this.state.triggerEvent} />
<button onClick={this.handleClick}>Click to trigger</button>
<button onClick={this.handleClickWithRef}>Click to trigger using ref</button>
</div>;
}
}
class Child extends React.Component{
constructor(props) {
super(props);
}
functionA(){
alert("With ref");
}
componentWillReceiveProps(nextProps){
if(nextProps.onTrigger !== 0 && nextProps.onTrigger !== this.props.onTrigger){
alert("Your event here");
}
}
render(){
return <div>Child Component</div>
}
}
full working example

Categories

Resources