React displaying which item was clicked - javascript

im having a real hard time learning react and I can't quite understand how to solve this problem. I have Tournaments.js which reads tournament names from an API and displays them as clickable elements with a reference to templates.js in which I would like to simply display the name of the element that was clicked.
Tournaments.js:
import React, { Component } from "react";
const API = 'http://localhost:8080/api/tournaments';
class Tournaments extends Component {
constructor() {
super();
this.state = {
data: [],
}
}
componentDidMount() {
fetch(API)
.then((Response) => Response.json())
.then((findresponse) => {
console.log(findresponse)
this.setState({
data:findresponse,
})
})
}
testfun(e) {
var target = e.target;
console.log(target)
}
render() {
return(
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="jumbotron text-center">
{
this.state.data.map((dynamicData, key) =>
<div>
<a key={dynamicData.id} href={"/#/template"} onClick={this.testfun}>{dynamicData.name}</a>
</div>
)
}
</div>
</div>
</div>
</div>
)
}
}
export default Tournaments;
template.js:
import React, { Component } from "react";
import Tournaments from "./Tournaments";
const API = 'http://localhost:8080/api/tournaments';
class template extends Component {
constructor() {
super();
this.state = {
data: [],
}
}
render() {
return(
<div>
<h1>clicked item</h1>
</div>
)
}
}
export default template;
And my API stores data looking like this:
[{"name":"abc","id":1,"organizer":"kla"},{"name":"fsdfs","id":2,"organizer":"fsdf"}]
I've tried to use onclick to get the value that was clicked but not sure how to display it in template.js.
Thanks for any help

I think you want to put user clicked item from Tournaments.js and display in Template.js
You can try to put Template inside Tournaments
Tournaments.js
import React, { Component } from "react";
import Template from './template';
class Tournaments extends Component {
constructor() {
super();
this.state = {
data: [],
clickedData: []
}
}
testfun(e) {
var target = e.target;
console.log(target);
let newClickedData = [];
newClickedData.push(target);
this.setState({
clickedData: newClickedData
});
}
render() {
return (
<div>
...
...
<Template clickedData={this.state.clickedData} />
</div>
);
}
}
export default Tournaments;
Template.js
import _ from 'lodash';
import React, { Component } from "react";
class template extends Component {
render() {
let clickedData = this.props.clickedData;
let displayClickedData = _.map(clickedData, (item) => {
return <div>item</div>;
});
return(
<div>
{displayClickedData}
</div>
)
}
}
export default template;

I would suggest picking one of the following options:
Wrap both Tournaments and Tournament in some parent component. Then if you click on any specific tournament, you can change its ID in state. If ID is null, you render list of tournaments, if ID is defined, you render your tournament component. That way you would lose the URL effect.
You can pass ID as GET parameter in URL and then read it in your Tournament component, so your code would look similar to this <a href={`/#/template?id=${dynamicData.id}`}>{dynamicData.name}</a>
You can try out React Router which would handle all that for you

Related

React onClick method giving undefined on first selection and values passed are not the same?

