Conditional rendering a button value - javascript

I'm creating a game in which the initial state of the cards is face down, upon clicking a single card, the card's icon is revealed. When two cards match, both cards are removed from the board.
The challenge I'm having is in my conditional rendering. When I click on one card, all cards reveal their icon, not just one per click. Here is the codesandbox
I'm trying to keep state as local as possible the the relevant components and also trying the do it the "React" way by not toggling classnames and directly manipulating the DOM. How can I fix this and is the btnMask class the right way to go? If I can get this, then matching id's on cards may be a quick implementation. Here is the Card class
class Card extends Component {
constructor(props) {
super(props);
this.state = {
hidden: true
};
this.revealIcon = this.revealIcon.bind(this);
}
revealIcon(e) {
console.log("clicked", e.target.id);
this.setState({ hidden: false });
}
render() {
const card = this.props.cardsEasy["cards"].map((card, index) => {
if (this.state.hidden) {
return (
<button
key={card + index}
style={btn}
onClick={this.revealIcon}
id={card}
>
<div style={btnMask}>{card}</div>
</button>
);
} else {
return (
<button
key={card + index}
style={btn}
onClick={this.revealIcon}
id={card}
>
<div>{card}</div>
</button>
);
}
});
return <div style={divContainer}>{card}</div>;
}
}
export default Card;

You problem is that you have a common state for all cards. This means you have Card Component in which you render all the cards. The component has the state of hidden and renders a bunch of cards. Those cards access all the same variable in the state to check if they should hide. Therefore if you change that variable, all of then show up. What I would suggest you to do is to create a component "CardCollection" which renders the card.
class CardCollection extends React.Component {
render() {
this.props.cardsEasy["cards"].map((item) => {
<Card {...item} />
})
}
}
Like this each CardComponent will have their own state. You will need to adjust your CardComponent a little bit.
Instead of using a class component / stateful component you could also use a statless component for the CardCollection, as the collection doesn't need any state and is only there to render the childrens.
const CardCollection = (props) => {
return this.props.cardsEasy["cards"].map((item) => {
<Card {...item} />
});
}

Related

Can't Get React Component to Update or Pass Prop

