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.
Related
I have a basic app which simply returns a number of cards with some content inside, I have some buttons which then filter the returned cards by a value found in the dataset. The filter buttons do work indididually but if I click one after the other the filter is being applied to the now filtered data. How can I make sure the filter is being applied to the initial state of the data or how can I reset the state to everything before the filter is applied? Thanks.
parent.js
import './App.scss';
import DataThoughts from "./assets/data/DataThoughts";
import Thoughts from "./components/Thoughts";
function AppThoughts() {
return (
<div className="App">
<main className={'bg-light'}>
<div className={'container'}>
<Thoughts data={DataThoughts} />
</div>
</main>
</div>
);
}
export default AppThoughts;
Thoughts.js
import React, { Component } from 'react';
import FilterButton from "./FilterButton";
class Thoughts extends Component {
constructor(props) {
super(props);
this.state = {...props};
}
handleClick = value => () => {
let filtered = this.state.data.filter(item => item.Emotion === value);
this.setState({ data: filtered });
console.log(this.state.data);
};
render() {
let data = this.state.data;
let numberOfThoughts = data.length;
let dataList = this.state.data.map((thought, i) =>
<div className={`col-12 col-sm-12 col-md-6 ${i % 2 === 0 ? i % 3 === 0 ? 'col-lg-3 col-xl-3' : 'col-lg-5 col-xl-5' : 'col-lg-4 col-xl-4'}`} key={'thought'+i}>
<div className="card mb-4">
{thought.Photo ? <img src={thought.Photo} className="card-img-top" alt={thought.Emotion}/> : ''}
<div className="p-5">
<blockquote className="blockquote mb-0">
<p className={'small text-muted'}>{thought.Date}</p>
<p className={`${i % 2 === 0 ? i % 3 === 0 ? 'display-6' : 'display-4' : 'display-5'} mb-4`}>{thought.Thought}</p>
<footer className="small text-muted">{thought.Author}, <cite title="Source Title">{thought.Location}</cite></footer>
</blockquote>
</div>
</div>
</div>
);
return (
<section className="row section-row justify-content-start thoughts">
<div className={`col-12`} key={'filters'}>
<FilterButton buttonText={"Happy"} onClick={this.handleClick('Happy')} />
<FilterButton buttonText={"Sad"} onClick={this.handleClick('Sad')} />
<FilterButton buttonText={"Thinking"} onClick={this.handleClick('Thinking')} />
<FilterButton buttonText={"All"} onClick={this.handleClick('All')} />
</div>
{dataList}
<div className={`col-12 col-sm-12 col-md-6 col-lg-2 col-xl-2 d-flex align-items-center justify-content-center`} key={'total'}>
<span className={'display-1'}>{numberOfThoughts}</span>
</div>
</section>
);
}
}
Thoughts.defaultProps = {
Photo: '',
Emotion:'Happy',
Date:'Morning',
Thought:'Default',
Author:'Me',
Location:'Somewhere'
};
export default Thoughts; // Don’t forget to use export default!
FilterButtons.js
import React, { Component } from 'react';
class FilterButton extends Component {
render() {
return (
<button className={'btn btn-primary d-inline-block mb-4'} onClick={this.props.onClick}>{this.props.buttonText}</button>
);
}
}
export default FilterButton; // Don’t forget to use export default!
It looks like the initial data comes from the props. You can access the props with this.props. So you can do something like this:
handleClick = value => () => {
// filter the initial data
let filtered = this.props.data.filter(item => item.Emotion === value);
this.setState({ data: filtered });
// set to initial data
// this.setState({ ...this.props });
console.log(this.state.data);
};
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>
)}
}
I'm new at React and I created a project where I'm using two functions which are defined inside the main component. I was wondering how I could change the functions into components.
Those are the two functions I'm trying to change into components:
function AnswerFrame(props) {
var selectedNumbers = props.selectedNumbers.map(function(i) {
return <span onClick={props.unselectNumber.bind(null, i)}>{i}</span>;
});
return (
<div id="answer-frame">
<div className="well">{selectedNumbers}</div>
</div>
);
}
function NumbersFrame(props) {
var selectNumber = props.selectNumber,
usedNumbers = props.usedNumbers,
selectedNumbers = props.selectedNumbers,
numbers = [],
className = null;
for (var i = 1; i <= 9; i++) {
className = "number selected-" + (selectedNumbers.indexOf(i) >= 0);
className += " used-" + (usedNumbers.indexOf(i) >= 0);
numbers.push(
<div className={className} onClick={selectNumber.bind(null, i)}>
{i}
</div>
);
}
return (
<div id="numbers-frame">
<div className="well">{numbers}</div>
</div>
);
}
I've changed the both to components following the structure of one of the answers
import React, { Component } from 'react'; import '../App.css';
export default class NumbersFrame extends Component{
constructor(props) {
super(props);
}
render(){
var selectNumber = this.props.selectNumber,
usedNumbers = this.props.usedNumbers,
selectedNumbers = this.props.selectedNumbers,
numbers = [],
className = null;
for (var i=1; i <=9; i++){
className = "number selected-" + (selectedNumbers.indexOf(i)>=0);
className += " used-" + (usedNumbers.indexOf(i)>=0);
numbers.push(
<div className={className} onClick={selectNumber.bind(null, i)}>
{i}
</div>
)
}
return(
<div id="numbers-frame">
<div className="well">
{numbers}
</div>
</div>
);
}
}
class AnswerFrame extends React {
constructor(props) {
super(props);
}
render() {
var selectedNumbers = this.props.selectedNumbers.map(function(i){
return(
<span onClick={this.props.unselectNumber.bind(null, i)}>
{i}
</span>
)
});
return(
<div id="answer-frame">
<div className="well">
{selectedNumbers}
</div>
</div>
);
}
After using both Components I'm getting the following error in the AnwserFrame component:
TypeError: Cannot read property 'props' of undefined
in the line
<span onClick={this.props.unselectNumber.bind(null, i)}>
I think it is because before I was using the function with the props as a parameter and now when I change to a component, the props are undefined.
Any idea about how I could handle that error before with the functions it was working well.
Both are Components in themselves, called functional components.
If you need to convert it into class components here is a sample for one,
class AnswerFrame extends React.Component{
render(){
var selectedNumbers = this.props.selectedNumbers.map(function(i){
return(
<span onClick={this.props.unselectNumber.bind(null, i)}>
{i}
</span>
)
});
return(
<div id="answer-frame">
<div className="well">
{selectedNumbers}
</div>
</div>
);
}
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.
I have an object with the property home.ready = false. When the object is done getting data, cleaning it etc it changes to home.ready= true.
I need my component to register the change and update. My component:
class HomeNav extends React.Component {
render() {
let data = this.props.data;
let uniqueTabs = _.uniq(_.map(data, x => x.tab)).sort();
let tabs = uniqueTabs.map((tab, index) => {
let itemsByTab = _.filter(data, (x => x.tab == tab));
return <Tabs key={tab} tab={tab} index={index} data={itemsByTab} />;
});
console.log(this.props)
return (
<section>
<div className="wb-tabs">
<div className="tabpanels">
{ this.props.ready ? {tabs} : <p>Loading...</p> }
</div>
</div>
</section>
)
}
};
ReactDOM.render(
<HomeNav data={home.data.nav} ready={home.ready}/>,
document.getElementById('home-nav')
);
This is the home object. It's a simple object that gets data and once the data is ready the property ready changes from false to true. I can't get React to recognize that change. And at times React will say home is undefined.
Since you didn't post any code around the request, or data formatting, I will assume you got all that figured out. So, for your component to work the way it is currently written, you need to drop the curly braces around tabs ({ this.props.ready ? tabs : <p>Loading...</p> }), then, this.props.data should always contain a valid Array, otherwise it will break when you try to sort, filter, etc.
Or, you can do an early dropout, based on the ready property:
class HomeNav extends React.Component {
render() {
if(!this.props.ready){
return <section>
<div className="wb-tabs">
<div className="tabpanels">
<p>Loading...</p>
</div>
</div>
</section>
}
let data = this.props.data;
let uniqueTabs = _.uniq(_.map(data, x => x.tab)).sort();
let tabs = uniqueTabs.map((tab, index) => {
let itemsByTab = _.filter(data, (x => x.tab == tab));
return <Tabs key={tab} tab={tab} index={index} data={itemsByTab} />;
});
console.log(this.props)
return (
<section>
<div className="wb-tabs">
<div className="tabpanels">
{tabs}
</div>
</div>
</section>
)
}
};