I have tried to create this list of users on which clicking on them should open up a correspond users message but upon clicking the first time an 'undefined ' value is passed and then
I have tried searching the problem and also tried github for the related code but nothing solid is coming up
i have the following code :-
//code where the selection takes place
setselectRoomId(id)
{
this.toggleMessageContainer(false,true);
this.setState({selectedRoomid:id});
}
//code where the effect on changing selection should take place
loadConversation(id)
{
this.setState({isLoading:true,disableTextArea:true})
let room=(id) ? id : this.props.selectedRoomid
//let data={room:selectedRoomId}
axios('/chat/messages/'+room).then(res=>{
this.setState({messages:res.data.messages,isLoading:false,disableTextArea:false});
console.log(res);
}).catch(err=>{
console.log(err);
});
}
// front end code where click event occurs
render()
{
let {showRoomPanel,onlinerooms}=this.props;
let {activeRoom,isLoading,currentUser}=this.state;
let roomStyle=(showRoomPanel==false) ?"rooms-panel hide-div":"rooms-panel"
return(
<div className={roomStyle}>
{ (isLoading===true) ? <Spinner></Spinner>:
this.state.rooms.map((room)=>{
return <Roominfo
key={room.room}
userInfo={currentUser}
activeRoomId={activeRoom}
onlinerooms={onlinerooms}
senderName={room.senderName}
senderId={room.senderId}
setSelectedRoomId={this.setSelectedRoomid.bind(this,room.room)}/>
})
}
// the onclick event
const RoomInfo=({senderName,roomId,senderId,setSelectedRoomId,activeRoomId})=>{
const allConstants= new Constants();
return(
<div className={(activeRoomId==roomId) ? "room-info active-room":"room-info"} onClick={setSelectedRoomId}>
<div className='room'>
<p> {senderName}</p>
</div>
</div>
);
}
</div>
);
}
// the room panel class
import React,{Component} from 'react';
import axios from 'axios';
import Roominfo from './Roominfo';
import {Spinner} from 'reactstrap';
import Constants from '../Constants';
import decode from 'jwt-decode';
import '../Styles/RoomPanel.css';
class Roompanel extends Component{
state={
rooms:[],
isLoading:true,
message:null,
currentUser:null
}
constructor(props)
{
super(props);
this.allConstants=new Constants();
}
componentDidMount()
{
this.loadRooms()
}
loadRooms()
{
//let allConstants=this.allConstants
const token=decode(localStorage.getItem('token'));
const rid=token.id;
this.setState({currentUser:rid});
const data={rid:rid}
axios.get('/chat/user/'+rid).then(res=>{
//console.log(res.data.users);
this.setState({rooms:res.data.users,isLoading:false});
}).catch(err=>{
console.log(err);
this.setState({isLoading:false,message:'error occured'});
});
}
setSelectedRoomId(id)
{
this.props.setselectRoomId(id)
//console.log(this.props.selectedRoomid)
this.setState({activeRoom:id});
}
render()
{
let {showRoomPanel,onlinerooms}=this.props;
let {activeRoom,isLoading,currentUser}=this.state;
let roomStyle=(showRoomPanel==false) ?"rooms-panel hide-div":"rooms-panel"
return(
<div className={roomStyle}>
{ (isLoading===true) ? <Spinner></Spinner>:
this.state.rooms.map((room)=>{
return <Roominfo
key={room.room}
userInfo={currentUser}
activeRoomId={activeRoom}
onlinerooms={onlinerooms}
senderName={room.senderName}
senderId={room.senderId}
setSelectedRoomId={this.setSelectedRoomId.bind(this,room.room)}/>
})
}
</div>
);
}
}
export default Roompanel;
//the message panel class where effect should take place
import React,{Component} from 'react';
import axios from 'axios';
import Message from './Message';
import WriteMessage from './WriteMessage';
import {Spinner} from 'reactstrap';
import '../Styles/MessagePanel.css';
class MessagePanel extends Component{
state={
messages:[],
isLoading:false,
disableTextArea:true
}
constructor(props)
{
super(props);
this.onLineRoom=this.onLineRoom.bind(this);
}
componentDidMount()
{
this.scrollToBottoom()
}
componentWillUpdate()
{
this.scrollToBottoom()
}
scrollToBottoom()
{
this.messageEnd.scrollIntoView({behaviour:'smooth'});
}
componentWillReceiveProps(nextProps)
{
this.loadConversation(nextProps.selectedRoomId)
console.log(this.props.selectedRoomid)
}
loadConversation(id)
{
this.setState({isLoading:true,disableTextArea:true})
let room=(id) ? id : this.props.selectedRoomid
//let data={room:selectedRoomId}
axios('/chat/messages/'+room).then(res=>{
this.setState({messages:res.data.messages,isLoading:false,disableTextArea:false});
console.log(res);
}).catch(err=>{
console.log(err);
});
}
onNewMessageArrival(data)
{
let newMessages=[...this.state.messages]
if(data.room==this.props.selectedRoomId)
{
this.setState((prevState,props)=>({
messages:[...this.state.messages,{...data}]
}));
}
this.props.fillRoomInfoFromSocket(data)
}
onLineRoom(roomsOnline)
{
this.props.notifyOnlineRooms(roomsOnline);
}
render()
{
let {messages,isLoading,disableTextArea}=this.state
let{selectedRoomId,showMessagePanel}=this.props
let messageStyle=(showMessagePanel==true) ? "message-panel":"message-panel hide-div"
return(
<div className={messageStyle}>
<div className='show-messages'>
{
(isLoading==true) ? <Spinner></Spinner> :
messages.map(message=>{
return <Message key={message.id} Message={message.Message} senderId={message.senderId} recieverId={message.recieverId} />
})
}
<div style={{ float: "left", clear: "both" }} ref={(el) => { this.messageEnd = el; }}></div>
</div>
<WriteMessage
isDisabled={disableTextArea}
// userInfo={userInfo}
selectedRoomId={selectedRoomId}
onLineRoom={this.onLineRoom}
onNewMessageArrival={this.onNewMessageArrival.bind(this)}/>
</div>
);
}
}
export default MessagePanel;
// the main class where the two components meets together
import React,{Component} from 'react';
import Constants from './Constants';
import Roompanel from './Rooms/Roompanel';
import MessagePanel from './Message/MessagePanel';
class Messagecontainer extends Component{
state={
showMessagePanel:true,
showRoomPanel:true,
onlinerooms:[]
}
constructor(props)
{
super(props);
this.setselectRoomId=this.setselectRoomId.bind(this);
this.fillRoominfointoSocket=this.fillRoominfointoSocket.bind(this);
this.notifyOnlinerooms=this.notifyOnlinerooms.bind(this);
}
fillRoominfointoSocket(message)
{
this.setState({newMessageFromSocket:message});
}
componentDidMount()
{
this.toggleMessageContainer(true,false);
}
toggleMessageContainer(showRoomPanel,showMessagePanel)
{
if(window.innerWidth<500)
{
this.setState({showRoomPanel,showMessagePanel});
}
}
setselectRoomId(id)
{
this.setState({selectedRoomid:id});
this.toggleMessageContainer(false,true);
}
notifyOnlinerooms(rooms)
{
this.setState({onlinerooms:rooms});
}
render()
{
let{showMessagePanel,showRoomPanel,selectedRoomid,newMessageFromSocket,onlinerooms}=this.state;
if(window.innerWidth<500)
{
showMessagePanel=false;
showRoomPanel=true;
}
return(
<div className='content'>
<Roompanel
showRoomPanel={showRoomPanel}
onlinerooms={onlinerooms}
setselectRoomId={this.setselectRoomId}
selectedRoomid={this.state.selectedRoomid}
newMessageFromSocket={newMessageFromSocket}/>
<MessagePanel
showMessagePanel={showMessagePanel}
selectedRoomid={selectedRoomid}
fillRoominfointoSocket={this.fillRoominfointoSocket}
notifyOnlinerooms={this.notifyOnlinerooms}/>
</div>
);
}
}
export default Messagecontainer;
can anybody explain me how to debug this problem as it is creating a bit of problem in my code ?
The component Roominfo receives this.setSelectedRoomid.bind(this,room.room) for the property setSelectedRoomId.
From the code you have exposed it's visible that you have a typo setSelectedRoomid which should be -> setselectRoomId.
setSelectedRoomId={this.setselectRoomId.bind(this,room.room)}
Edited: After code update
can you bind the roomId to the setSelectedRoomId ->
<div className={(activeRoomId==roomId) ? "room-info active-room":"room-info"} onClick={this.setSelectedRoomId.bind(this, roomId)}>
Every parameters after the first parameter of bind is passed to the method as variables e.g
const sum = function(a, b) {
return a+b
}
const sumOfThreeAndFive = sum.bind(this, 3, 5)
console.log(sumOfThreeAndFive()) // 3 + 5 -> 8
setSelectedRoomId(id) does not receive an id variable, instead it receives a click event that you must access with e.target.value
Additionally, you must also have a value prop set for the element that is clicked if you want to retrieve a value property from the click event
First add value={roomId} to the clickable div's props:
<div value={roomId} onClick={setSelectedRoomId} className={(activeRoomId==roomId) ? "room-info active-room":"room-info"}>
Then change your setSelectedRoomId method to destructure the click event:
setselectRoomId(e){
const id = e.target.value
this.toggleMessageContainer(false,true);
this.setState({selectedRoomid:id});
}

