I'm relatively new to React and working on a John Conway - Game of Life app. I have built a Gameboard.js functional component for the board itself (which is a child of App.js) and a Square.js functional component which represents an individual square in the board (and is a child of Gameboard and a grandchild of App).
In App I have a function called alive which I want to change the color of an individual square when it is clicked by the user. App also has an 'alive' property in it's state set to false initially and alive will change the property to true when called.
Here is App.js:
import React, { Component } from 'react';
import './App.css';
import GameBoard from './GameBoard.js';
import Controls from './Controls.js';
class App extends Component {
constructor(props){
super(props);
this.state = {
boardHeight: 50,
boardWidth: 30,
iterations: 10,
reset: false,
alive: false
};
}
selectBoardSize = (width, height) => {
this.setState({
boardHeight: height,
boardWidth: width
});
}
onReset = () => {
}
alive = () => {
this.setState({ alive: !this.state.alive });
console.log('Alive function has been called');
}
render() {
return (
<div className="container">
<h1>Conway's Game of Life</h1>
<GameBoard
height={this.state.boardHeight}
width={this.state.boardWidth}
alive={this.alive}
/>
<Controls
selectBoardSize={this.selectBoardSize}
iterations={this.state.iterations}
onReset={this.onReset}
/>
</div>
);
}
}
export default App;
Gameboard looks like this and passes props.alive to Square:
import React, { Component } from 'react';
import Square from './Square.js';
const GameBoard = (props) => {
return (
<div>
<table className="game-board">
<tbody>
{Array(props.height).fill(1).map((el, i) => {
return (
<tr key={i}>
{Array(props.width).fill(1).map((el, j) => {
return (
<Square key={j} alive={props.alive}/>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
);
}
export default GameBoard;
In my CSS I have a class called active that changes the color of an individual square if it is clicked on. How can I make it so that in Square if a td element is clicked the color changes (i.e. the CSS classes is changed to active)?
I've tried this:
import React, { Component } from 'react';
const Square = (props) => {
return(
<td className={props.alive ? "active" : "inactive"} onClick={() => props.alive()}></td>
);
}
export default Square;
The CSS looks like this:
.Square {
background-color: #013243; //#24252a;
height: 12px;
width: 12px;
border: .1px solid rgba(236, 236, 236, .5);
overflow: none;
&:hover {
background-color: #48dbfb; //#00e640; //#2ecc71; //#39FF14;
}
}
.inactive {
background-color: #013243; //#24252a;
}
.active {
background-color: #48dbfb;
}
How can I make it so the .Square CSS class is ALWAYS applied to every square but the individual square color is changed if it's active? In other words, can I set Square's td to always be styled with the .Square CSS class and then individual elements within Square can be colored appropriately depending on whether or not alive is true in App's state?
Is there are ternary approach to always set one particular CSS class and then, in addition, set 1 of 2 other classes....i.e. the Square CSS class is always shown and active or inactive is rendered depending on logic/state?
The comments have the right idea.
You could use a template literal and embed ternary conditionals in that:
return (
<td
className={`Square ${props.alive ? "active" : "inactive"}`}
onClick={() => props.alive()}
></td>
);
A quick refesher on template literals: use backticks to wrap a string, and you can insert a JavaScript expression inside of that by wrapping it in the ${} pattern. As a bonus, template literals can span multiple lines, so no more awkward string concatenation!
const myName = "Abraham Lincoln";
const myString = `Some text.
This text is on the next line but still in the literal.
Newlines are just fine.
Hello, my name is ${myName}.`;
Edit: The bigger problem that I see now is that you're not storing the state of each your cells anywhere. You have only a single boolean stored in App called alive... what you really need is an array of booleans, with each boolean representing the state of a single Square.
The array of "alive" states should live in the App or GameBoard, following the React principle of "the data flows down". In your case you could try keeping it in App, and that way GameBoard and Square can remain purely functional components.
Inside of App you could create a new 2-dimensional array, board, in the constructor and fill it with sub-arrays of 0 values initially:
// App.js
constructor(props){
super(props);
this.state = {
boardHeight: 50,
boardWidth: 30,
board: [],
iterations: 10,
reset: false,
};
this.state.board = new Array(this.state.boardHeight).fill(new Array(this.state.boardWidth).fill(0));
}
In the board array, each index represents one row. So a simplified example of [[0, 0, 1], [0, 1, 0], [1, 1, 1]] would represent:
0 0 1
0 1 0
1 1 1
GameBoard should render your grid of cells based purely on the board prop passed to it, and pass each Square its alive value and callback function as props:
const GameBoard = (props) => {
return (
<div>
<table className="game-board">
<tbody>
{this.props.board.map((row, y) => {
return <tr key={y}>
{row.map((ea, x) => {
return (
<Square
key={x}
x={x}
y={y}
isAlive={ea}
aliveCallback={this.props.alive}
/>
);
})}
</tr>;
})}
</tbody>
</table>
</div>
);
}
From there you should be able to see how this app would work. App stores the game state and renders the functional component GameBoard. In GameBoard, each Square renders according to its alive value, and triggers an aliveCallback when clicked. aliveCallback should set the state of the appropriate value in the board array inside of App, based on its x and y prop.
You can do like
return(
<td className={`Square ${props.alive ? "active" : "inactive"}`}
onClick={() => props.alive()}>
</td>
);
Please refer this code
Problem from title was not a real reason of 'not working'
NOTE: This statement
className={props.alive ? "active" : "inactive"}
is correct, using template literals isn't required.
You can write/use it in many ways:
className={'Square '+ (props.alive ? 'active' : 'inactive')}
To be true there is no need to use 'inactive' as 'Square' has the same bg color.
className={'Square '+ (props.alive ? 'active' : null)}
and de facto no need for ternary operator
className={'square '+ (props.alive && 'active')}
and of course you can 'calculate/prepare' values in plain js before return
const Square = (props) => {
let classes = ['Square','bb']
if( props.alive ) classes.push('active')
classes = classes.join(' ')
return (
<h1 className={classes}>Hello</h1>
)};
Just read docs or google for 'react css in js'.
Related
Hello guys I currently have a buttons like category. I want that when I click a button it will have a color, and when I click it again it will turn to it's original color which is white. When I click 2 button both will have dark color, then click again to remove single color.
this is my div when I'm adding a the category id
<div className={classes.scrollMenu}>
{categories.map((category) => {
return (
<>
<Button
key={category._id}
className={classes.button}
onClick={(e) => {
let values = {
price: [],
category: [category._id],
}
}}
>
{category.name}
</Button>
</>
)
})}
</div>
This is the image that when I click single button it will color one button.
Thank you
code Solution: https://codesandbox.io/s/stoic-meadow-y5cei?file=/src/App.js
App.js
import "./styles.css";
import React, { useState } from "react";
export default function App() {
let categories = ["one", "two", "three"];
const [activeFilter, setActiveFilter] = useState(["one"]);
const categoryOnClick = (category) => {
activeFilter.includes(category)
? removeCategory(category)
: setCategory(category);
};
const setCategory = (category) => {
setActiveFilter([...activeFilter, category]);
};
const removeCategory = (category) => {
const index = activeFilter.findIndex((cat) => cat === category);
activeFilter.splice(index, 1);
setActiveFilter([...activeFilter]);
};
return (
<div className="chip-list my-3">
{categories.map((category, index) => {
return (
<button
key={index}
className={`${activeFilter.includes(category) ? "active" : ""}`}
onClick={() => categoryOnClick(category)}
>
<span>{category}</span>
</button>
);
})}
</div>
);
}
css
.active {
background-color: black;
color: white;
}
check if this solution works for you
used useState hook to hold the state of buttons which you will select
.active class will apply to the button which is selected
On click of that button we will check if the button is already selected or not if selected removeCategory() function run
or if button is not selected then setCategory() function will run and it will update the state
if you need clarification please let me know thanks
Few tips to start with:
Fragment is unnecessary when wrapping single DOM element
Inline function initialisation inside a render is a bad thing. On each new re-render, it allocates extra client memory to newly initialised function. That means, for every map object you will have that many functions, that gets newly created and referenced on each reload
You can easily go with single line return statement of arrow function here. () => <hi> instead of () => { return <hi> }
As for solutions, there are quite a few ways to change button colour during execution. I will suggest the most simple (in my opinion) way to do it. Just have classname variable, then add subclass that will style button accordingly.
Example:
By default it has class name of .button, after click you simply add styling and it ends up having .button .button--red, all is left to do, declaration in css.
.button {
style button here
. . .
add additional stylings here
. . .
&.button--red { color: red }
}
As for how handler should look like, if that is what you asking. Button could be used in your new component let's say, named StyledButton or ColourfulButton that will have internal state to handle what kind of colour is represented.
Say I have a React functional component with some simple state:
import React, { useState } from 'react'
import { makeStyles } from "#material-ui/core"
export default function Basket() {
const [itemCount, setItemCount] = useState<number>(0)
return (
<div>
<Count count={itemCount} />
<button onClick={() => setItemCount(itemCount + 1)}>
Add One
</button>
</div>
)
}
function Count({count}: {count: number}) {
const classes = useStyles()
return (
<div className={classes.count}>
{count}
</div>
)
}
const useStyles = makeStyles({
count: {
backgroundColor: "yellow",
transition: "backgroundColor 2s ease" // ???
}
}
I want the Count component to apply a property whenever the count changes and then remove it again; say, turn on backgroundColor: yellow for 2 seconds and then gradually fade it over 1 second. What's the simplest way to achieve this?
Note that presumably this could be either triggered by a state change on the parent or by a re-rendering of the child. Alternatively, I could add the key property to <Count/> to force a re-mount of the child:
<Count
key={itemCount}
count={itemCount}
/>
Any of those would be acceptable; I'm looking for the simplest, cleanest solution, hopefully one that doesn't require additional state and is compatible with Material-UI styling APIs.
Just an idea.
const Component = () => {
useEffect(() => {
// will trigger on component mount
return () => {
// will trigger on component umount
}
}, [])
}
...
document.getElementById('transition').classList.add('example')
You can use useEffect along with useRef containing a reference to the element or directly getting it with document.getElementById and then update the transition class that way in component mount/unmount. Not sure if it'll work, I haven't tested it myself.
I have a table I've built in React. I want to be able to color a cell if I click on it, and uncolor it if I click on a different cell (the new cell would get colored instead).
I can successfully color cells when I click on them, but I'm not sure how to uncolor them, since the cell object doesn't know when a different cell has been clicked on. (I can't think of anything in the row/table object that would know either, though.) As a result, I end up with a bunch of colored cells if I click multiple times, instead of just one.
Here is my cell code:
class Cell extends React.Component {
state = {
bgColor: 'inherit'
}
handleClick = (columnId) => {
this.setState({
bgColor: "blue"
})
}
render() {
const content = this.props.content;
return (
<td
onClick={()=> this.handleClick()}
style={{backgroundColor: this.state.bgColor}}
>
{content}
</td>
)
}
}
Thanks in advance for your help!
I can successfully color cells when I click on them, but I'm not sure
how to uncolor them, since the cell object doesn't know when a
different cell has been clicked on
You need a single source of truth so that your components know who is active. In the example below, I put the activeCell state on a parent component App so that this will hold the true value of who is active. Pass the props down the to Cell as needed for the updating of the new activeCell on-click
class Cell extends React.Component {
render() {
const content = this.props.content;
return (
<td
onClick={()=>{this.props.handleChangeActiveCell(this.props.identifier)}}
style={{backgroundColor: this.props.activeCell === true ? this.props.bgColor : 'inherit'}}
>
{content}
</td>
)
}
}
class App extends React.Component {
state = {
bgColor: "blue",
activeCell: -1
}
handleChangeActiveCell = (key) => {
this.setState({
activeCell: key
});
}
render(){
return(
<React.Fragment>
<table>
<tr>
<Cell
identifier={0}
handleChangeActiveCell={this.handleChangeActiveCell}
activeCell={this.state.activeCell === 0 ? true : false}
bgColor={this.state.bgColor}
content={`sample_content`}
/>
<Cell
identifier={1}
handleChangeActiveCell={this.handleChangeActiveCell}
activeCell={this.state.activeCell === 1 ? true : false}
bgColor={this.state.bgColor}
content={`sample_content`}
/>
</tr>
</table>
</React.Fragment>
);
}
}
ReactDOM.render(<App/>, document.getElementById("root"));
td {
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I'm coding a React function with parent component containing an array of objects:
let const ingredients = [
{name:"lettuce",color:"green"},
{name:"tomato",color:"red"}
]
...
In a child component, there is a map function that breaks down an array to single items to be displayed in a div.
What is the best practice for defining CSS styling for an object className:"name" to set backgroundColor: {ingredient.color};? I'm trying to avoid manual entry of the entire set of key/values of 'ingredients', to allow updating the object without breaking the code.
I'm currently using inline styling, which I have been advised against. Currently using:
let burg = props.toppings.map((item) => {
const divColor = {backgroundColor: item.color};
return (<div style={divColor}>{item.name}</div>)
Inline style is bad when you have other solution to do what you want. Here, you have a string that is the color (red, green, etc.) so you could write a css class for every color, but that is of course a really bad idea. Inline style is the good way to do it here.
I would suggest setting the class of the div instead of the style. That way you can change the look without resorting to inlining the style.
You could create a css class for lettuce with the background color green, instead of using the item.color you'd set class={ item.name }
You can use this way.
css can be more handy if you use scss
// css
.color-green {
color: green;
}
.color-red {
color: red;
}
import React from "react";
import "./styles.css";
const ingredients = [
{ name: "lettuce", color: "green" },
{ name: "tomato", color: "red" }
];
const Vegetable = ({ color, text }) => {
return (
<p>
this is <span className={`color-${color}`}>{text}</span> color{" "}
</p>
);
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{ingredients.map((item, index) => {
return <Vegetable key={index} color={item.color} text={item.name} />;
})}
</div>
);
}
I have following React code
Code
What I would like is to when I hover my "E-commerce" picture App component background should change on "E-commerce" picture background.
So respectively and for other pictures.
I will be very grateful if you help me solve this problem.
Context, according to the React docs, should be used only for truly global state like current user or theme. Using context for components makes them less reusable.
updated code
Your component tree is App -> SolutionBox -> SolutionItem.
You want to "react" to an event in SolutionItem in App but there is SolutionBox inbetween them so you have to thread the event thru SolutionBox to App.
Step 1
Add a prop to SolutionItem called on OnHover, this will be a function call back that any parent component can use to react to changes.
function SolutionsSectionBoxItem({ solutionIMG, onHover }) {
let callOnHover = state => {
if (_.isFunction(onHover)) {
onHover(state);
}
};
return (
<div className="solutions-section-item-box">
<img
src={solutionIMG}
alt=""
onMouseEnter={() => {
callOnHover(true);
}}
onMouseLeave={() => {
callOnHover(false);
}}
className="solutions-section-item-img"
/>
</div>
);
}
Step 2
Add a prop to SolutionBoxItem called on BGChanged, this will again be a function call back that will be called when any solutionitem onhover happens. This function will take a menuName string and pass either the current menu name or default.
function SolutionsSectionBox({ onBGChanged }) {
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
return (
<div className="solutions-section-box-box">
<SolutionItem
solutionIMG={Ecommerce}
onHover={state => {
callBGChanged(state === true ? "Ecommerce" : "default");
}}
/>
<SolutionItem
solutionIMG={SalesMarketing}
onHover={state => {
callBGChanged(state === true ? "SalesMarketing" : "default");
}}
/>
<SolutionItem
solutionIMG={Analytics}
onHover={state => {
callBGChanged(state === true ? "Analytics" : "default");
}}
/>
<SolutionItem
solutionIMG={Middleware}
onHover={state => {
callBGChanged(state === true ? "Middleware" : "default");
}}
/>
</div>
);
}
Step 3
In the App component listen for the changes. In here we now set state when ever the mouse enters or leaves a solution item. From here you have to change the background, you are using css to control the background url, this will be harder since you now need css class for each background type. You could use the bgImage state value to change the name of the extra css className like 'AppSalesMarketing', 'AppEcommerce', etc.
export default function App() {
const [bgImage, setbgImage] = useState(E);
const onBGChanged = menuName => {
setbgImage(menuName);
};
return (
<div className={`App ${bgImage === "default" ? "" : `App${bgImage}`}`}>
<SolutionBox onBGChanged={onBGChanged} />
</div>
);
}
In CSS
Leave the original App class but based on the bgImage value add an additional one using the name of the bgImage + App like below to cascade down the updated background-image value.
.AppEcommerce {
background-image: url(https://placekitten.com/600/600);
}
.AppSalesMarketing {
background-image: url(https://placekitten.com/500/800);
}
.AppAnalytics {
background-image: url(https://placekitten.com/800/500);
}
.AppMiddleware {
background-image: url(https://placekitten.com/700/700);
}
Extra
I added lodash to test that the incoming props are functions before I call them, it is good to do defensive programming because you never know who may use your component in the future.
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
Two ways to solve the problem. One is passing down a function to update state, the other is to useContext. In this case it makes sense to use context because you are passing down a function through multiple components that do not care about the function.
First thing to do is make the background image dynamic in the div's style and use context:
// Put this outside the component
export const BackgroundContext = React.createContext(null);
// -- snip
const [backgroundImage, setBackgroundImage] = useState(Ecommerce);
const updateBackgroundImage = newImage => setBackgroundImage(newImage);
// -- snip
<BackgroundContext.Provider value={updateBackgroundImage}>
<div className="App" style={{ backgroundImage: `url(${backgroundImage})` }}>
{/* -- snip */}
</BackgroundContext.Provider>
Now in your SolutionsSectionBoxItem component you can import the background context:
import BackgroundContext from "../App";
Then using that context and react's mouseover api, update the selected background image:
const setBackgroundImage = useContext(BackgroundContext);
// -- snip
<img onMouseOver={() => setBackgroundImage(solutionIMG)} {/* -- snip -- */} />
You can read more here: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down