All the code does is to update a counter +1 every time you click in the button, the problem here is when i'm trying to pass the prop counter to text it does not update in the Text component i've been doing some research and look like i have to wake it up with another function, if someone could explain me, i would be really grateful.
import React from 'react';
import ReactDOM from 'react-dom';
class Button extends React.Component {
constructor(props) {
super(props)
this.state = {counter: 1}
}
render() {
return (
<button onClick={() => {
this.setState({
counter: this.state.counter+1
});
//tries passing the prop counter with the state of the counter
<Text counter={this.state.counter} />
}}>
Click here
</button>
)
}
}
class Text extends React.Component {
render() {
return (
// returns 'clicked undefined times'
<h2 id='counter-text'>{'Clicked ' + this.props.counter + ' times'}</h2>
)
}
}
class App extends React.Component {
render() {
return (
<>
<Text/>
<Button/>
</>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'));
Well, the code is syntactically wrong.
You cannot render a Text in the onChange method of a button.
What you wanted was, when the count is updated in the Button, it should reflect in another component, i.e., the Text.
As these two are different components altogether, you have to have a shared state for them.
And for that, you can lift the counter state from Button to a parent component App. Check this out: https://reactjs.org/docs/lifting-state-up.html
This should work:
import React from "react";
import ReactDOM from "react-dom";
class Button extends React.Component {
render() {
// 3. on every click, Button uses App's updater method to update the count
return <button onClick={this.props.handleCounter}>Click here</button>;
}
}
class Text extends React.Component {
render() {
return (
<h2 id="counter-text">{"Clicked " + this.props.counter + " times"}</h2>
);
}
}
// 1. Let App bear the counter state
class App extends React.Component {
constructor(props) {
super(props);
this.state = { counter: 1 };
this.handleCounter = this.handleCounter.bind(this);
}
handleCounter() {
this.setState({
counter: this.state.counter + 1
});
}
// 2. Let the Button and Text components share the App state
render() {
return (
<>
<Text counter={this.state.counter} />
<Button
counter={this.state.counter}
handleCounter={this.handleCounter}
/>
</>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
There's a few mistakes on how your code is written.
I've changed it slightly and put it on a sandbox you can play around with.
What's wrong:
You're trying to render <Text counter={this.state.counter} /> from
within the onClick callback for the button, but this won't ever
render without being inside the return statement of a render
function.
You're using state inside the Button component, but Text component is not a child of this component.
Both Button and Text are children of App, and both of them need
to access this counter state, so App should have the state and pass
it as prop for Text
Button needs a callback to be able to update it's parent (App) state. You can pass a function that does this as a prop to Button and then call this on onClick.

Display a different components in a click of a button (react component class)

I created two dropdowns selection, input, and a button in my Addroom component and I want to display that component in my main page only in a click of the button - it will look like a page with a single button.
After the user will finish fill the details (at Addroom component) and clicking on the Create button I want to display the titles with the values that the user filled - Room component.
In the current situation, everything is just already on the main page..
the Addroom component with elements for fill out.
the Room Component with the titles that filled with the user details.
And this is what I want it to look like:
I hope the explanation is understandable - these are two buttons...
I have tried different methods to display the components separately but none of the methods work properly and just made a lot of mess that I couldn't understand in the code.
I tried things like boolean values and tried to create a button based on the specific condition, and also methods that based on the state value that contact operators with functions to update the state values based on the unique identifiers.
Is there a simple way to do this without ruin the algorithms that work?
I could really use a hand..
Thank you! and particularly thanks for your patience..
App.js
import React, { Component } from 'react'
import 'bootstrap/dist/css/bootstrap.min.css'
import './App.css';
import Addroom from './components/Addroom.js'
import Room from './components/Room.js'
export default class App extends Component {
state={
roomsList:[{room:''}],
roomTypeSelection:[{roomType:''}],
colorTypeSelection:[{colorType:''}],
isActive: false
}
handleShow = () => {
this.setState({isActive: true});
};
handleHide = () => {
this.setState({isActive: false});
};
create=(r)=> {
this.setState({roomsList:[...this.state.roomsList,{room:r}]})
}
getRoomTypeSelection=(rt)=> {
this.setState({roomTypeSelection:[...this.state.roomTypeSelection,{roomType:rt}]})
}
getColorTypeSelection=(ct)=> {
this.setState({colorTypeSelection:[...this.state.colorTypeSelection,{colorType:ct}]})
}
render() {
return (
<div>
<h1>My Smart House</h1>
{this.state.roomsList.map((element)=>{
return <Room r={element.room} rt={element.roomType} ct={element.colorType}/>
})}
<Addroom add={this.create} addRoomType={this.getRoomTypeSelection} addColorType={this.getColorTypeSelection}/>
</div>
)
}
}
Addroom.js
import React, { Component } from 'react'
export default class Addroom extends Component {
constructor(props) {
super(props)
this.state = {
backgroundColor:'white',
room:'',
roomNameInputColor:'white',
roomTypes:["kitchen", "bathroom", "bedroom"],
roomSelected: [''],
roomSel:'',
colorTypes:['red', 'green', 'blue', 'teal'],
colorSelected:[''],
colorSel:'',
};
}
addRoomName = (e) => {
const room = e.target.value;
let roomNameInputColor = 'white';
if (e.target.value.length >= 5) {
roomNameInputColor = 'green';
} else {
roomNameInputColor = 'red';
}
this.setState({ room, addRoomName: room, roomNameInputColor });
}
createRoom=()=> {
this.props.add(this.state.room);
}
createRoomType=()=> {
this.props.addRoomType(this.state.roomSelected);
}
createColorType=()=> {
this.props.addColorType(this.state.colorSelected);
}
setCategory = (roomSel) => {
this.setState({roomSelected : roomSel});
};
setColorCategory = (colorSel) => {
this.setState({colorSelected : colorSel});
};
render() {
return (
<div>
{/* //Select Room Type */}
<select onChange={(e) => this.setCategory(e.target.value)}>{this.state.roomTypes.map((type) =>
<option value={type}>{type}</option>
)}</select><br/>
{/* //Select Room Color */}
<select onChange={(e) => this.setColorCategory(e.target.value)}>{this.state.colorTypes.map((type) =>
<option value={type}>{type}</option>
)}
</select><br/>
<input onChange={this.addRoomName} style={{backgroundColor:this.state.roomNameInputColor}} placeholder='Name Your Room'/><br/>
<button onClick={() => {this.createRoom(); this.createRoomType(); this.createColorType();}}>Create</button>
</div>
)
}
}
Room.js
import React, { Component } from 'react'
export default class Room extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<div>
<h3>Room1: {this.props.r} </h3>
<h3>Room Type1: {this.props.rt}</h3>
<h3>Room Color1: {this.props.ct}</h3>
</div>
)
}
}
If I understand correctly, you only want to show the Addroom dropdowns and input when the user has clicked the Create button once, after which they can add a room. For that you can use a state to toggle what to display.
Ive made a quick sandbox example, please see if that works.
Note: dont forget to add a key when using map(), like this:
<select>
{items.map((value, key) => (
<option key={key} value={value}>{value}</div>
))}
</select>
Edit: improved the sandbox code

how to call functional component in class based component using onClick event?

i want to show my functional component in class base component but it is not working. i made simpletable component which is function based and it is showing only table with some values but i want to show it when i clicked on Show user button.
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.userList = this.userList.bind(this);
}
userList = () => {
//console.log('You just clicked a recipe name.');
<SimpleTable/>
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList} >
Show User List
</Button>
</div>
);
}
}
export default ShowUser;
Why your code is not working
SimpleTable has to be rendered, so you need to place it inside the render method. Anything that needs to be rendered inside your component has to be placed there
On Click can just contain SimpleTable, it should be used to change the value of the state variable that controls if or not your component will be shown. How do you expect this to work, you are not rendering the table.
Below is how your code should look like to accomplish what you want :
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.state = { showUserList : false }
this.userList = this.userList.bind(this);
}
showUserList = () => {
this.setState({ showUserList : true });
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.showUserList} >
Show User List
</Button>
{this.state.showUserList ? <SimpleTable/> : null}
</div>
);
}
}
export default ShowUser;
You can also add a hideUserList method for some other click.
Or even better a toggleUserList
this.setState({ showUserList : !this.state.showUserList});
If you're referring to the method userList then it appears that you're assuming there is an implicit return value. Because you're using curly braces you need to explicitly return from the function meaning:
const explicitReturn = () => { 134 };
explicitReturn(); <-- returns undefined
const implicitReturn = () => (134);
implicitReturn(); <-- returns 134
The problem lies with how you are trying to display the SimpleTable component. You are using it inside the userList function, but this is incorrect. Only use React elements inside the render method.
What you can do instead is use a state, to toggle the display of the component. Like this:
const SimpleTable = () => (
<p>SimpleTable</p>
);
class ShowUser extends React.Component {
constructor(props) {
super(props);
this.state = {showSimpleTable: false};
this.toggle= this.toggle.bind(this);
}
toggle = () => {
this.setState(prev => ({showSimpleTable: !prev.showSimpleTable}));
}
render() {
return (
<div>
<button variant = "contained" color = "primary" onClick={this.toggle}>
Show User List
</button>
{this.state.showSimpleTable && <SimpleTable />}
</div>
);
}
}
ReactDOM.render(<ShowUser />, document.getElementById("app"));
<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>
<div id="app"></div>
The functionality you are looking for is called Conditional Rendering. The onClick prop function is an event handler and events in react may be used to change the state of a component. That state then may be used to render the components. In normal vanilla javascript or jQuery we call a function and modify the actual DOM to manipulate the UI. But React works with a virtual DOM. You can achieve the functionality you are looking for as follows:
class ShowUser extends Component {
constructor(props) {
super(props)
// This state will control whether the simple table renders or not
this.state = {
showTable: false
}
this.userList.bind(this)
}
// Now when this function is called it will set the state showTable to true
// Setting the state in react re-renders the component (calls the render method again)
userList() {
this.setState({ showTable: true })
}
render() {
const { showTable } = this.state
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList}>
Show User List
</Button>
{/* if showTable is true then <SimpleTable /> is rendered if falls nothing is rendered */}
{showTable && <SimpleTable />}
</div>
)
}
}

