I reuse the Chat component twice into another component. It display when you click Chat button but it overlaps each other.
class Chat extends React.Component {
constructor() {
super();
this.state = {
show: false,
};
}
reset = () => {
this.setState(false);
}
open = () => {
this.setState({ show: true });
}
close = () => this.setState({ show: false });
render() {
return (<div className="chat">
<button className="btn-yes round" onClick={this.open}>{this.props.title}</button>
{this.state.show &&
<div className="show-chat">
<div className="chat-head">Now Chatting <i className="fas fa-angle-down" onClick={this.close}></i></div>
<div className="chat-body">
<div className="blue">Teresa wants to chat about her healthcare finances</div>
<ul>
<li><img src={agentPhoto} alt="chat agent avatar" /></li>
<li>
<h6>John Newman</h6>
<div className="gray">Hi Teresa!</div>
<div className="gray">Here is the link to the finance tool we discussed.</div>
<div className="gray">If you have any questions, let me know!</div>
</li>
</ul>
</div>
<input placeholder="Type here and hit enter to chat"></input>
</div>}
</div>);
}
}
I expect to display chat one at a time. When I click the Chat button 2 and the Chat 1 is displayed, Chat 1 should be hidden.
Essentially, you need to give each Chat component an identifier and keep track of the one that is currently opened.
Here is the basic structure for your Parent component:
class App extends React.Component {
state = {
currentChatId: null
};
handleOpen = id => {
this.setState({
currentChatId: id
});
};
render() {
return (
<div>
<Chat
identifier={1}
currentChatId={this.state.currentChatId}
handleOpen={this.handleOpen}
/>
<Chat
identifier={2}
currentChatId={this.state.currentChatId}
handleOpen={this.handleOpen}
/>
</div>
);
}
}
So notice, we give each Chat component an identifier prop. We will use identifier to update the active chat - which we stored as a value called currentChatId in our parent-state. That is all done through the handleOpen() event-handler, which we also pass down as a prop to Chat.
Now in your Chat component, we need to configure logic for open() and componentDidUpdate()
class Chat extends React.Component {
constructor() {
super();
this.state = {
show: false
};
}
componentDidUpdate(prevProps) {
const { identifier, currentChatId } = this.props;
if (this.props.currentChatId !== prevProps.currentChatId) {
this.setState({
show: identifier === currentChatId ? true : false
});
}
}
open = () => {
const { identifier, handleOpen } = this.props;
handleOpen(identifier);
};
render() {
return (
<div className="chat">
<button className="btn-yes round" onClick={this.open}>
{this.props.title}
</button>
{this.state.show && (
<div className="show-chat">
<div className="chat-head">
Now Chatting{" "}
<i className="fas fa-angle-down" onClick={this.close} />
</div>
<div className="chat-body">
<div className="blue">
Teresa wants to chat about her healthcare finances
</div>
<ul>
<li>
<img src={""} alt="chat agent avatar" />
</li>
<li>
<h6>John Newman</h6>
<div className="gray">Hi Teresa!</div>
<div className="gray">
Here is the link to the finance tool we
discussed.
</div>
<div className="gray">
If you have any questions, let me know!
</div>
</li>
</ul>
</div>
<input placeholder="Type here and hit enter to chat" />
</div>
)}
</div>
);
}
}
Workflow:
User clicks one of the Chat buttons, triggering handleOpen()and we
pass in the unique identifier....
That gets passed back up to the Parent, and now currentChatId
should be updated with the identifier...
That currentChatId gets passed back down to the Chat component as the
currentChatId prop...
Triggers componentDidUpdate() on all Chat components, and we check
the currentChatId against their own identifiers, only one will be
matching, so we display that one.
See codesandbox for working example: https://codesandbox.io/s/react-example-kgm2h
Related
I have info button that is supposed to open specific description element onclick event - info is obtained from Firebase. However, myOnclick event triggers all of the siblings elements and I need to toggle/untoggle only specific one. What am I missing and doing wrong?
here's the code:
import React, { Component } from "react";
import firebase from "../../firebase";
//Data obtained from DB and rendered on page
export default class Tour extends Component {
constructor() {
super();
this.state = {
tours: [],
showInfo: false,
};
}
// button that toggles info
handleInfo = () => {
this.setState({
showInfo: !this.state.showInfo,
});
};
// component did mount
componentDidMount() {
const dbRef = firebase.database().ref();
dbRef.on("value", (snapshot) => {
// checking changes in db
const data = snapshot.val();
const newToursAarray = [];
for (let inventoryName in data) {
const toursObject = {
id: inventoryName,
tours: data[inventoryName],
name: data[inventoryName].name,
seats: data[inventoryName].seats,
date: data[inventoryName].date,
duration: data[inventoryName].duration,
imgUrl:"https://source.unsplash.com/350x350/?" + data[inventoryName].name,
// temporary tour info placeholder and will be removed and connetcted to real DB
info: "Lorem ipsum dolora saepe fugiat. " +
data[inventoryName].name,
};
newToursAarray.push(toursObject);
}
this.setState({
tours: newToursAarray,
});
});
}
render() {
return (
<div className="tourlist">
{this.state.tours.map((toursObject) => {
return (
<section className="tourItem">
<header>
<h3> {toursObject.name} </h3>
<h5>
info
{/* button that toggles info */}
<span onClick={this.handleInfo}>
<i className="fas fa-caret-square-down"></i>
</span>
</h5>
</header>
<ul className="inventoryItem" key={toursObject.id}>
<li> {toursObject.date} |</li>
<li>{toursObject.duration} hrs |</li>
<li> {toursObject.seats} seats </li>
</ul>
<div className="img-container">
{this.state.showInfo && (
// text that toggles when clicking on info button
<p className="tour-info">{toursObject.info}</p>
)}
<img src={toursObject.imgUrl} alt="image of the tour" />
</div>
</section>
);
})}
</div>
);
}
}
So the issue here is that the expanding options are all referencing the same piece of state. You have your state.showInfo outside of your tours.map. So as you map through your tours, you say "if 'showInfo' then reveal this section" and that 'showInfo' is the same for everything.
What I would recommend is storing the id of the expanded tourObject, and then you can check against that.
Something like this:
handleInfo = (id) => {
this.setState({
infoToShow: id,
});
};
And then in your onClick it would look more like this:
<span onClick={() => this.handleInfo(tourObject.id)}>
<i className="fas fa-caret-square-down"></i>
</span>
And your logic to show or hide can just be this:
{this.state.infoToShow === tourObject.id && (
// text that toggles when clicking on info button
<p className="tour-info">{toursObject.info}</p>
)}
This way you can store the id in a place accessible to all the looped through tours, but they won't conflict with each other.
So instead of checking if showInfo is true, check if the id that you want to show matches the id of the tour.
I want to add a feature that increments how many "likes" someone gets similar to FB. The profiles are getting passed in through an Axios GET request through a 3rd party API. When a user clicks on the like button, the amount of likes someone gets should increment by 1. The code I previously wrote in handleClicks() increments everyone's likes by 1 rather than just one individual person. The data is passed into cards[] in one chunk.
App.js
<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>
class App extends React.Component {
constructor(props) {
super(props)
this.state = {cards: [], numVotes: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log("This is working!");
this.setState(numVotes: state.numVotes + 1})
}
componentDidMount() {
axios.get('/')
.then(res => {
this.setState({cards: res.data})
console.log(this.state);
})
}
render() {
return (
<div className="main-container">
<Header />
<CardList
cards={this.state.cards}
handleClick={this.handleClick}
/>
<hr className="ui divider"></hr>
</div>
);
}
export default App;
const CardList = props => {
const cards = props.cards.map(card => {
return <Card image={card.image_url}
name={card.name}
title={card.title}
blurb={card.bio}
handleClick={props.handleClick}
numVotes={props.numVotes}
/>
})
return <div className="ui divided items">
{cards}
</div>
}
Card.js
const Card = (props) => {
return (
<div className="card-component item">
<div class="ui small rounded image">
<img className="portrait"
src = {props.image}
onError={(e)=>{e.target.onerror = null; e.target.src='https://image.shutterstock.com/image-vector/no-image-available-vector-illustration-260nw-744886198.jpg'}}
/>
</div>
<div class="content">
<a className="header">{props.name}</a>
<div class="meta">
<span className="title">{props.title}</span>
</div>
<p className="blurb">{props.blurb}</p>
<p><span className="question"> Want to work with {props.name}?</span>
<span className="like-button" onClick={props.handleClick}>
<img className="icon" src={icon} />Yes!
</span>
</p>
<p className="yes-amt">{props.numVotes} people have said Yes!</p>
</div>
</div>
)
}
You need to first decide how to identify each card as unique so you can update the correct one. If you have an id that would be ideal, but I'm going to assume the name is unique since its a value in your question.
// pass the unique identifier to handler
handleClick(name) {
this.setState((prevState) => ({
// map over the previous cards and return a new array
cards: prevState.cards.map((card) => {
// If the name matches the current card, change it
if (card.name === name) {
return {...card, numVotes: card.numVotes + 1};
} else {
// Otherwise just return the same card unchanged.
return card;
}
})
}))
}
Then in your component use it like this:
// Use inline function so we can pass it a prop as a parameter
<span className="like-button" onClick={() => props.handleClick(props.name)}>
I am building an app which uses user input and shows number of recipes and they can click on recipe card to view ingredients as well. Every time they click on recipe card I make an API call to get appropriate recipe ingredient. But I am not able to figure out how to show the component which contains the recipe ingredients. I tried with conditional routing and conditional rendering as well but couldn't find the solution.
Recipe_Template.js
export class RecipeTemplate extends Component {
renderRecipe = recipeData => {
return recipeData.recipes.map(recipeName => {
return (
<div className="container">
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<a
href={recipeName.source_url}
target="_blank"
onClick={() => {
this.props.fetchRecipeId(recipeName.recipe_id);
}}
>
<img
src={recipeName.image_url}
className="mx-auto d-block img-fluid img-thumbnail"
alt={recipeName.title}
/>
<span>
<h3>{recipeName.title}</h3>
</span>
</a>
<span}>
<h3>{recipeName.publisher}</h3>
</span>
</div>
</div>
</div>
);
});
};
render() {
return (
<React.Fragment>
{this.props.recipe.map(this.renderRecipe)}
</React.Fragment>
);
}
}
Recipe_Detail.js
class RecipeDetail extends Component {
renderRecipeDetail(recipeData) {
return recipeData.recipe.ingredients.map(recipeIngredient => {
return <li key={recipeIngredient}>recipeIngredient</li>;
});
}
render() {
if (this.props.recipeId === null) {
return <div>Loading...</div>;
}
return <ul>{this.props.recipeId.map(this.renderRecipeDetail)}</ul>;
}
}
function mapStateToProps({ recipeId }) {
return { recipeId };
}
export default connect(mapStateToProps)(RecipeDetail);
Not entirely sure why you would need Redux here (unless it's being shared among other nested components), but I'm fairly certain you can just utilize React state.
One approach would be to configure your routes as such:
<Route path="/recipes" component={Recipes} />
<Route path="/recipe/:id" component={ShowRecipe} />
When the user sends a query, gets some results, and you display all matching recipes to a Recipes component. Each recipe then has a name (and other associated displayable data) and a clickable link:
<Link to={`/recipe/id?recipeId=${recipeId}`}>View {recipeName} Recipe</Link>
which for simplicity sake might look like:
<ul>
<Link to="/recipe/id?recipeId=08861626">View Prosciutto Bruschetta Recipe</Link>
<Link to="/recipe/id?recipeId=04326743">View Pasta Bundt Loaf Recipe</Link>
...etc
</ul>
When the user clicks on the link, react-router sends the user to the ShowRecipe component with a unique recipeId.
ShowRecipe then makes another AJAX request to get the recipe details:
ShowRecipe.js
export default class ShowRecipe extends Component {
state = { recipeDetail: '' }
componentDidMount = () => {
const { recipeId } = this.props.location.query; // <== only natively available in react-router v3
fetch(`http://someAPI/recipe/id?recipeId=${recipeId}`)
.then(response => response.json())
.then(json => this.setState({ recipeDetail: json }));
}
render = () => (
!this.state.recipeDetails
? <div>Loading...</div>
: <ul>
{this.state.recipeDetail.map(ingredient => (
<li key={ingredient}>ingredient</li>
)}
</ul>
)
}
Another approach:
Have the recipeDetails stored and available within the original fetched recipes JSON. Then map over the recipes and create multiple <Card key={recipeId} recipeName={recipeName} recipeDetail={recipeDetail} /> components for each recipe.
which for simplicity sake might look like:
<div>
{this.state.recipes.map(({recipeId, recipeName, recipeDetail}), => (
<Card key={recipeId} recipeName={recipeName} recipeDetail={recipeDetail} />
)}
</div>
Then each individual Card has it's own state:
Card.js
export default class Card extends Component {
state = { showDetails: '' }
toggleShowDetails = () => this.setState(prevState => ({ showDetails: !this.state.showDetails }))
render = () => (
<div>
<h1>{this.props.recipeName} Recipe</h1>
<button onClick={toggleShowDetails}> {`${!this.state.showDetails ? "Show" : "Hide"} Recipe<button>
{ this.state.showDetails &&
<ul>
{this.props.recipeDetail.map(ingredient => (
<li key={ingredient}>ingredient</li>
)}
</ul>
}
)
}
Therefore, by default the recipeDetail is already there, but hidden. However, when a user clicks the Card's button, it will toggle the Card's showDetails state to true/false to display/hide the recipe detail.
I am new to react. I am fetching github user info on search. I unable
to fetch data in my child component. this is my code below.
whats the problem , cant i use this.state.userList.map
class SearchHeader extends Component {
constructor(props) {
super(props);
this.state = {
errorMessage: '',
userList: [],
isOpen: false,
userName:''
};
this.toggle = this.toggle.bind(this);
this.getUsers = this.getUsers.bind(this);
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
// componentWillMount(){
// this.getUsers();
// }
getUsers(e) {
console.log('get users called='+e.target.value);
fetch('https://api.github.com/search/users?q='+ e.target.value)
.then(res => res.json())
.then(
userList =>{
this.setState({userList: userList})
console.log(userList);
}
);
}
render() {
return (
<div>
<nav className="navbar navbar-expand-lg navbar-light bg-primary navbar-inner">
<div className="collapse navbar-collapse navbar-inner navb" >
<ul className="navbar-nav bg-light mr-auto">
<li className="nav-item dropdown">
<a className="nav-link dropdown-toggle auto" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Sort
</a>
<div className="dropdown-menu" aria-labelledby="navbarDropdown">
<a className="dropdown-item" href="#">Sort by Name (ascending)</a>
<a className="dropdown-item" href="#">Sort by Name (descending)</a>
<div className="dropdown-divider"></div>
<a className="dropdown-item" href="#">Sort by Rank (ascending)</a>
<a className="dropdown-item" href="#">Sort by Rank (descending)</a>
</div>
</li>
</ul>
<form className="form-inline my-2 my-lg-0 auto" onSubmit={this.getUsers}>
<div className="form-group">
<input className="form-control mr-sm-2" type="Search" placeholder="Search"
aria-label="Search"
id="userName"
onKeyUp={this.getUsers} >
</input>
</div>
</form>
</div>
</nav>
<div >
<UserList userList={this.state.userList}/>
</div>
</div>
);
}
}
export default SearchHeader;
This is my child component below where I am fetching data from parent
component
This is my child component below where I am fetching data from parent
component
class UserList extends Component {
constructor(props) {
super(props)
this.state ={
users:this.props.userList
}
}
render() {
return (
<div className="container-fluid">
<br />
{
this.state.users.map((user)=>
<div className="jumbotron container">
{user.login}
</div>
)
}
</div>
);
}
}
export default UserList;
You have several problems in your components:
do not copy parent's state into chilren states: users:this.props.userList. Use this.props directly instead and React will know it must re-render children
do not rely on current state to set new state. Use function with prevState instead of isOpen: !this.state.isOpen.
make a copy of event's value before passing it to setState like this const {value} = e.target;
assign unique key to each user in your list (not indexes!), or it won't re-render correctly on list update
So your code would look like this:
class SearchHeader extends Component {
constructor(props) {
super(props);
this.state = {
errorMessage: '',
userList: [],
isOpen: false,
userName:''
};
}
toggle = () => {
this.setState( (prevState) => ({
isOpen: !prevState.isOpen
}));
}
getUsers = (e) => {
const {value} = e.target;
console.log('get users called='+value);
fetch('https://api.github.com/search/users?q='+ value)
...
}
}
and:
class UserList extends Component {
// Use default constructor
render() {
const users = this.props.userList.map( (user) => (
<div className="jumbotron container" key={user.login}>
{user.login}
</div>
));
return (
<div className="container-fluid">
<br />
{users}
</div>
);
}
}
parent component change should be.
getUsers(e) {
console.log('get users called='+e.target.value);
fetch('https://api.github.com/search/users?q='+ e.target.value)
.then(res => res.json())
.then(
userList =>{
this.setState({userList: userList.items})
console.log(userList);
}
);
}
Change your user list and check initially values are there or not and you dont need to user state in userList component.
that is all because initially there are no values also there can be an case when you are setting state for userList value after fetching data that might be coming as null undefined or something else so put an console log there and check that too.
class UserList extends Component {
render() {
return (
{
this.props.userList && this.props.userList.length && this.props.userList.map((user)=>
{user.login}
)
}
</div>
);
}
}
export default UserList;
I am new to react. In my project, I have a Home Component.
class Home extends Component {
logOut(){
console.log('log out in side home js');
this.props.LogOutAction();
localStorage.setItem('loginFlag', null);
}
render(){
return(
<div>
<Header />
<br/>
<div className="col-md-12 ">
<div className="col-md-3 home-border">
a
</div>
<div className="col-md-6 home-border">
<User/>
</div>
<div className="col-md-3 home-border">
c
</div>
</div>
</div>
)
}
}
And my User component is
class User extends React.Component {
viewProfile(id){
}
render() {
var userdetails = [ {"id":"1", "name":"Abraham", "country": "USA", "age":"29", "image":""},
{"id":"2", "name":"Gregory", "country": "Canada", "age":"23", "image":""},
{"id":"3", "name":"Mathews", "country": "Newzeland", "age":"24", "image":""},
{"id":"4", "name":"Williamson", "country": "China", "age":"27", "image":""},
{"id":"5", "name":"Edwerd", "country": "Germany", "age":"22", "image":""}
];
var myStyle = {
border:"1px solid black",
height:"170px",
}
return(
<div>
<div>
{userdetails.map((data, i) => <div className="col-md-12 div-bottom" style={myStyle} key={i}>
<div className="col-md-12">
<div className="col-md-12">
<img className="img-thumbnail img-margin" width="150" height="236" src = {'profile.jpg'} />
</div>
<div className="col-md-12 div-bottom"><label>{data.name}</label></div>
<div className="col-md-12 div-bottom">
<div className="col-md-6"><label>age:</label> {data.age}</div>
<div className="col-md-3"><label>Country:</label> {data.country}</div>
</div>
<div className="col-md-4 .div-bottom"><button className="btn btn-primary" onClick={ () => { this.viewProfile(data.id) } }>view Profile</button></div>
</div>
</div>)
}
</div>
</div>
)
}
}
I want to show the more user details in place of 'c' in the Home Component on the button click in the User Component.What is the correct method and how it can done?. Thanks in advance..
There are two approaches. First is to use React only. You create a parent component, containing the state of the app. In the parent component you can create a function that changes the state. You can pass the function to your user component and the state to the home component.
Simplified:
const Home = ({ user }) => <div>{user.name}</div>;
const User = ({ changeUser }) => {
const users = [
{ name: 'User1' },
{ name: 'User2' },
];
const renderUsers = () => users.map(
user => <li onClick={() => changeUser(user)}>{user.name}</li>
);
return <ul>{renderUsers()}</ul>;
};
class Parent extends React.Component {
constructor(props) {
super(props);
this.changeUser = this.changeUser.bind(this);
this.state = { user: null };
}
changeUser(user) {
this.setState({ user });
}
render() {
return (
<div>
<Home user={this.state.user} />
<User changeUser={this.changeUser} />
</div>
);
}
}
However, when your app grows it becomes very complicated to manage the state via a parent component. You can use something like a "Flux" architecture for this. "Redux" is the most common way in the React world for building up Flux like architecture.
What does 'a' stand for?
First of all , who does query you user's data?
I think Home must handle server calls, then you need to store that data in the Home component's state, then you need to pass them as props to the User and UserDetails component.
In my opinion Flux or Redux are needed only if your application become really big, and you don't want to have to many server calls during the session.
As a tips, if your User and UserDetails components only need to visualize data you can try to make them 'stateless' (refer to stateless components), in than way you don't need to handle willReceiveProps.
You can Handle 'viewProfile' method in the Home component, calling it from the User component with a callback (passed as a prop)
you can send an event as props and call it from other component.
Say you have a class
Class Home {
handleChange(evt) {
this.setState({ username: evt.target.value });
}
render {
return (
<div>
<Users name={this.state.username} onChange={this.handleChange}/>
<div> {this.state.username} </div>
</div>
);
}
}
Child Component
Class Users {
handleChange() {
//logic
}
render {
return (
<div>
<input type="text" onChange={this.props.onChange}/>
{this.props.name}
</div>
);
}
}
Here in Component Users when you change the input it will call the
method of class Home and update state of Home .
Now getting the updated state as props in component Users will give
you the changed text that you just entered