A bit confused as to why the event listener isn't firing. I also tried not commenting out the bind since I don't think it is necessary, but it still does not work. Not sure what the error is here...
I also triple checked the last line (rendering App component at id=root on my main html tag... that is fine).
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends React.Component {
constructor() {
super();
this.state = {text: 'X'};
this.i = 0;
this.val = ['X', 'O'];
this.update = this.update.bind(this);
}
//click to change state from X to O.
update() {
console.log("clicked");
// if(this.i === 0) {
// this.setState({text: this.val[1]});
// this.i = this.i + 1;
// } else {
// this.setState({text: this.val[0]});
// this.i = this.i - 1;
// }
}
render() {
return (
<div>
<h1>Tic Tac Toe</h1>
<Box text={this.state.text} onClick={this.update}/>
</div>
);
}
}
class Box extends React.Component {
render() {
return (
<button className = "box" >{this.props.text}</button>
// <button className = "box">hi</button>
);
}
}
render(<App />, document.querySelector('#root'));
You forgot to use the passed onClick prop on Box.
class Box extends React.Component {
render() {
return (
<button className="box" onClick={this.props.onClick}>{this.props.text}</button>
// <button className="box">hi</button>
);
}
}
That should work!
Related
I want to re-render html in App.js what is triggered by click event.
In first load JSX component <WaypointList waypoint_is_done={false} App={this}/> is rendered.
But when i click button then it wont render JSX component <WaypointList waypoint_is_done={true} App={this}/> again.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
content: this.index()//LETS LOAD CONTENT
};
this.index = this.index.bind(this);
this.toggleDoneList = this.toggleDoneList.bind(this);
};
index() {
return <WaypointList waypoint_is_done={false} App={this}/>;
};
//SET NEW RENDERER ONCLICK
toggleDoneList(){
console.log('click');
this.setState({
content: <WaypointList waypoint_is_done={true} App={this}/>
//content: <div>see</div>
});
};
render() {
console.log(this.state.content);
return this.state.content;
};
}
ReactDOM.render(
<App/>,
document.getElementById('app')
);
First time it fire WaypointList class, but when i click button "object-done-listing" then not
It calls the App.toggleDoneList and App.render is also fired and it get correct JSX component but does not fire WaypointList class again
class WaypointList extends React.Component {
constructor(props) {
super(props);
this.App = props.App;
this.state = {
content: this.index(props)
};
this.index = this.index.bind(this);
};
index(props) {
let rows = logistic_route_sheet_waypoint_rows;
if (rows.length > 0) {
return (
<div className="map-listing">
<div className="object-done-listing noselect btn btn-success"
onClick={() => this.App.toggleDoneList()}>
<i className="fa fa-list" aria-hidden="true"></i>
</div>
</div>
);
}
return (null);
};
render() {
return this.state.content;
};
}
It works if i set
this.setState({
content: <div>see</div>
});
but not with
this.setState({
content: <WaypointList waypoint_is_done={true} App={this}/>
});
What is the problem ?
I found a solution to re-renderer the class
i made "CustomEvent" "reRenderer" and i call re_renderer function outside of react.
I have a small question.
Let's imagine I have component A which holds , after component A does it's job I render component B. I would like that same DOM element (textarea) would be reused in component B.
The reason is if new textarea is rendered in component B it loses focus as it's just new DOM element. It's like after component A lifetame take textarea element from it and just put it in component B instead of rendering new one.
Sample APP
https://jsfiddle.net/remdex/v67gqyLa/1/#&togetherjs=aiRvTGhRK2
class AComponent extends React.Component {
render() {
return ( <textarea>A Component</textarea> )
}
}
class BComponent extends React.Component {
render() {
return ( <textarea>Should be A Component text</textarea> )
}
}
class ABComponent extends React.Component {
constructor(props) {
super(props)
this.state = {'component' : 'A'};
}
render() {
return (
<div><button onClick={(e) => this.setState({component:'B'})}>Switch to B Component</button>
{this.state.component == 'A' && <AComponent/>}
{this.state.component == 'B' && <BComponent/>}
</div>
)
}
}
ReactDOM.render(<ABComponent />, document.querySelector("#app"))
In your sandbox example, ComponentA and ComponentB are redundant. You can create ComponentA and ComponentB as a class if they are using same element and operate them with ComponentAB. You can change your ComponentAB like:
class A {
handle(input) {
// Do your A job here and return result
console.log("Handler A is running");
};
}
class B {
handle(input) {
// Do your B job here and return result
console.log("Handler B is running");
};
}
class ABComponent extends React.Component {
currentHandler = new A();
handleClick = () => {
this.currentHandler = new B();
};
handleChange = (event) => {
// Handle the input with current handler
var result = this.currentHandler.handle(event.target.value);
// If you want you can use result to cahnge something in view
// this.setState({value: result});
}
render() {
return (
<div>
<button onClick={this.handleClick}>
Switch to B Component
</button>
<textarea onChange={this.handleChange}>Text Area used between A class and B class</textarea>
</div>
)
}
}
I also edit the codebox example. You can find it here.
This can be achieved using a ref. ABComponent passes a ref to BComponent to attach to the textarea. When the state of ABComponent updates to component = 'B', then the ref is used to set focus. Use a ref passed to AComponent to grab its textarea value before it's unmounted, then set the value of the textarea in B to it.
import React, { Component, createRef } from "react";
...
class AComponent extends Component {
render() {
const { textareaRef } = this.props;
return <textarea ref={textareaRef} defaultValue="A Component" />;
}
}
class BComponent extends Component {
render() {
const { textareaRef } = this.props;
return <textarea ref={textareaRef} defaultValue="Should be A Component text" />;
}
}
class ABComponent extends Component {
state = { component: "A" };
refA = createRef();
refB = createRef();
componentDidUpdate(prevProps, prevState) {
const { component, content } = this.state;
if (prevState.component !== component) {
if (component === "B") {
this.refB.current.focus();
this.refB.current.value = content;
}
}
}
render() {
return (
<div>
<button
onClick={e =>
this.setState({ component: "B", content: this.refA.current.value })
}
>
Switch to B Component
</button>
{this.state.component === "A" && <AComponent textareaRef={this.refA} />}
{this.state.component === "B" && <BComponent textareaRef={this.refB} />}
</div>
);
}
}
I am learning React js. I need to rerender one of the child components from the parent component. One way is I can use setState for the matrix but the entire matrix which is parent component will be rerendered instead I want to rerender only one child component. This have added by code below.
Child.js
import React from 'react';
class Child extends React.Component {
constructor(props){
super(props);
this.state = {
text : ""
};
}
updateParent(text) {
if(text) {
this.setState({text : text});
}
}
render() {
return(
<div>Child {this.state.text}</div>
);
}
}
export default Child;
Parent.js
import React from 'react';
import Child from './Child'
class Parent extends React.Component {
constructor(props){
super(props);
this.state = {
table : [[<Child key={11}/>, <Child key={12}/>, <Child key={13}/>],
[<Child key={21}/>, <Child key={22}/>, <Child key={23}/>]],
i : 0,
j : 0
};
}
componentDidMount() {
this.timerID1 = setInterval(() => this.updateTable(), 1000);
}
updateTable() {
//this.state.table[this.state.i][this.state.j].updateParent("");
this.state.j++;
if( this.state.j % 3 == 0) {
this.state.i++;
this.state.i %= 2;
}
//this.state.table[this.state.i][this.state.j].updateParent("*");
// or tempTable[i][j] = <Child key={ij} text={"*"}/>; this.setState({table: tempTable});
this.state.j++;
}
createTable() {
let table = []
for(let i = 0; i < 2; i++) {
table.push( <div key={i} style={{display:"flex"}}>{this.state.table[0]}</div> )
}
return table;
}
render() {
return(
<div>{this.createTable()}</div>
);
}
}
export default Parent;
Don't store Child component instances in state, instead render them dynamically
You can implement Child as a PureComponent so that if no props or state change for it, it doesn't re-render
Do not mutate state directly like you do this.state.j++ and so on. Use setState
Parent.js
export default class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
table: this.createTableData(3),
i: 0,
j: 0
};
}
createTableData(size) {
const arr = new Array(size);
for (var i = 0; i < size; i++) {
arr[i] = new Array(size).fill("");
}
return arr;
}
componentDidMount() {
this.timerID1 = setInterval(() => this.updateTable(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID1);
}
updateTable() {
let { i, j, table } = this.state;
j++;
if (j % 3 == 0) {
i++;
i %= 2;
}
const newTable = table.map((tr, row) => {
return tr.map((td, col) => {
if (row == i && col == j) {
return "*";
} else {
return "";
}
});
});
j++;
this.setState({
table: newTable,
i,
j
});
}
createTable() {
return this.state.table.map((row, i) => {
return (
<div className="row">
{row.map((col, j) => {
return <Child key={`${i + 1}${j + 1}`} text={col} />;
})}
</div>
);
});
}
render() {
return <div>{this.createTable()}</div>;
}
}
Child.js
class Child extends React.PureComponent {
render() {
console.log("child rerender", this.props.text);
return <div>Child {this.props.text} </div>;
}
}
working demo
NOTE: The demo only contains the display and performance optimization logic along with the architecture, The logic to update the indexes i, j needs to be done by you in updateTable method.
If you look at the demo, only the cell you whose value changed from "" to "*" and vice versa will re-render, the rest will not
you can rerender child component without rendering parent component
by using ref to call child function from parent and update child component
Parent.js
import React from "react";
import Table from "./Table";
import Child from "./Child";
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.table = [
[<Child key={11} />, <Child key={12} />, <Child key={13} />],
[<Child key={21} />, <Child key={22} />, <Child key={23} />],
];
this.i = 0;
this.j = 0;
}
componentDidMount() {
this.timerID1 = setInterval(() => this.updateTable(), 1000);
}
updateTable() {
this.j++;
if (this.j % 3 == 0) {
this.i++;
this.i %= 2;
}
this.j++;
this.table[0].push(
<Child key={21} />,
<Child key={22} />,
<Child key={23} />
);
this.refs.table.updateTable(this.table[0]);
}
componentDidUpdate() {
console.log("component rerender");
}
render() {
return (
<div>
<h1>Parent Component</h1>
<Table ref="table" />
</div>
);
}
}
export default Parent;
Child.js
import React from 'react';
class Child extends React.Component {
constructor(props){
super(props);
this.state = {
text : ""
};
}
updateParent(text) {
if(text) {
this.setState({text : text});
}
}
render() {
return(
<div>Child {this.state.text}</div>
);
}
}
export default Child;
Table.js
import React, { Component } from "react";
class table extends Component {
state = {
table: [],
};
updateTable = (table) => {
this.setState({ table });
};
render() {
const { table } = this.state;
return (
<div>
{table.map((tableItem, i) => {
return <div key={i} style={{ display: "flex" }}>{tableItem}</div>;
})}
</div>
);
}
}
export default table;
componentDidUpdate will give log if rerendering is happen
Note: I did not use state in parent component. if you want to use parent component state then you have to stop rerendering by using shouldComponentUpdate lifecycle
How to fix this error when I have the binding this way: previously binding in constructor solved but this is a bit complex for me:
{this.onClick.bind(this, 'someString')}>
and
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
Option 1:
Use arrow functions (with babel-plugins)
PS:- Experimental feature
class MyComponent extends Component {
handleClick = (args) => () => {
// access args here;
// handle the click event
}
render() {
return (
<div onClick={this.handleClick(args)}>
.....
</div>
)
}
}
Option 2: Not recommended
Define arrow functions in render
class MyComponent extends Component {
render() {
const handleClick = () => {
// handle the click event
}
return (
<div onClick={handleClick}>
.....
</div>
)
}
}
Option 3:
Use binding in constructor
class MyComponent extends Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// handle click
}
render() {
return (
<div onClick={this.handleClick}>
.....
</div>
)
}
}
I recommend you to use binding in the class constructor. This will avoid inline repetition (and confusion) and will execute the "bind" only once (when component is initiated).
Here's an example how you can achieve cleaner JSX in your use-case:
class YourComponent extends React.Component {
constructor(props) {
super(props);
// Bind functions
this.handleClick = this.handleClick.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleClick() {
this.onClick('someString');
}
onClick(param) {
// That's your 'onClick' function
// param = 'someString'
}
handleSubmit() {
// Same here.
this.handleFormSubmit();
}
handleFormSubmit() {
// That's your 'handleFormSubmit' function
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<p>...</p>
<button onClick={this.handleClick} type="button">Cancel</button>
<button type="submit">Go!</button>
</form>
);
}
}
Even though all the previous answers can achieve the desire result, but I think the snippet below worth mentioning.
class myComponent extends PureComponent {
handleOnclickWithArgs = arg => {...};
handleOnclickWithoutArgs = () => {...};
render() {
const submitArg = () => this.handleOnclickWithArgs(arg);
const btnProps = { onClick: submitArg }; // or onClick={submitArg} will do
return (
<Fragment>
<button {...btnProps}>button with argument</button>
<button onClick={this.handleOnclickWithoutArgs}>
button without argument
</button>
</Fragment>
);
}
}
I'm using Clappr player with ReactJS.
I want Clappr player component appear and destroy when I click to a toggle button. But it seems like when Clappr player is created, the entire page has reload (the toggle button dissapear and appear in a blink). So here is my code:
ClapprComponent.js
import React, { Component } from 'react'
import Clappr from 'clappr'
class ClapprComponent extends Component {
shouldComponentUpdate(nextProps) {
let changed = (nextProps.source != this.props.source)
if (changed) {
this.change(nextProps.source)
}
return false
}
componentDidMount() {
this.change(this.props.source)
}
componentWillUnmount() {
this.destroyPlayer()
}
destroyPlayer = () => {
if (this.player) {
this.player.destroy()
}
this.player = null
}
change = source => {
if (this.player) {
this.player.load(source)
return
}
const { id, width, height } = this.props
this.player = new Clappr.Player({
baseUrl: "/assets/clappr",
parent: `#${id}`,
source: source,
autoPlay: true,
width: width,
height: height
})
}
render() {
const { id } = this.props
return (
<div id={id}></div>
)
}
}
export default ClapprComponent
Video.js
import React, { Component } from 'react'
import { Clappr } from '../components'
class VideoList extends Component {
constructor() {
super()
this.state = {
isActive: false
}
}
toggle() {
this.setState({
isActive: !this.state.isActive
})
}
render() {
const boxStyle = {
width: "640",
height: "360",
border: "2px solid",
margin: "0 auto"
}
return (
<div>
<div style={boxStyle}>
{this.state.isActive ?
<Clappr
id="video"
source="http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/sl.m3u8"
width="640"
height="360" />
: ''}
</div>
<button class="btn btn-primary" onClick={this.toggle.bind(this)}>Toggle</button>
</div>
)
}
}
export default VideoList
Anyone can explain why? And how to fix this problem?
Edit 1: I kind of understand why the button is reload. It's because in index.html <head>, I load some css. When the page is re-render, it load the css first, and then execute my app.min.js. The button doesn't reload in a blink if I move the css tags under the <script src="app.min.js"></script>.
But it doesn't solve my problem yet. Because the css files have to put in <head> tags. Any help? :(
Here you have a running (jsbin link) example. I simplified a little bit and it still shows your main requirement:
class ClapprComponent extends React.Component {
componentDidMount(){
const { id, source } = this.props;
this.clappr_player = new Clappr.Player({
parent: `#${id}`,
source: source
});
}
componentWillUnmount() {
this.clappr_player.destroy();
this.clappr_player = null;
}
render() {
const { id } = this.props;
return (
<div>
<p>Active</p>
<p id={id}></p>
</div>
);
}
}
class NotActive extends React.Component {
render() {
return (
<p>Not Active</p>
);
}
}
class App extends React.Component {
constructor(props){
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
isActive: false
}
}
toggle() {
this.setState({
isActive: !this.state.isActive
})
}
render() {
return (
<div>
<h1>Clappr React Demo</h1>
{ this.state.isActive ?
<ClapprComponent
id="video"
source="http://www.html5videoplayer.net/videos/toystory.mp4"
/> :
<NotActive />}
<button onClick={this.toggle}>Toggle</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
Also make sure to rename the button class property to className.
Maybe you can work from here to find your exact problem? Hope that helps.
In Clappr's documentation I found a like about how to use clappr with reactjs