I am new to react. I am just trying to create a comment box and comment board which contain multiple comments.
Each comments have one inputbox, button(save,edit) and button(remove). I have passed function made in board named updateComment to Component Comment as props.
Now When I am trying to execute save of child function in which I have called parent function updateComment using this.props.updateComment
it is giving me error can't read property of undefined.
I have searched for similar question on stackoverflow but I am unable to solved this proplem.
My app.js code is as below.
import React from 'react';
import { Home } from './home.jsx';
class App extends React.Component {
render() {
return (
<div>
<Header/>
<Board />
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div>
<h1>Header</h1>
</div>
);
}
}
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
comments:[
"My name is brijesh",
"My name is santosh",
"My name is manoj"
]}
};
removeComment(i) {
console.log("going to remove element i",i);
var arr = this.state.comments;
arr.splice(i,1);
this.setState({comments:arr});
};
updateComment(newComment, i) {
var arr = this.state.comments;
arr[i] = newComment;
this.setState({comments:arr});
};
render() {
return (
<div className="board">
{
this.state.comments.map(function(text,i) {
return (
<Comment key ={i} index = {i}
updateComment={() => {this.updateComment}}
removeComment={() => {this.removeComment}}>
{text}
</Comment>
)
})
}
</div>
)
}
}
class Comment extends React.Component {
constructor(props) {
super(props);
this.state = {
edit: false
};
};
edit(){
this.setState({edit:true});
console.log("you clickced on edit0");
};
save(){
this.setState({edit:false});
var newText = this.refs.newText.value;
this.props.updateComment(newText, this.props.index);
console.log("you clickced on edit0",newText);
};
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
if(this.state.edit) {
return (
<div>
<div className="comment">
<input type="text" ref="newText" defaultValue={this.props.children} onChange={ this.handleChange.bind(this) } />
<button onClick={this.save.bind(this)}>Save</button>
</div>
</div>
)
}
else {
return (
<div>
<div className="comment">
<div>{ this.props.children }</div>
<button onClick={this.edit.bind(this)}>Edit</button>
</div>
</div>
)
}
}
}
export default App
And my main.js looks like this.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(
( < App / > ), document.getElementById('app'));
I have also created fiddle also.
https://jsfiddle.net/aubrijesh/k3h2pcnj/#&togetherjs=uEI7TFnJD1
I believe that DOMZE is on the right track but you should also bind the function in the map statement. In my opinion arrow functions makes it much easier to keep track of what this refers to.
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
comments:[
"My name is brijesh",
"My name is santosh",
"My name is manoj"
]}
};
removeCommment(i) {
console.log("going to remove element i",i);
var arr = this.state.comments;
arr.splice(i,1);
this.setState({comments:arr});
};
updateComment(newComment, i) {
var arr = this.state.comments;
console.log("new Comment");
arr[i] = newComment;
this.setState({comments:arr});
};
render() {
return (
<div className="board">
{
this.state.comments.map((text,i) => {
return (
<Comment key ={i} index = {i}
updateComment={() => {this.updateComment}}
removeComment={() => {this.removeComment}}>
{text}
</Comment>
)
})
}
</div>
)
}
}
class Comment extends React.Component {
constructor(props) {
super(props);
this.state = {
edit: false
};
};
edit(){
this.setState({edit:true});
console.log("you clickced on edit0");
};
save(){
this.setState({edit:false});
var newText = this.refs.newText.value;
this.props.updateComment(newText, this.props.index);
console.log("you clickced on edit0",newText);
};
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
if(this.state.edit) {
return (
<div>
<div className="comment">
<input type="text" ref="newText" defaultValue={this.props.children} onChange={ this.handleChange} />
<button onClick={this.save.bind(this)}>Save</button>
</div>
</div>
)
}
else {
return (
<div>
<div className="comment">
<div>{ this.props.children }</div>
<button onClick={this.edit.bind(this)}>Edit</button>
</div>
</div>
)
}
}
}
ReactDOM.render(<Board />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
update your render method
let self = this;
return (
<div className="board">
{
self.state.comments.map(function(text,i) {
return (
<Comment key ={i} index = {i}
updateComment={() => {self.updateComment}}
removeComment={() => {self.removeComment}}>
{text}
</Comment>
)
})
}
</div>
)
You need to bind the class to the function, so that it knows what "this" is
render() {
return (
<div className="board">
{
this.state.comments.map(function(text,i) {
return (
<Comment key ={i} index = {i}
updateComment={this.updateComment.bind(this)}
removeComment={this.removeComment.bind(this)}>
{text}
</Comment>
)
})
}
</div>
)
}
Note that you may want to do those bindings in the constructor so that it doesn't bind at each and every render
Related
I have a text and a button.
What I want to achieve is something like this, that if I clicked the Button the Text will be hidden.
I want to achieve this without using state.
class Test extends Component {
constructor(props){
//codes
}
hide = () => {
const span = this.refs.spanLoading;
span.ClassName = "hidden";
}
render() {
return (
<span ref="spanLoading" id="test-id" className="">The Quick Brown Fox.</span>
<button onClick={() => this.hide()}>Hide</button>
);
}
}
export default Test;
You can use useRef() hook.
Maintain one ref object which is not a string.
const {useRef} = React;
function App() {
const ref = useRef(null);
const onClick = () => {
const span = ref.current; // corresponding DOM node
span.className = "hidden";
};
return (
<div>
<span ref={ref} className="">The Quick Brown Fox.</span>
<button onClick={onClick}>Hide</button>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
.hidden {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This can be achieved simply, your approach is correct just some few fixes.
You can return only one element in JSX
The ref should be maintained somewhere in JS memory
Check this code and the working codesandbox instance here
class Test extends React.Component {
constructor() {
super();
this.inputRef = React.createRef();
}
hide = () => {
console.log(this.inputRef);
this.inputRef.current.style.visibility="hidden";
};
render() {
return (
<>
<span ref={this.inputRef} id="test-id" className="">
The Quick Brown Fox.
</span>
<button onClick={() => this.hide()}>Hide</button>
</>
);
}
}
EDIT! As you asked about dynamically generated refs...
This is what I understood as your requirement... see whether it matches it.
Working sandbox here
class Test extends React.Component {
constructor() {
super();
this.inputRef = React.createRef();
this.refCollection = {};
for (let id = 0; id < 10; id++) {
this.refCollection["id_" + id] = React.createRef();
}
}
hide = e => {
console.log(this.inputRef);
this.inputRef.current.style.visibility = "hidden";
};
hideInCollection = k => {
let changedRef = this.refCollection[k];
changedRef.current.style.visibility = "hidden";
};
render() {
return (
<>
<span ref={this.inputRef} id="test-id" className="">
The Quick Brown Fox.
</span>
<button onClick={() => this.hide()}>Hide</button>
{Object.keys(this.refCollection).map(k => (
<div>
<span ref={this.refCollection[k]} id="test-id" className="">
Looped the Quick Brown Fox
</span>
<button onClick={() => this.hideInCollection(k)}>Hide</button>
</div>
))}
</>
);
}
}
class Test extends Component {
constructor(props){
this.hide = this.hide.bind(this);
}
hide() {
this.span.classList.add("hidden");
}
render() {
return (
<div>
<span ref={(el) => { this.span = el; }} id="test-id" className="">The Quick Brown Fox.</span>
<button onClick={this.hide}>Hide</button>
</div>
);
}
}
export default Test;
You should return a single element tho
I want to add a string to my list and I have a CounterComponent that works separately from HelloWordComponent, I set 1 for 'Microsoft', 2 for 'FaceBook' and 3 for 'React'; when I add 'yahoo' to first of the list, Counter for 'yahoo' set to 1 and react turns to 0.
I know I have to use unique key and I think {name} is qualified, but I don't know where to use key = {name} exactly?
class HelloWordComponent extends React.Component {
render() {
return <div>{this.props.name}</div>
}
}
class Counter extends React.Component {
constructor(){
super()
this.onPlusClick = this.onPlusClick.bind(this)
this.state = {count : 0}
}
onPlusClick(){
this.setState(prevState => ({count: prevState.count + 1}))
}
render(){
return <div>
{this.state.count}
<button onClick = {this.onPlusClick}>+</button>
</div>
}
}
class App extends React.Component{
constructor(){
super()
this.addName = this.addName.bind(this)
this.state = {
name: "Sara",
list:['Microsoft', 'FaceBook', 'React']
}
}
addName(){
this.setState(prevState =>({list: ['Yahoo', ...prevState.list]}))
}
render(){
return (
<div >
{this.state.name}
{this.state.list.map(name =>{
return <div>
<HelloWordComponent key = {name} name = {name}/>
<Counter/>
</div>
})}
<br/>
<button onClick= {this.addName}>add a Name</button>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById("app"));
<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="app"> </div>
I found the right solution of my problem, I have to set unique key (key = {name}) in my parent root element <div key = {name}> so the render method change to:
render(){
return (
<div >
{this.state.name}
{this.state.list.map(name =>{
return <div key = {name}>
<HelloWordComponent name = {name}/>
<Counter/>
</div>
})}
How to return element in react class functions on a click. is it even possible?
class Item extends Component {
constructor(props) {
super(props);
this.itemInfo = this.itemInfo.bind(this);
}
itemInfo = () =>{
return <div> some info</div>
}
render(){
return(
<div>
<div onClick={this.itemInfo}> Click Here <div>
</div>
)
}
}
class Item extends React.Component {
state = {
showDiv: false
};
render() {
return (
<div>
<div
style={{ cursor: "pointer" }}
onClick={() =>
this.setState(prevState => ({
showDiv: !prevState.showDiv
}))
}
>
Click Me
</div>
{/*Show the INFO DIV ONLY IF THE REQUIRED STATE IS TRUE*/}
{this.state.showDiv && <InfoDiv />}
</div>
);
}
}
//This is the div which we want on click
var InfoDiv = () => (
<div style={{ border: "2px solid blue",borderRadius:10, padding: 20 }}>
<p> Long Text DIVLong Text DIVLong Text DIVLong Text DIVLong Text DIV </p>
</div>
);
ReactDOM.render(<Item />, 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"></div>
You should do that in the state.
itemInfo = () =>{
this.setState({ component:<div> some info</div> });
}
and render the component like this
return(
<div>
<div onClick={this.itemInfo}> Click Here <div>
{this.state.component}
</div>
)
You can try something like this, using the state and conditional rendering:
class Item extends Component {
constructor(props) {
super(props)
this.state = {
showMore: false,
}
}
toggleShowMore = () => {
this.setState({ showMore: !this.state.showMore })
}
render() {
return (
<div>
<div onClick={this.toggleShowMore}>
{this.state.showMore ? 'Show less' : 'Show more'}
</div>
{this.state.showMore ? <div>some info</div> : null}
</div>
)
}
}
Here's how I would do it:
function ItemInfo() {
return(
<div>Some Info</div>
);
}
class Item extends Component {
constructor(props) {
super(props);
this.handleClick= this.handleClick.bind(this);
this.state = {
showInfo: false
}
}
handleClick() {
this.setState((prevState) => {showInfo: !prevState.showInfo});
}
render(){
return(
<div>
<div onClick={this.handleClick}> Click Here <div>
{ this.state.showInfo ?
<ItemInfo/>
: null }
</div>
)
}
}
I am trying to map over an array and get a chart to appear alongside with each element, but it doesn't seem to work. This same code appeared once correctly, but no other time and I am not sure what I am missing.
I tried to change the id name to where it tags the chart and I did that by adding an index variable, but still not working
import React from 'react'
import c3 from '/c3.min.js'
class SearchedFood extends React.Component {
constructor(props) {
super(props)
this.state = {
}
this.graph = this.graph.bind(this)
}
graph(index) {
c3.generate({
bindto: '#class' + index,
data: {
columns: [
[1, 2, 3], [2, 3,4]
],
type: 'bar'
},
bar: {
width: {
ratio: 0.3
}
}
})}
render () {
return (
<div>
{this.props.foodResults.map((food, i) => {
return (
<div key={i}>
<label>{food.recipe.label}</label>
<img className="card-img-top" src={food.recipe.image} height="250" width="auto"></img>
<a href={food.recipe.url}>{food.recipe.source}</a>
<p>{food.recipe.dietLabels[0]}</p>
<div>
{food.recipe.ingredientLines.map((ingredient, i) => {
return (
<p key={i}>{ingredient}</p>
)
})}
</div>
<p>Calories {Math.floor(food.recipe.calories/food.recipe.yield)}</p>
<div id={`class${i}`}>{this.graph(i)}</div>
</div>
)
})}
</div>
)
}
}
export default SearchedFood
bindto: '#class' + index,/{this.graph...} isn't gonna work. React doesn't render directly/immediately to the DOM.
Looks like you can use elements with bindTo - your best bet is to use a ref
class SearchedFoodRow extends React.Component {
componentDidMount() {
c3.generate({
bindTo: this.element,
...
})
}
render() {
const { food } = this.props
return (
<div>
<label>{food.recipe.label}</label>
<img className="card-img-top" src={food.recipe.image} height="250" width="auto"></img>
<a href={food.recipe.url}>{food.recipe.source}</a>
<p>{food.recipe.dietLabels[0]}</p>
<div>
{food.recipe.ingredientLines.map((ingredient, i) => {
return (
<p key={i}>{ingredient}</p>
)
})}
</div>
<p>Calories {Math.floor(food.recipe.calories/food.recipe.yield)}</p>
<div ref={ element => this.element = element } />
</div>
)
}
}
and then
class SearchFood extends React.Component {
render() {
return (
<div>
{ this.props.foodResults.map((food, i) => <SearchedFoodRow key={i} food={food} />)}
</div>
)
}
}
I have a react element like this:
import React, { PropTypes, Component } from 'react'
class AlbumList extends Component {
constructor(props) {
super(props);
this.state = {'active': false, 'class': 'album'};
}
handleClick() {
if(this.state.active){
this.setState({'active': false,'class': 'album'})
}else{
this.setState({'active': true,'class': 'active'})
}
}
render() {
var album_list
const {user} = this.props
if(user.data){
list = user.data.filter(album => album.photos).map((album => {
return <div className={"col-sm-3"} key={album.id}>
<div className={this.state.class} key={album.id} onClick={this.handleClick.bind(this)}>
<div className={"panel-heading"}>{ album.name }</div>
<div className={"panel-body"}>
<img className={"img-responsive"} src={album.photo.source} />
</div>
</div>
</div>
}))
}
return (
<div className={"container"}>
<div className="row">
{list}
</div>
</div>
)
}
}
export default AlbumList
Here map gives the list of filter data as I wanted. Here what I am doing changes the class of all the list element if I click on one.
I am getting the class name from this.state.class
How can I change the class of only element that i have clicked..
Thanks in advance ...
I have considered it once.So you have so many divs and you want to know which is clicked.My way to solve this problem is to give a param to the function handleClick and you can get the dom of the div while you click the div.Like this:
array.map(function(album,index){
return <div onClick={this.handleClick}/>
})
handleClick(e){
console.log(e.target);
e.target.className = 'active';
...
}
Then you have a param for this function.While you can use the e.target to get the dom of your div which is clicked.
There are some mistake into your code about the state.class.
class AlbumList extends Component {
constructor(props) {
super(props);
this.state = {'active': false, 'class': 'album'};
}
handleClick(e) {
if(e.target.class === 'active'){
e.target.className = 'album'
}else{
e.target.className = 'active'
}
}
render() {
var album_list
const {user} = this.props
if(user.data){
list = user.data.filter(album => album.photos).map((album => {
return (
<div className={"col-sm-3"} key={album.id}>
<div className='active' key={album.id} onClick={this.handleClick.bind(this)}>
<div className={"panel-heading"}>{ album.name }</div>
<div className={"panel-body"}>
<img className={"img-responsive"} src={album.photo.source} />
</div>
</div>
</div>
)
}))
}
return (
<div className={"container"}>
<div className="row">
{list}
</div>
</div>
)
}
}
You can try this and tell me anything wrong.