ReactJS component reloading unexpectedly on className change

TLDR;
Does adding a className to an exciting div reload the parent component in ReactJs? Added images near button to show console.log being called multiple times.
Here is the bug..
I'm building a simple 'order' app, which includes a sidebar.
I recreated the sidebar in a new project for a sanity check. Same issues. Here is the simple version.
class App extends Component {
constructor() {
super();
this.state = {
addList : [],
}
}
render() {
return (
<div className="App">
<Sidebar list = {this.state.addList}/>
</div>
);
}
}
export default App;
and in the sidebar component
class Sidebar extends Component {
constructor(props){
super(props);
this.state = {
active : false
}
}
toggleSidebar () {
if (this.state.active) {
this.setState({
active : false
})
} else {
this.setState({
active: true
})
}
}
render () {
return (
<div className={ 'sidebar ' + ((this.state.active) ? '' : 'hidden')}>
<button className='tab' onClick={(e)=>{this.toggleSidebar()}}>
TAB
</button>
<div className="itemList">
{console.log(this.props.list)}
</div>
</div>
)
}
}
export default Sidebar;
The sideBar class has a position: fixed and I move it out of the screen on a button click and adding a hidden className (.hidden { right: -x })
When an item gets selected in the parent app component, it gets added to its state (addItem).
The Sidebar component has that property passed into so when addItem get a new item, it displays it. It works just as predicted. I was able to add items and display them no problem.
I noticed the bug when I started adding number and doing price totals etc, because it seems the sidebar keep rendering I would find myself getting caught in infinite setState loops
Any solutions or advice?
Images for a those that are visual (clicking the tab and console displaying):
The answer is simple, I'm foolish. In fact I AM changing the state of sidebar causes a reload.
Therefor to get around this is having the parent component hold the values and pass them down as properties for the sidebar to just display. Therefor on reload the values don't change or re-add.

