I'm rendering a list of child components which contain a checkbox, and when that checkbox is clicked, I want to move that child component inside another div element.
Here's an image of what my app looks nice. I'd like to check the student names and move them up, under the "Present" sub-heading..
let ClassComp = (props) => {
const { teacher, subject, students, day } = props.classOf
const renderStudents = (students) => {
if (students && students.length > 0) {
return (
<div>
{students.map((student, index) =>
<StudentCheckbox key={index} student={student} handleCheckboxClick={handleCheckboxClick} />
)}
</div>
)
} else {
return <p style={{ margin: '10px' }} >No students registered.</p>
}
}
const handleCheckboxClick = (elId) => {
const presentStudentEl = document.getElementById('present-students')
// move StudentCheckbox element inside this element ^
}
return (
<div className="ui segment" style={segmentStyle} >
<div className="content">
<div className="ui medium header">{teacher} - {subject}</div>
<div className="ui divider"></div>
<div className="ui sub header">Students</div>
<div className="ui tiny header">Present:
<div id="present-students"></div>
</div>
<div className="ui tiny header">Absent:
<div id="absent-students">
{renderStudents(students)}
</div>
</div>
<div style={{ marginBottom: '30px' }}>
<button className="mini compact ui negative right floated button"
onClick={() => setModalVisible(true)}>Delete Class
</button>
<Link to={`/todaysclass/edit/${props.classId}`} className="mini compact ui right floated button">Edit Class</Link>
</div>
</div>
</div >
)
}
const mapStateToProps = (state, ownProps) => {
return { classOf: state.classes[ownProps.classId] }
}
export default connect(mapStateToProps, { deleteClass })(ClassComp)
and here's my child component:
const StudentCheckbox = (props) => {
const uniqId = idGenerator()
return (
<div className="field" style={{ margin: '5px' }}>
<div className="ui checkbox">
<input type="checkbox" id={uniqId} onChange={() => props.handleCheckboxClick(uniqId)} />
<label htmlFor={uniqId}>{props.student}</label>
</div>
</div>
)
}
In this case, you'll need a state for your component. Take a look in the docs:
https://reactjs.org/docs/state-and-lifecycle.html
So basically, besides props (which are "fixed"), you'll have a state, which will change when you check the items.
Your render method will use the state to place the items either in one div, or the other. So all you have to do is use setState to change the state and the render method will redraw the new one.
You're using redux to manage state. That's good. It helps properly manage/manipulate data.
In this case you're trying to decorate a view without data changes [in redux store] - that's not good, it doesn't even make sense in react.
Rendered components/view is only a [derived] View from a Model(state) - in MVC. Moving some element from one div to another in DOM (if you implement this) doesn't change the [base] state - after rerendering you'll loose these kind of changes.
UPDATE:
You should keep students' presence in the store (default false). You need a separate action (with params/payload: classId, studentId), call API (in action creator to save attendence) and reducer for change 'flag' in redux store.
Each student will have isPresent property. You can simply change your renderStudents to render both divs (additional boolean argument and apriopriate filtering at the beginning).
Related
I'm building a kind of shopping cart in React. I set some props in the child component and then passed state into them when they are used in the parent component. There are several things that I want to do, but to begin I want to update the quantity of the item when the buttons are pressed. I know how this would work if it were all coded in the parent component, but I'm trying to create a child component in order to make the code more efficient and to figure out how components interact with each other.
Can the props of the child be manipulated with onClick events here, and be used to change the state of different things?
If so, how do I do that?
Is there a logical way of creating this, or is there a much simpler way?
I know that Redux uses state management, but I want to figure this out in React for now.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
applesCount: 0,
orangesCount: 0,
peachesCount: 0,
grapesCount: 0,
applesPrice: 0.00,
orangesPrice: 0.00,
peachesPrice: 0.00,
grapesPrice: 0.00
}
}
render() {
return (
<>
<h1 className="text-center">Grocery Store</h1>
<div className="row text-center m-2">
<div className="col border border-primary m-2">
<Item fruitName='Apples' quantity={this.state.applesCount} price={this.state.applesPrice}/>
</div>
<div className="col border border-primary m-2">
<Item fruitName='Oranges' quantity={this.state.orangesCount} price={this.state.orangesPrice}/>
</div>
<div className="col border border-primary m-2">
<Item fruitName='Peaches' quantity={this.state.peachesCount} price={this.state.peachesPrice}/>
</div>
<div className="col border border-primary m-2">
<Item fruitName='Grapes' quantity={this.state.grapesCount} price={this.state.grapesPrice}/>
</div>
</div>
</>
)
}
}
class Item extends React.Component {
render() {
return (
<>
<h1>{this.props.fruitName}</h1>
<div>picture here</div>
<div className="row d-flex justify-content-center">
<button>-</button>
<h1>{this.props.quantity}</h1>
<button>+</button>
</div>
<h2>${this.props.price}</h2>
</>
)
}
}
export default App;
Pass down a callback to the child.
const Parent = () => {
const [value, setValue] = useState('')
return <Child setValue={setValue}/>
}
...
const Child = ({setValue}) => {
// use setValue here which will update parent state
}
you can do many type of solution for this
passing method wich that do setState to child and call it in there then you can update the state of parent wich gets from props.
const Parent = () => {
const [value, setValue] = useState('')
return ();
}
const Child = ({setValue}) => {}
do it with redux or another state management libraries .
Currently I've got a react component that looks like this:
const GeraCard = (cards, cart = false) => {
return cards.map((v, i) => {
return (
<div key={i} className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div>
);
});
};
Based on the cards array, it renders something like this:
Rendered Component
Whenever I click the trash button, I make a request to my backend, edit the list on my database and rerender the component based on the now updated "cards". The problem is that this takes sometime to happen, so i wanted a way to remove it from the dom instantly while my backend does it's job.
somehting like
{show ? renderCompoennt : null}
I've tried using vanilla javascript to grab the parent from the trash can, which would be the card i want to remove, but the results are unpredictable and it's quite slow as well.
My latest try was this:
const GeraCard = (cards, cart = false) => {
return cards.map((v, i) => {
const [show, setShow] = useState(true);
return (
<div key={i}>
{show ?
<div className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
setShow(false);
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div> :
null
}
</div>
);
});
};
but react won't let me do this. Even tho its fast, everytime one item gets deleted, react complains that "less hooks were rendered" and crashes the app.
You are attempting to do some Optimistic UI, in which you assume that your action will succeed, and reflect the expected/assumed state instantly, before the request to the backend completes. This would be in lieu of showing some progress/busy indicator, like a spinner, until the action completes with the server.
The first problem and immediate problem in your code-- it violates the rules of hooks, which state that hooks may only be used at the top-level (never inside loops, conditionals, etc).
The second problem is that you are leveraging vanilla JS to manipulate the DOM directly; this generally an antipattern in MV* frameworks, and very much so here. Instead, I would suggest doing managing it in your data model; something like this:
Rewrite your .map handler to return null if the card has a deleted property.
When the user clicks the trash button, do two things:
Make the request to the backend to delete it
Use a setState to add a deleted: true property to the clicked card
Now you will get a rerender that will omit the deleted card, and also make the request to the backend, all inside the React data model. Make sure that you handle complexity for:
How to handle the response
How to handle an error if the deletion fails at the backend
How to manage if a user quickly clicks many cards for deletion before any of the requests can complete.
The problem is that in the first render you have {cards.length} calls to hook "useState" within GeraCard, but after deletion of one card, you will have {cards.length-1} calls to hook "useState". As the React docs state:
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function. By
following this rule, you ensure that Hooks are called in the same
order each time a component renders. That’s what allows React to
correctly preserve the state of Hooks between multiple useState and
useEffect calls.
You should extract the content of map callback into separate a component.
const GeraCards = (cards, cart = false) => {
return cards.map((v, i) =>
<GeraCard card={v} index={i} cart={cart} />
);
};
const GeraCard = ({ card, index, cart }) => {
const [show, setShow] = useState(true);
const v = card;
return (
<div key={index}>
{show ?
<div className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
setShow(false);
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div> :
null
}
</div>
);
}
I am using the npm library react-flippy for a flash cards Q/A app. I have an array of Q/A objects as a reducer and a random number as component state. When the user clicks the Next button, a random number is generated which determines the next Q/A object to populate the front and back of the card.
The issue I am having is if the BackSide (answer) of the card is visible when the Next button is clicked, the card does not revert back to FrontSide (question) for the next Q/A information, it stays on the BackSide revealing the answer to the question.
I have looked through the react-flippy module to try and find the variable/state that determines if the card is flipped or not (I am assuming its a boolean called isFlipped). I need to reset this variable back to false in the randomQuestion function thats called by the Next button. How do I find the variable/state doing this in the flippy-react module and how do I access it outside of the Flippy element?
The Github for react-flippy: https://github.com/sbayd/react-flippy
My code:
import React from 'react';
import { connect } from 'react-redux';
import '../styles/style.css';
import Flippy, { FrontSide, BackSide } from 'react-flippy';
class MenuCards extends React.Component{
state = {
randomNumber: 0
}
randomQuestion = (arry=[]) => {
var num = Math.floor(Math.random() * arry.length);
this.setState({randomNumber: num});
//-- Need to reset isFlipped back to false here...
}
render() {
return(
<div className='ui container'>
<div style={{ margin: '2em' }} className='ui one column stackable center aligned page grid'>
<div style={{ margin: '2em' }} className='ui one column stackable center aligned page grid'>
<h1 className='ui header'>Food Menu Flash Cards</h1>
<div className='column twelve wide'>
<Flippy flipOnHover={false} flipOnClick={true} flipDirection='horizontal' ref={(r) => this.flippy = r}>
<FrontSide style={{ backgroundColor: 'maroon' }} id='question'>
<h2 id='text' className='ui header'>Question</h2>
<p id='text'>{ this.props.questionAnswer[this.state.randomNumber].question }</p>
</FrontSide>
<BackSide style={{ backgroundColor: 'maroon' }} id='answer'>
<h2 id='text' className='ui header'>Answer</h2>
<p id='text'>{ this.props.questionAnswer[this.state.randomNumber].answer }</p>
</BackSide>
</Flippy>
<button id='next-button' className='fluid ui button' onClick={() => this.randomQuestion(this.props.questionAnswer)}>Next</button>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
questionAnswer: state.questionAnswer
};
}
export default connect(mapStateToProps)(MenuCards);
This isFlipped is a prop your Flippy accepts, meaning you can set the card to be flipped or not from outside the component (but, according to the official documentation, your flipOnHover and ’flipOnClick’ wouldn't work). It doesn't have its internal state.
Read some more about React state and props - these are the fundamentals.
I am trying to create an accordion component using React, but the animation is not working.
The basic idea is, I believe, pretty standard, I am giving each item body a max-height of 0 which is affected by adding a show class to an element. I am able to select and show the item I want, but the animation to slide in/out is not working.
With the Chrome dev tools open, when I click on one of the items I can see that the whole "accordion" element is flashing, which leads me to believe that the whole element is being re-rendered. But I am unsure why this would be the case.
Here is the relevant Accordion component:
import React, { useState } from "react";
const Accordion = ({ items }) => {
const [selectedItem, setSelectedItem] = useState(0);
const AccordionItem = ({ item, index }) => {
const isOpen = index === selectedItem;
return (
<div className="accordion-item">
<div
onClick={() => {
setSelectedItem(index);
}}
className="accordion-header"
>
<div>{item.heading}</div>
</div>
<div className={`accordion-body ${isOpen ? "show" : ""}`}>
<div className="accordion-content">{item.body}</div>
</div>
</div>
);
};
return (
<div className="accordion">
{items.map((item, i) => {
return <AccordionItem key={i} item={item} index={i} />;
})}
</div>
);
};
export default Accordion;
And here is a codepen illustrating the problem:
https://codesandbox.io/s/heuristic-heyrovsky-xgcbe
of course, its going to re-render. When ever you call setSelectedIem, state changes and hence react re-renders on state change to exhibit that change.
Now if you place this
const [selectedItem, setSelectedItem] = useState(0);
inside Accordion Item, it would just re-render accordion item, but would mess up your functionality.
Hi I'm a beginner in ReactJS and I'm making a memory game with it and have problems with revealing tiles.
I wanted to have a few values in parent component's state, like firstPick, secondPick, pickCount and also isHidden (this toggles icon visibility).
But putting it in parent component makes click on any button toggle icon on every tile. To solve this, I moved isHidden property to Button (child) component. Now i wanted to manipulate state of Button from its parent component, so I found information about using refs somewhere in the internet. But using them makes clicking every button only toggle the last button in the container. I suppose i messed up something.
So my Button components looks like this:
class Button extends Component {
state = {isHidden: true};
toggleHidden () {
this.setState({isHidden: !this.state.isHidden});
}
render() {
return (
<div>
<button className="tile" onClick={this.props.onClick}>
<i
className={this.props.icon}
style={
this.state.isHidden ? { display: "none" } : { display: "block" }
}
/>
</button>
</div>
);
}
}
and my Board components looks like this:
class Board extends Component {
state = { firstPick: "", secondPick: "", pickCount: 0 };
constructor(props) {
super(props);
this.buttonElement = React.createRef();
}
handleClick = () => {
this.buttonElement.current.toggleHidden();
this.setState({
firstPick: this.icon,
pickCount: this.state.pickCount + 1
});
alert(this.state.firstPick);
};
render() {
return (
<div className="board">
<div className="container">
<div className="row align-items-start">
<div className="col-3">
<Button
ref={this.buttonElement}
icon={animalArray[0]}
onClick={this.handleClick}
/>
Of course there are 11 other Button components here, but they look just the same. (Btw is there a way to not repeat this code? They are placed in bootstrap container).
I also have 1 bonus question. Is there a way to set the state.firstPick to the icon property? I don't know how to refer to this value from the Parent component method. I tried writing this.icon, but i don't think it works. I wanted to keep two choices in state variables and then compare them. Or is there maybe a better way to solve this problem? Thanks
Why not just handle the toggle in the button itself?
class Button extends Component {
state = {isHidden: true};
toggleHidden () {
this.setState({isHidden: !this.state.isHidden});
}
onClick = () => {
this.toggleHidden();
if (this.props.onClick) {
this.props.onClick();
}
}
render() {
return (
<div>
<button className="tile" onClick={this.onClick}>
<i
className={this.props.icon}
style={
this.state.isHidden ? { display: "none" } : { display: "block" }
}
/>
</button>
</div>
);
}
}
Update: To preserve unique reference for firstPick, you can wrap your handleClick in another function:
handleClick = (icon) => () => {
this.buttonElement.current.toggleHidden();
this.setState({
firstPick: icon,
pickCount: this.state.pickCount + 1
});
alert(this.state.firstPick);
};
render() {
return (
<div className="board">
<div className="container">
<div className="row align-items-start">
<div className="col-3">
<Button
ref={this.buttonElement}
icon={animalArray[0]}
onClick={this.handleClick(animalArray[0])}
/>
handleClick now becomes a function that returns your original function (with some extra/unique context, in this case animalArray[0]).
I would say the complexity of your project is enough to warrant a store like flux or redux to keep track of all your button properties. This is generally the solution to complex relationships between parent and child elements.
In my opinion, refs should only be used for uncontrolled components, that is, any elements on your page that are not in react.