How do I access some data of a property I passed from parent component to child component?

I am learning React and I am trying to call a function in a child component, that accesses a property that was passed from parent component and display it.
The props receives a "todo" object that has 2 properties, one of them is text.
I have tried to display the text directly without a function, like {this.props.todo.text} but it does not appear. I also tried like the code shows, by calling a function that returns the text.
This is my App.js
import React, { Component } from "react";
import NavBar from "./components/NavBar";
import "./App.css";
import TodoList from "./components/todoList";
import TodoElement from "./components/todoElement";
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo(input) {
const newTodo = {
text: input,
done: false
};
const todos = [...this.state.todos];
todos.push(newTodo);
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" />
<button
onClick={() => this.addNewTodo(document.getElementById("text"))}
>
Add new
</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
export default App;
This is my todoElement.jsx
import React, { Component } from "react";
class TodoElement extends Component {
state = {};
writeText() {
const texto = this.props.todo.text;
return texto;
}
render() {
return (
<div className="row">
<input type="checkbox" />
<p id={this.writeText()>{this.writeText()}</p>
<button>x</button>
</div>
);
}
}
export default TodoElement;
I expect that when I write in the input box, and press add, it will display the text.
From documentation
Refs provide a way to access DOM nodes or React elements created in the render method.
I'll write it as:
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.textRef = React.createRef();
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo() {
const newTodo = {
text: this.textRef.current.value,
done: false
};
const todos = [...this.state.todos, newTodo];
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" ref={this.textRef} />
<button onClick={this.addNewTodo}>Add new</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
In your approach, what you got as an argument to the parameter input of the method addNewTodo is an Element object. It is not the value you entered into the text field. To get the value, you need to call input.value. But this is approach is not we encourage in React, rather we use Ref when need to access the html native dom.

How do I reveal my button only when on a specific component?

I want to show the Logout button on the same row of the title but only when the user has made it to Home component.
In other words, I don't want to show the logout button at all times, especially when the user's at the login screen. I want it to show on the same row of the title only when they've logged in successfully and they're in Home
How would I achieve this? My head hurts from trying to make this work :(
Below's what I've tried so far, among other things.
import React, {Component} from 'react';
import classes from './Title.css';
import LogoutButton from '../../containers/LogoutButton/LogoutButton';
import Home from '../../components/Home/Home';
class Title extends Component {
constructor(props) {
super(props);
this.state = {
show: false,
showLogoutButton: true
};
}
showButton() {
this.setState({show: true});
if(this.state.show) {
return <LogoutButton/>;
} else {
return null;
}
}
render() {
return(
<div>
{ this.state.showLogoutButton ? this.showButton : null }
<h1 className={classes.Title}>Pick Ups</h1>
</div>
);
}
}
export default Title;
You can try something like below. You don't need to deal with function and modifying states.
You can simply do like below
import classes from './Title.css';
import LogoutButton from '../../containers/LogoutButton/LogoutButton';
import Home from '../../components/Home/Home';
class Title extends Component {
constructor(props) {
super(props);
this.state = {
showLogoutButton: this.props.authenticated
};
}
render() {
const { showLogoutButton } = this.state;
return(
<div className="row" style={{"display" :"flex"}}>
{ showLogoutButton && <LogoutButton/>}
<h1 className={classes.Title}>Pick Ups</h1>
</div>
);
}
}
export default Title;
Note: When you modify state using setState the state value will be updated only after render so you can't directly check immediately modifying the value.

ReactJS change state of another class from child

I'm trying to understand the state.props to help me pass a value from a child to a parent. I have a header component (child) that when a link is clicked it should pass a value to a method inside the App component (parent). This method then sets the state of a value which is used for other things. I've tried binding the method inside the App.js file but that isn't working. I get the error when I click a link: ×
TypeError: _this.changeSearchState is not a function
Header.js
import React, { Component } from "react";
import { Link } from 'react-router-dom';
class Header extends Component {
changeSearch = (e) => {
e.preventDefault();
var type = this.value;
this.changeSearchState(type);
}
render() {
return (
<header>
<div className="float-right top-bar">
<Link onClick={this.changeSearch} value="users" to="/users">Users</Link>
<Link onClick={this.changeSearch} value="feedback" to="/feedback">Feedback</Link>
...
</div>
</header>
)
}
}
export default Header;
App.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
isOpen: false,
dataType: ''
}
this.changeSearchState = this.changeSearchState.bind(this);
}
changeSearchState = (dataType) => {
this.setState({
dataType: dataType
});
}
...
I think you want to pass changeSearchState to the Header component as a prop.
In render() for App.js:
<Header
changeSearchState={this.changeSearchState}
/>
Then to access it from Header.js:
changeSearch = (e) => {
e.preventDefault();
var type = this.value;
this.props.changeSearchState(type)
}
You can read more about components and props here.
You should update state just in one place. So you should update it in the Parent component and pass it down to the Child. You can access the method passed from parent to child like a normal prop. Something like this:
App.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
isOpen: false,
dataType: ''
}
this.changeSearchState = this.changeSearchState.bind(this);
}
changeSearchState = (dataType) => {
this.setState({
dataType: dataType
});
}
render() {
return (
<Header changeSearchState={this.changeSearchState} /> //this is what you want!!!
)
}
Header.js
import React, { Component } from "react";
import { Link } from 'react-router-dom';
class Header extends Component {
changeSearch = (e) => {
e.preventDefault();
var type = this.value;
this.props.changeSearchState(type); // call it here with this.props!!
}
render() {
return (
<header>
<div className="float-right top-bar">
<Link onClick={this.changeSearch} value="users" to="/users">Users</Link>
<Link onClick={this.changeSearch} value="feedback" to="/feedback">Feedback</Link>
...
</div>
</header>
)
}
}
export default Header;

