I have the code below, what i want it to do is: when i click the quizzes button , the div with renderComponent in it renders the list of quizzes as in QuizList function, when i press Members button i want the same div to render the list of members as in MemberList function, but i can't manage to do it, could you help me with it?
class GroupPage extends React.Component {
constructor(props){
super(props);
this.state={
members:[],
quizzes:[]
}
}
QuizList = () =>{
const {quizzes} = this.state;
return(
<div>{
quizzes.map(({id,...otherQuizProps}) => (
<QuizItem key={id} id={id}{...otherQuizProps}/>
))
}
</div>
)
}
MemberList = () =>{
const {members} = this.state;
return(
<div>{
members.map(({id,...otherMemberProps}) => (
<GroupMember key={id} id={id}{...otherMemberProps}/>
))
}
</div>
)
}
renderComponent = (component) =>{
switch(component){
case "QuizList":
return <this.QuizList/>
case "MemberList":
return <this.MemberList/>
default:
return <this.QuizList/>
}
}
render(){
const {quizzes,members} = this.state;
return(
<div className='group-page'>
<div className='group-controls'>
<div className='group-buttons'>
<button onClick={}>Quizzes</button>
<button onClick={}>Members</button>
</div>
</div>
<div>
{this.renderComponent()};
</div>
</div>
)}
};
In the render method you don't provide any parameter to renderComponent. So everytime the default case will be returned, because the parameter component is undefined.
I would store the current selection inside the state of the component and set it with the buttons accordingly.
constructor(props){
super(props);
this.state={
members:[],
quizzes:[],
currentMode: 'QuizList',
}
}
...
render(){
const {quizzes,members, currentMode} = this.state;
return(
<div className='group-page'>
<div className='group-controls'>
<div className='group-buttons'>
<button onClick={()=>this.setState({currentMode:'QuizList'}) }>Quizzes</button>
<button onClick={()=>this.setState({currentMode:'MemberList'}) }>Members</button>
</div>
</div>
<div>
{this.renderComponent(currentMode)};
</div>
</div>
)}
}
Related
I have been attempting to toggle a class on click so that when I click on one of the mapped items in my Tasks component, I add the 'complete' class and put a line through that item (crossing items off of a todo list). However with my current code set up, when I click on one element to add the class, all the other elements get crossed out as well and vice versa.
Here is my current setup. The class 'complete' is what will add a line through one of the mapped items in the Tasks component.
import { Container, Row} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import axios from 'axios';
const List = (props) =>{
return(
<div>
<Link style={{textDecoration:'none'}} to={`/lists/${props.listId}`} > <p className="list-item">{props.item}</p></Link>
</div>
)
}
const Tasks = (props) =>{
return(
<div onClick={props.onClick} className={props.className} >
<div className='task-item' >
<p >{props.item}</p>
</div>
</div>
)
}
export default class Display extends Component {
constructor(props){
super(props)
this.onCompletedTask = this.onCompletedTask.bind(this);
this.state = {
list: [],
tasks:[],
complete:false
}
}
componentWillUpdate(nextProps){
axios.get(`http://localhost:8080/lists/${this.props.match.params.listId}`)
.then(response =>{
this.setState({
tasks:response.data
})
})
}
componentDidMount(){
axios.get('http://localhost:8080/lists')
.then(response=>{
this.setState({
list:response.data
})
})
.catch(error =>{
console.log(error)
});
}
onCompletedTask(item){
this.setState({ complete: !this.state.complete});
}
listCollection(){
return(
this.state.list.map(item=>{
return(<List item = {item.title} listId={item._id} key = {item._id} />)
})
)
}
taskCollection(){
return(
this.state.tasks.map((item, index) =>{
return(<Tasks onClick = {()=>this.onCompletedTask(item)} className={this.state.complete ? 'complete': ''} item={item.task} key={index}/>)
})
)
}
render() {
return (
<div id='main' >
<Container>
<Row>
<div className="sidebar">
<h1 style={{fontSize:"25pt"}}>Lists</h1>
<div className="list-menu">
{this.listCollection()}
</div>
<form action='/new-list' method='GET'>
<div style={{textAlign:'center'}}>
<button className='list-button' style={{fontSize:'12pt', borderRadius:'5px'}}>
+ New List
</button>
</div>
</form>
</div>
<div className='tasks'>
<h1 style={{fontSize:'25pt'}}>Tasks</h1>
{this.taskCollection()}
<form action={`/lists/${this.props.match.params.listId}/new-task`} method='GET'>
<button className='task-button'>
+
</button>
</form>
</div>
</Row>
</Container>
</div>
)
}
}
Your state holds only a single completed value, which OFC toggle all tasks. You could instead store a map of completed tasks.
this.state = {
list: [],
tasks: [],
complete: {}, // <--- use empty object as simple map object
}
Update onCompletedTask to store some uniquely identifying property of a task, like an id field
onCompletedTask(item){
this.setState(prevState => ({
completed: {
...prevState.completed, // <--- spread existing completed state
[item.id]: !prevState.completed[item.id] // <--- toggle value
},
}));
}
Update. taskCollection to check the completed map by id
taskCollection = () => {
const { completed, tasks } = this.state;
return tasks.map((item, index) => (
<Tasks
onClick={() => this.onCompletedTask(item)}
className={completed[item.id] ? "complete" : ""} // <--- check completed[item.id]
item={item.task}
key={index}
/>
))
};
I am new to both coding as well as React.js, so any assistance in learning what I am doing incorrectly is greatly appreciated! I am creating multiple cards on a page with riddles where the answer is hidden via css. I am using an onClick function ("toggleAnswer") to toggle the state of each answer to change the className so that the answer will either be visible or hidden. Currently, the onClick event is changing the state for all the answers. I realize this is because my code is not targeting a particular element, but I am unsure how this can be done. How can this be achieved? My code is currently like this:
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
isHidden: true
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e) {
this.setState({ isHidden: !this.state.isHidden });
}
componentWillMount() {
this.getPageData();
}
render() {
const answerClass = this.state.isHidden ? "answer-hide" : "answer";
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={answerClass}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={props.onClick}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
You'd have to keep track of each answer that has been shown in state (in an array or something).
First
Send the index of the answer up in the onclick function. In that function, check if it exists in the "shownAnswers" array and either add or remove it.
onClick={e => props.onClick(e, props.id)}
and
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({
shownAnswers: this.state.shownAnswers.filter(val => val !== index)
});
} else {
this.setState({
shownAnswers: this.state.shownAnswers.concat(index)
});
}
}
Then
When you're passing the class name down to the child component, check if its index is in the "shownAnswers" array to decide which class name to pass.
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
Building off your example, it could look something like this (untested):
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
shownAnswers: []
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({ shownAnswers: this.state.shownAnswers.filter(val => val !== index) });
} else {
this.setState({ shownAnswers: this.state.shownAnswers.concat(index) });
}
}
componentWillMount() {
this.getPageData();
}
render() {
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={e => props.onClick(e, props.id)}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
I have a component built using the below code. The aim is to add a class on the card to highlight it when the button inside it is clicked. However, the below code works on the first click but doesn't work for the subsequent clicks.
I understood that I have to set the clicked state of other elements to false when I remove the class. How can this be done?
import React, { Component } from 'react';
import './PricingCard.css';
class PricingCard extends Component {
constructor(){
super();
this.state = {
clicked : false
}
}
makeSelection(){
let elems = document.getElementsByClassName('Card');
for(var i=0;i<elems.length;i++){
elems[i].classList.remove("active");
}
this.setState({clicked: true});
}
render() {
var activeClass = this.state.clicked ? 'active' : '';
return (
<div className= {"categoryItem Card " + this.props.planName + " " +activeClass}>
<div className="cardDetails">
<div> {this.props.planName} </div>
<div className="pricing"> {this.props.price} </div>
<button onClick={this.makeSelection.bind(this)} className="buttonPrimary"> Select this plan </button>
<div className="subtitle"> {this.props.footerText} </div>
</div>
</div>
);
}
}
export default PricingCard;
Wouldn't it be easier to have the logic in a parent component? Since it is "aware" of all the child Card components.
Have something like...
this.state = { selectedComponent: null };
onClick(card_id) {
this.setState({ selectedComponent: card_id });
}
...in render:
const cards = smth.map((card) =>
<Card onClick={this.onClick.bind(this, card.id)}
isActive={map.id === this.state.selectedComponent} />
Would this work?
Best way will be to lift lift the state up. Like this:
class PricingCardContainer extends React.Component {
constructor(props){
super(props);
this.state = {
selectedCard: NaN,
}
}
handleCardClick(selectedCard){ this.setState({ selectedCard }); }
render() {
return (
<div>{
this.props.dataArray.map((data, i) =>
<PricingCard
key={i}
className={this.state.selectedCard === i ? 'active': ''}
price={data.price}
onClick={() => this.handleCardClick(i)}
footerText={data.footerText}
planName={data.planName}
plan={data.plan}
/>
)
}</div>
)
}
}
const PricingCard = ({ className = '', planName, price, onClick, footerText }) => (
<div className= {`categoryItem Card ${planName} ${className}`}>
<div className="cardDetails">
<div> {planName} </div>
<div className="pricing"> {price} </div>
<button onClick={onClick} className="buttonPrimary"> Select this plan </button>
<div className="subtitle"> {footerText} </div>
</div>
</div>
);
export default PricingCard;
Although it would be better to use some data id than index value.
What implementation is needed to render different components from render method. As you can see below the idea is that Survey component receives an array which contains different components names (could be Input, CheckList, Dropdown, File). The array passes as property to Survey Component is generated properly depending of what button is clicked, but at the time to render different components is not working. I'm using JsComplete to test it.
const Dropdown = () =>{
return(
<div>
<select>
<option value="initial" selected>Select...</option>
<option value="Option ">Option 1</option>
<option value="Option ">Option 2</option>
</select>
</div>
)
}
const Checklist = () =>{
return(
<div>
<h4>Question name</h4>
<label>
Option 1:
<input
name="pl"
type="checkbox" />
</label>
<label>
Option 2:
<input
name="tz"
type="checkbox" />
</label>
</div>
)
}
const Input = () =>{
return(
<div>
<label>
Question name:
<input
name="input"
type="text" />
</label>
</div>
)
}
const File = () =>{
return(
<div>
<label>
Upload:
<input
name="file"
type="file" />
</label>
</div>
)
}
class Survey extends React.Component {
constructor(props){
super(props);
}
render(){
var ChildName ;
for (var i = 0; i < this.props.components.length; i++) {
log("Log:" + this.props.components[i]);
ChildName = this.props.components[i];
return <ChildName />;
}
return (
false
)
}
}
class Form extends React.Component {
handleSubmit = (name) => {
this.props.onSubmit(name);
};
render() {
return (
<div id="components">
<button onClick={()=>this.handleSubmit("Input")} name="Input">Input</button>
<button onClick={()=>this.handleSubmit("Checklist")} name="Checklist">Checkbox</button>
<button onClick={()=>this.handleSubmit("Dropdown")} name="Dropdown">Dropdown</button>
<button onClick={()=>this.handleSubmit("File")} name="File">File</button>
<div id="new-question">
</div>
</div>
)
}
}
class App extends React.Component {
state = {
components: []
};
addNewElement = (element) => {
this.setState(prevState => ({
components: prevState.components.concat(element)
}));
};
render() {
return (
<div>
<Form onSubmit={this.addNewElement} />
<Survey components={this.state.components} />
</div>
);
}
}
ReactDOM.render(<App />, mountNode);
Try this. Dont pass string in handleSubmit method. Instead pass component itself like this:
class Form extends React.Component {
handleSubmit = (name) => {
this.props.onSubmit(name);
};
render() {
return (
<div id="components">
<button onClick={()=>this.handleSubmit(Input)} name="Input">Input</button>
<button onClick={()=>this.handleSubmit(Checklist)} name="Checklist">Checkbox</button>
<button onClick={()=>this.handleSubmit(Dropdown)} name="Dropdown">Dropdown</button>
<button onClick={()=>this.handleSubmit(File)} name="File">File</button>
<div id="new-question">
</div>
</div>
)
}
}
Also in you survey component return the elements like this
class Survey extends React.Component {
constructor(props) {
super(props);
}
render() {
if (this.props.components.length === 0) {
return null;
}
const renderCommpos = this.props.components.map((Elem, index) => {
return <Elem key={index} />
});
return (
<div>
{renderCommpos}
</div>
);
}
}
Also notice the Elem in map function. When it comes to react component jsx needs the first letter capital. So doesn't matter what variable you keep at place of Elem, you should always keep the first letter capital.
The render method for your survey component should be like this :
render(){
const { components } = this.props;
return (
<div>
{
components.map((c, index) => {
return (
<div key={`one-of-components-${index}`}>
{c}
</div>
);
})
}
</div>
);
}
Now it will return all the components in the props.
Try this out.
const Survey = ({ components }) => {
const Components = components.map(
( component, index ) => {
return (
<div key={ index }>
{ component }
</div>
);
}
);
return (
<div>
{ Components }
</div>
);
};
In your for loop you're returning from the function on the first component. Add them to an array and then return the array. On another note, I used a functional stateless component here. I don't see the need for the class overhead.
I have developed a page which contains a single recipe page and there is an order button. When order button is clicked the order of that recipe should be shown along with the price just side of recipe section. But I get sorry recipe no longer available. In the render section inside Order Component i get an id of order. But why i get an undefined error inside renderOrder function(this.props.order[key] and this.props.recipe[key]). Why is that so?
export default class SingleRecipe extends Component {
constructor(){
super();
this.state = { order: {} };
}
getMeteorData(){
let data = {};
let recipehandle = Meteor.subscribe('singlerecipe',this.props.slug);
if(recipehandle.ready()){
data.recipe = Recipes.findOne({_id:this.props.slug});
}
return data;
}
addToOrder(key){
this.state.order[key] = this.state.order[key]+1 || 1;;
this.setState({
order:this.state.order
});
}
render() {
const {recipe}=this.data;
const {order}=this.state;
return (
<div className="container">
<div className="row">
<div className="col s6">
{recipe.nameOfRecipe}
</div>
<div className="col s6">
<Order order={order} recipe={recipe} removeFromOrder = {this.removeFromOrder} />
</div>
</div>
</div>
);
}
}
export default class Order extends Component {
renderOrder(key){
let order = this.props.order[key];
let recipe = this.props.recipe[key]; // get undefined
let removeButton = <button onClick={this.props.removeFromOrder.bind(this,key)}>×</button>;
if(!recipe){
return <li key={key}>Sorry, recipe no longer available! {removeButton}</li>
}
return(
<li key={key}>
<span>
<CSSTransitionGroup component="span" transitionName="count" transitionLeaveTimeout={250} transitionEnterTimeout={250} className="count">
<span key={count}>{count}</span>
</CSSTransitionGroup>
{recipe.nameOfRecipe}{removeButton}
</span>
</li>
)
}
render() {
let orderId = Object.keys(this.props.order); // returns the id
return (
<div className="order-wrap">
<h2 className="order-title">Your Order</h2>
<CSSTransitionGroup
className="order"
component="ul"
transitionName="order"
transitionEnterTimeout={100}
transitionLeaveTimeout={500}
>
{orderId.map(item=>this.renderOrder(item))}
</CSSTransitionGroup>
</div>
);
}
}
{orderId.map(this.renderOrder)}
change to
{orderId.map(item=>this.renderOrder(item))}
changing this.props.recipe[key] to this.props.recipewill work.