React - toggle display of one component from the onClick of another component

I'm trying to build my first React project, and am currently putting together a burger nav button, and a menu which appears when clicking the nav.
I've broken this into two components; Hamburger and MenuOverlay. The code for both is below.
Currently I have an onClick on Hamburger toggling a class on it, but how would I also toggle the menu from that click? It's hidden with display: none; by default. Probably a very basic question so apologies - still trying to get my head around React.
MenuOverlay
import React from 'react';
import { Link } from 'react-router';
const MenuOverlay = () => {
return (
<div className="menuOverlay">
<div className="innerMenu">
<p><Link to="/">Home</Link></p>
<p><Link to="/">About</Link></p>
<p><Link to="/">Contact</Link></p>
</div>
</div>
);
};
export default MenuOverlay;
Hamburger
import React, { Component } from 'react';
class Hamburger extends Component {
constructor(props) {
super(props);
this.state = { active: '' };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
var toggle = this.state.active === 'is-active' ? '' : 'is-active';
this.setState({active: toggle});
}
render() {
return (
<button className={`hamburger hamburger--emphatic fadein one ${this.state.active}`} onClick={this.handleClick} type="button">
<span className="homeMenuTextButton">Menu</span>
<span className="hamburger-box">
<span className="hamburger-inner"></span>
</span>
</button>
);
}
}
export default Hamburger;
In the most simplistic form you would have a container component that wraps around both of them and manages the state of the components.
<MenuContainer>
<Hamburger />
<MenuOverlay />
</MenuContainer>
And in <MenuContainer> you would have a state of active some quick pseudocode.
class MenuContainer extends React.Component {
constructor() {
super();
this.state = { active: false}
}
toggleMenu = () => {
// function that will toggle active/false
this.setState((prevState) => {
active: !prevState.active
});
}
render() {
return (
<div>
<Hamburger active={this.state.active} onClick={this.toggleMenu} />
<MenuOverlay active={this.state.active} />
</div>
)
}
}
so in hamburger you would just use the this.props.onClick to change the state of active and then in those corresponding components use the prop of this.props.active to determine what classes should be applied, etc.
Given that one element is not the parent of another element, you will have to pull up the variable keeping the toggle information up the chain of elements until it resides in one common place.
That is, keep the "active" state variable in an ancestor of the two elements and provide to the Hamburger a callback in the props that, when called, modifies the state of that ancestor component. At the same time, also pass the active state variable down to the MenuOverlay as a prop, and everything should work together.
See here for more information:
https://facebook.github.io/react/tutorial/tutorial.html#lifting-state-up
Specifically,
When you want to aggregate data from multiple children or to have two child components communicate with each other, move the state upwards so that it lives in the parent component. The parent can then pass the state back down to the children via props, so that the child components are always in sync with each other and with the parent.

Categories