ReactJS: Can not type into input field

I am starting to learn react and download and follow any tutorials in internet. I am trying to build friend list.
I have tree components,
friends_container:
import React from 'react';
import AddFriend from './add_friend.jsx'
import ShowList from './show_list.jsx'
class FriendsContainer extends React.Component {
constructor() {
this.state = {
friends: ['Jake Lingwall', 'Murphy Randall', 'Merrick Christensen']
}
}
addFriend(friend) {
this.setState({
friends: this.state.friends.concat([friend])
});
}
render() {
return (
<div>
<h3> Add your friend to friendslist </h3>
<AddFriend addNew={this.addFriend}/>
<ShowList names={this.state.friends}/>
</div>
)
}
}
export default FriendsContainer;
add_friend:
import React from 'react';
class AddFriend extends React.Component {
constructor() {
this.state = {newFriend: ''};
}
updateNewFriend(e) {
this.setState({
newFriend: e.target.value
})
}
handleAddNew() {
this.props.addNew(this.state.newFriend);
this.setState({
newFriend: ''
});
}
render() {
return (
<div>
<input type="text" value={this.state.newFriend} onChange={this.updateNewFriend}/>
<button onClick={this.handleAddNew}>Add Friend</button>
</div>
)
}
}
AddFriend.propTypes = { addNew: React.PropTypes.func.isRequired };
export default AddFriend;
show_list:
import React from 'react';
class ShowList extends React.Component {
render() {
var listItems = this.props.names.map((f, i) => <li key={i}>{f}</li>);
return (
<div>
<h3>Friends</h3>
<ul>
{listItems}
</ul>
</div>
)
}
}
ShowList.defaultProps = { names: [] };
export default ShowList;
and app.jsx
import React from 'react';
import FriendsContainer from './components/friends_container.jsx';
window.React = React;
React.render(<FriendsContainer />, document.body);
as you can see on the code, I am using es6 and babel as transcompiler.
My problem, I can not type any letters into the input field to add new friend into friends list. What am I doing wrong?
In the context of your updateNewFriend method, this refers to the window object and not the current component instance. You need to bind the method before passing it as the onChange event handler. See Function::bind.
You have two options:
class AddFriend extends React.Component {
constructor() {
// ...
this.updateNewFriend = this.updateNewFriend.bind(this);
this.handleAddNew = this.handleAddNew.bind(this);
}
}
or
class AddFriend extends React.Component {
render() {
return (
<div>
<input type="text" value={this.state.newFriend} onChange={this.updateNewFriend.bind(this)}/>
<button onClick={this.handleAddNew.bind(this)}>Add Friend</button>
</div>
)
}
}
Keep in mind that Function::bind returns a new function, so binding in render creates a function every time your component is rendered, though the performance impact is negligible.

Categories