I'm trying to make a simple color memory game in React (find where there are two identical images).
When I click on one card all of the other cards re-render. How can I prevent this?
//app class
handleClick = index => {
this.setState((prevState) => {
var temp = [...prevState.clickedPicture, index]
return{clickedPicture: temp}
})
}
isClicked = (index) => this.state.clickedPicture.indexOf(index) === -1
render() {
return(
<div className="content">
<div className="header">
<h1>Memory</h1>
</div>
<div className="main">
_.shuffle(this.state.colors).map((current,index) =>
<Game
key={index}
index={index}
current={current}
status={this.state.status}
handleClick={this.handleClick}
bool={this.isClicked(index)}
/>)
</div>
}
// Game component
class Game extends Component {
clickHandle = () => {
if(this.props.bool){
this.props.handleClick(this.props.index)
}
}
render() {
return(
<div className={this.props.status}
style={{ backgroundColor: this.props.bool ?
'black' : this.props.current }}
onClick={this.clickHandle}>
</div>
);
}
}
Make Cards their own component so they have access to lifecycle hooks. then
make the Cards a PureComponents, therefore they will only update if the reference to a prop changes.
Related
I have some code:
//Top row holding one card
const Top = (props) => {
return (
<div>
<div className='row'>
<Card />
</div>
</div>
)
}
export default top;
//Bottom row of cards
const Heroes = (props) => {
return (
<div>
<p>Select a hero</p>
<div className="row">
<Card cardSize={'little-card'} hero={'Spiderman'}/>
<Card cardSize={'little-card'} hero={'Batman'}/>
<Card cardSize={'little-card'} hero={'Superman'}/>
</div>
</div>
)
}
export default Heroes;
Each card component contains a superhero. The hero prop sets the image and also serves as an identifier. There is an empty space above.
When you click one, it should move from its place to the container above, like so:
Then it when clicked again while at the top, it should move back to its original place:
Can someone dumb this down for me or show me how to do this? Code examples would be greatly appreciated.
You can add useState for checking which card gets selected, and then pass that state handling selectCard to Card component
const Heroes = (props) => {
const [selectedCard, setSelectedCard] = useState()
const selectCard = (heroName) => { setSelectedCard(heroName) }
return (
<div>
<p>Selected hero: {selectedCard}</p>
<p>Select a hero</p>
<div className="row">
<Card cardSize={'little-card'} hero={'Spiderman'} selectCard={selectCard}/>
<Card cardSize={'little-card'} hero={'Batman'} selectCard={selectCard}/>
<Card cardSize={'little-card'} hero={'Superman'} selectCard={selectCard}/>
</div>
</div>
)
}
export default Heroes;
After that, modifying your Card component like below
const Card = (props) => {
return <div onClick={() => { props.selectCard(props.hero) }}>{props.hero}</div>
}
I am quite new in react and just struggling a bit to make it work properly.
So here is a snippet from JS file and I need to convert it react.
let squares = document.getElementsByClassName("square");
for (let i = 0; i < squares.length; i++) {
squares[i].addEventListener("mouseenter", () => {
squares[i].classList.add("light");
setTimeout(function () {
squares[i].classList.remove("light");
}, 800);
});
}
In that react component I have just some divs with className="square"
export default function SomeComponent() {
return (
<div className="row ">
<div className="square"></div>
<div className="square"></div>
<div className="square"></div>
<div className="square"></div>
<div className="square"></div>
<div className="square"></div>
<div className="square"></div>
<div className="square"></div>
<div className="square"></div>
<div className="square"></div>
</div>
);
}
Create a component for each square.
Return a JSX div from it.
Create a state in that component for light (default it to false).
If light is true, add the class to the list of class names for the div (the classnames module is helpful here).
Add a onMouseOver function which sets the state of light to true.
Add a useEffect hook which depends on the value of light. Inside that hook, use setTimeout to change the state back to false after the time period.
holo, may you can check this?
and this is online demo
constructor(props) {
super(props);
this.state = {
name: 'React',
currentIndx: undefined,
doms: Array.from({length: 10}).fill(1)
};
}
handleMouse = (index: number) => {
this.setState({
currentIndx: index
});
setTimeout(() => {
this.setState({
currentIndx: undefined
})
}, 1000)
}
render() {
const { doms, currentIndx } = this.state;
return (
<div>
<Hello name={this.state.name} />
<p>
Start editing to see some magic happen :)
</p>
<div>
{doms.map((item, index) => (<div onMouseEnter={() => this.handleMouse(index)} className={`square ${currentIndx === index ? 'light': ''}`} key={index}>{index}</div>))}
</div>
</div>
);
}
The easiest way to do it would be something like this
import React, { useState } from "react";
const ListItem = () => {
const [hovered, setHovered] = useState(false);
return (
<div
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
className={`square ${hovered ? 'light' : ''}`}
/>
);
}
const List = () => {
return (
<div className="row">
<ListItem />
<ListItem />
<ListItem />
<ListItem />
<ListItem />
<ListItem />
<ListItem />
<ListItem />
<ListItem />
<ListItem />
</div>
);
}
Create a Square component first;
import React, { useState } from "react";
import "./Square.css";
export const Square = () => {
const [light, setLight] = useState(false);
const mouseOver = () => {
setLight(!light);
};
return (
<div onMouseOver={mouseOver} className={light ? "light" : null}>
Square
</div>
);
};
export default Square;
Then on your page; import it and use it
import React from 'react'
import './App.css'
import Square from './components/Square'
function App() {
return (
<div className="container">
{Array(3)
.fill(' ')
.map((item) => {
return (
<tr>
<td className="space">
<Square />
</td>
<td className="space">
<Square />
</td>
<td className="space">
<Square />
</td>
</tr>
)
})}
</div>
)
}
export default App
When you mouseOver to the square component it will turn on red, if u do it again, className will be null so it will change back to normal color easy tutorial for you
CSS
.space{
padding: 5px;
}
.light{
color: red
}
I'm trying to see if multiple mapped children components can be passed a function to change some of their props. The deal is that, I have a children rows that are representing shift periods, like this:
Which is actually this child component code:
class ShiftRow extends React.Component {
rawMarkup() {
var md = createRemarkable();
var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
}
handleAvailableVal = () => {
props.onChange(event.target.checked);
}
render() {
return (
<tr className="shift" style={{ backgroundColor: this.props.availability ? 'green' : 'red', color: this.props.availability ? 'white' : 'black' }}>
{React.Children.map(this.props.children, (child, i) => {
// Ignore the first child
return child
})}
</tr>
);
}
}
The idea is, I want to be able to pass down initial values as the shifts are created in the parent ShiftList component, which are mapped out in the component like this:
if (this.props.data && this.props.data.length > 0) {
shiftNodes = this.props.data.map(shift => (
<ShiftRow key={shift.id} newInd={this.props.isNew} availability={shift.Available} ref={this.ShiftElement}>
<td>{shift.Description}</td>
<td style={{textAlign: 'center'}}>
<input
type="checkbox"
checked={shift.Available}
onChange={this.handleAvailableVal}
style={{ width: '1.5em', height: '1.5em'}}
/>
</td>
<td style={{ textAlign: 'center' }}>
<input readOnly
type="checkbox"
checked={shift.AllDay}
style={{ width: '1.5em', height: '1.5em' }}
/>
</td>
{this.determineShiftPeriod(shift)}
<td>{(shift.DayOfWeek && shift.ShiftType != 'Daily') ? shift.DayOfWeek : 'N/A (Daily)'}</td>
</ShiftRow>
));
}
Is there a way I can change the prop in a row by row fashion like this so that I can say, pass this full set of shift represented rows to save to a database? Let me know if I can clarify anything.
Example: I want to be able to click the "Available" checkbox and watch the props value of that row update for THAT row only, and then save the row as such with other rows.
Yes, the setState() function (or an anonymous function that calls it) could be passed down to child components so that they can modify parent state and by so doing, modify their props indirectly. Something like this:
class ParentComponent extends React.Component {
...
updateState = args => this.setState(args);
render() {
return (<ChildComponent key={shift.id} newInd={this.props.isNew} ... updateState={() => this.updateState()}>
</ChildComponent>);
}
}
Maybe you can do this:
<input
type="checkbox"
checked={shift.Available}
onChange={(evt) => this.handleAvailableVal({evt, shift})} // this way you'll have access to both shift and evt
style={{ width: '1.5em', height: '1.5em'}}
/>
Good Luck...
Use something like this to modify the children item's props. Hope this helps
function ANotherComponent(props) {
console.log("props", props);
return <div>Another component</div>;
}
function T(props) {
const newChildren = React.Children.map(props.children, (child, i) => {
// Ignore the first child
return React.cloneElement(
child,
{
newProps1: 1,
newProps2: 2,
},
child.props.children
);
});
return <div>{newChildren}</div>;
}
function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<T>
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
</T>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
So basically your handling of children will become something like this:
{React.Children.map(this.props.children, (child, i) => {
// Ignore the first child
return React.cloneElement(child, {
newProps1: 1,
newProps2: 2
}, child.props.children)
})}
So this is my code :
import React from "react";
import Navigation from './Navigation';
import Foot from './Foot';
import MovieCard from './MovieCard';
class Favorites extends React.Component {
render() {
const { onSearch, favorites, favoriteCallback, totalFavorites, searchKeyUpdate } = this.props;
return (
<div>
<Navigation
onSearch={onSearch}
totalFavorites={totalFavorites}
searchKeyUpdate={searchKeyUpdate} />
<div className="container">
<button onClick={()=> this.clearFavorites(favorites)}> Clear all movies </button>
{(favorites.length < 1) ?
<h1 style={{ fontSize: '13px', textAlign: 'center' }}>Please mark some of the movies as favorites!</h1>
:
<ul
className="movies">
{favorites
.map(movie => (
<MovieCard
movie={movie}
key={movie.imdbID}
toggleFavorite={favoriteCallback}
favorites={favorites}
/>
))}
</ul>
}
<Foot />
</div>
</div>
);
}
}
const clearFavorites = (favorites) => {
this.setState({ favorites: [] });
}
The thing I need for the button to do is that when i click it that it clears the whole state of favorites. The clearFavorites function is used to clear everything but when I try this I get an error:
Why doesn't this clear the state of favorites?
You have two problems:
clearFavorites function is not in your class. So you should put it inside.
You are trying to clear the data inside the favorites array, which is not part of your state, using the function clearFavorites. So, first of all, you should add favorites array to your state and then you can manipulate the information. I suggest you to use the function getDerivedStateFromProps.
As others mentioned, first moving clearFavorites function into Favorites class.
Second, your favorites list is not part of state object, but instead you pull it out from this.props.favorites, so instead of using this.setState, we should just change the props value.
Third, since you're emptying the array, the parameter in your clearFavorites probably not needed? Please refer to below:
First we define a constructor to get the value from props and pass it to state in the constructor as below:
constructor(props) {
super(props);
this.state = {favorites: this.props.favorites}
}
clearFavorites = () => {
this.setState({favorites: []});
};
Then at last in your render method change to following:
const { onSearch, favoriteCallback, totalFavorites, searchKeyUpdate } = this.props;
const favorites = this.state.favorites;// Or in your ul tag, instead of using favorites, change it to this.state.favorites
You can try to move the clearFavorites into your component
import React from "react";
import Navigation from "./Navigation";
import Foot from "./Foot";
import MovieCard from "./MovieCard";
class Favorites extends React.Component {
render() {
const {
onSearch,
favorites,
favoriteCallback,
totalFavorites,
searchKeyUpdate
} = this.props;
return (
<div>
<Navigation
onSearch={onSearch}
totalFavorites={totalFavorites}
searchKeyUpdate={searchKeyUpdate}
/>
<div className="container">
<button onClick={() => this.clearFavorites(favorites)}>
{" "}
Clear all movies{" "}
</button>
{favorites.length < 1 ? (
<h1 style={{ fontSize: "13px", textAlign: "center" }}>
Please mark some of the movies as favorites!
</h1>
) : (
<ul className="movies">
{favorites.map(movie => (
<MovieCard
movie={movie}
key={movie.imdbID}
toggleFavorite={favoriteCallback}
favorites={favorites}
/>
))}
</ul>
)}
<Foot />
</div>
</div>
);
}
clearFavorites = favorites => {
this.setState({ favorites: [] });
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
I am trying to use react DnD in my react Project. In my render method I define a variable named Populate like show below, which returns a list of cards like this
render() {
var isDragging = this.props.isDragging;
var connectDragSource = this.props.connectDragSource;
var Populate = this.props.mediaFiles.map((value) => {
return(
<div>
<MuiThemeProvider>
<Card style= {{marginBottom: 2, opacity: isDragging ? 0 : 1}} id={value.id} key={value.id}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
//onTouchTap={() => {this.handleClick(value.id)}}
zDepth={this.state.shadow}>
<CardHeader
title={value.Episode_Name}
//subtitle={value.description}
actAsExpander={false}
showExpandableButton={false}
/>
</Card>
</MuiThemeProvider>
</div>
)
});
And my return of render method looks like this
return connectDragSource (
<div>
<MuiThemeProvider>
<div className="mediaFilesComponent2">
{Populate}
</div>
</MuiThemeProvider>
</div>
)
Problem is when I try using drag, then the whole list of cards gets selected for drag. I want all the cards having individual drag functionality.
If you want each card to have drag functionality than you'll have to wrap each card in a DragSource, and not the entire list. I would split out the Card into it's own component, wrapped in a DragSource, like this:
import React, { Component, PropTypes } from 'react';
import { ItemTypes } from './Constants';
import { DragSource } from 'react-dnd';
const CardSource = {
beginDrag: function (props) {
return {};
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}
}
class CardDragContainer extends React.Component {
render() {
return this.props.connectDragSource(
<div>
<Card style= {{marginBottom: 2, opacity: this.props.isDragging ? 0 : 1}} id={value.id} key={value.id}
onMouseOver={this.props.onMouseOver}
onMouseOut={this.props.onMouseOut}
zDepth={this.props.shadow}>
<CardHeader
title={props.title}
actAsExpander={false}
showExpandableButton={false}
/>
</Card>
</div>
)
}
}
export default DragSource(ItemTypes.<Your Item Type>, CardSource, collect)(CardDragContainer);
Then you would use this DragContainer in render of the higher level component like this:
render() {
var Populate = this.props.mediaFiles.map((value) => {
return(
<div>
<MuiThemeProvider>
<CardDragContainer
value={value}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
shadow={this.state.shadow}
/>
</MuiThemeProvider>
</div>
)
});
return (
<div>
<MuiThemeProvider>
<div className="mediaFilesComponent2">
{Populate}
</div>
</MuiThemeProvider>
</div>
);
}
That should give you a list of Cards, each of which will be individually draggable.