Updating State via onClick, in a Component - javascript

I'm new to StackOverflow and looking forward to contributing back to the community!
My first question, I am trying to make some squares change color on the screeen, after an onClick event. I'm nearly there, but I keep getting an error when I try to update the state, which then should updates the color. Please could you let me know what I'm doing wrong?
App.js
import React from "react"
import boxes from "./boxes"
import Box from "./Box"
export default function App() {
const [squares, setSquares] = React.useState(boxes)
function changeOn() {
console.log(squares)//just checking I'm getting the full object
setSquares({
id: 1, on: false //this was previously [...prev], on: !squares.on
})
}
const squaresElement = squares.map(props => (
<Box key={props.id} on={props.on} onClick={changeOn} />
))
return (
<main>
{squaresElement}
</main>
)
}
Box.js
import React from "react"
export default function Box (props) {
const styles= props.on ? {backgroundColor: "#222222"} : {backgroundColor: "none"}
return (
<div className="box" style={styles} onClick={props.onClick}></div>
)
}
Boxes.js
export default [
{
id: 1,
on: true
},
{
id: 2,
on: false
},
{
id: 3,
on: true
},
{
id: 4,
on: true
},
{
id: 5,
on: false
},
{
id: 6,
on: false
},
]
I hope somebody can easily spot what's wrong here?
I was expecting to see the color of the top left box change to a different color, after a click.

There are two issues:
setSquares needs the whole array, so you need to give it a new squares array
The styling back to None does not work always. better to give it the white color again
Please find the codesandbox
export default function App() {
const [squares, setSquares] = React.useState(boxes);
function changeOn(id) {
setSquares(
squares.map((square) => {
return { ...square, on: id === square.id ? !square.on : square.on };
})
);
}
const squaresElement = squares.map((props) => (
<Box key={props.id} on={props.on} onClick={() => changeOn(props.id)} />
));
return <main>{squaresElement}</main>;
}
And in Box.js
const styles = props.on
? { backgroundColor: "#222222" }
: { backgroundColor: "#fff" };

You're calling setSquares and passing it a single object instead of an array.
On the next render squares.map(...) blows up because squares is the object, and the object doesn't have a map method.
// after this call squares is just this one object
setSquares({
id: 1, on: false
})
Here's a possible implementation that pushes the on/off responsibility into the box component itself.
// generates a list of items (faking your boxes.js)
const boxes = Array.from({length: 9}, (_, id) => ({ id }));
// container element to render the list
function Boxen ({ items }) {
return (
<div className="container">
{items.map((item, idx) => (
<Box item={item} key={idx} />
))}
</div>
)
}
// component for a single box that can toggle its own on/off state
function Box ({item}) {
const [active, setActive] = React.useState();
return (
<div onClick={() => setActive(!active)} className={active ? 'active' : ''}>{item.id}</div>
)
}
ReactDOM.render(<Boxen items={boxes}/>, document.getElementById('root'));
.container {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(3, 100px);
gap: 1em;
}
.container > * {
display: flex;
justify-content: center;
align-items: center;
background: skyblue;
}
.container > .active {
background: slateblue;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

React js dynamic z-index when click on a component

What i want to accomplish is when i click on a box, the previous box to be behind the one that is on top, for better reference please check the next code.
https://codesandbox.io/s/optimistic-payne-4644yf?file=/src/styles.css
Desired behavior:
click on red box
click on blue box
and the sequence from bottom to top would be: green,red,blue
I tried a lot of ways but im keep messing up the code, so any help will be welcomed.
do you mean something like this?
const { useState, useEffect } = React
const Test = () => {
const [data, setData] = useState([
{ id: 1, label: "box 1", class: "box1", z: 0 },
{ id: 2, label: "box 2", class: "box2", z: 1 },
{ id: 3, label: "box 3", class: "box3", z: 2 }
]);
const handleClickBox = id => {
setData(p => {
let tmpArr = [...p];
tmpArr = tmpArr.sort((a) => a.id - (id + 1)).reverse().map((ta, i) => ({ ...ta, z: i })).sort((a, b) => a.id - b.id);
return tmpArr;
})
}
return <div className="box-wrapper">
{data.map((d, i) => {
return (
<div
className={d.class}
key={d.id}
style={{ left: i * 100, zIndex: d.z }}
onClick={() => handleClickBox(d.id)}
>
{d.label}
</div>
);
})}
</div>
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Test />
);
.box-wrapper {
position: relative;
}
.box1,
.box2,
.box3 {
position: absolute;
width: 300px;
height: 150px;
color: white;
top: 0;
}
.box1 {
background: green;
}
.box2 {
background: red;
}
.box3 {
background: blue;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
When I was typing my response Layhout answered. That solution works, but mine is slightly different, you need to know the greatest value of zIndex.
import "./styles.css";
import { useState } from "react";
export default function App() {
const [data, setData] = useState([
{ id: 1, label: "box 1", class: "box1", index: 0 }, // red
{ id: 2, label: "box 2", class: "box2", index: 1 }, // blue
{ id: 3, label: "box 3", class: "box3", index: 2 } // green
]);
const handlePosition = (index, selectedIndex) =>
index === selectedIndex ? 2 : index > selectedIndex ? index - 1 : index;
const handleClick = (selectedIndex) => {
// nothing happens if we click on the first item
if (selectedIndex === 2) return;
setData(
data.map((i) => ({
...i,
index: handlePosition(i.index, selectedIndex)
}))
);
};
return (
<div className="App">
<div className="box-wrapper">
{data.map((b) => {
return (
<div
className={b.class}
key={b.id}
style={{ zIndex: b.index }}
onClick={() => handleClick(b.index)}
>
{b.label}
</div>
);
})}
</div>
</div>
);
}
Here is the full implementation on CodeSandbox.
The most important fact is zIndex of each object is also a UI state, so it needs to be in useState to change with user clicks. After this, you need to implement an algorithm to reorder items based on the clicked item. That is this function:
const handlePosition = (index, selectedIndex) =>
index === selectedIndex ? 2 : index > selectedIndex ? index - 1 : index;
It seems that the desired result may actually be a solution that handles z-index independently, without adding to the given data, and is capable of handling more than 3 div items if needed.
Here is a basic example that uses a state array activeList to handle the changes of z-index, so it is independent to data and can still work if data scales.
It uses the index of the state array to calculate z-index for each item. On click event, it pushes an item to the end of array (so it will have the highest z-index), as a lightweight approach to handle the re-order of z-index.
Forked live demo on: codesandbox
import "./styles.css";
import { useState } from "react";
export default function App() {
const [activeList, setActiveList] = useState([]);
const handleClick = (id) =>
setActiveList((prev) => {
const current = prev.filter((item) => item !== id);
return [...current, id];
});
const data = [
{ id: 1, label: "box 1", class: "box1" },
{ id: 2, label: "box 2", class: "box2" },
{ id: 3, label: "box 3", class: "box3" }
];
return (
<div className="App">
<div className="box-wrapper">
{data.map((b) => {
const activeIndex = activeList.findIndex((id) => id === b.id);
const zIndex = activeIndex >= 0 ? activeIndex + 1 : 0;
return (
<div
className={b.class}
key={b.id}
style={{ zIndex }}
onClick={() => handleClick(b.id)}
>
{b.label}
</div>
);
})}
</div>
</div>
);
}
Hope this could help.

React.js - How to check if the div is at the bottom of the screen (dynamically)

Is there any way to check if the div is at the bottom of the another div (acting as a parent, or container).
What I tried
So basically I made demo where there are child elements (items, setItems) in the div that can be added and deleted and also you can change the height of them by clicking on the divs (important here). Also there is another div, which is not in the items state, where I want to change the title of that item, if it is at the bottom of the his parent div (also items have the same parent as this div has).
Problem with my solution
I have tried something where I am looking at the getBoundingClientRect() of the parent container and this "blue" div, lets call it like that, and it will work fine, ONLY IF the items have the same height, but soon as a delete the one item and change the height of it by clicking on the div, it will not work. It will show that it is on the bottom of the screen (the title will be true) but in reality it is not.
My code
App.js - only for demo purposes
import "./styles.css";
import { useState, useEffect, useRef } from "react";
export default function App() {
const arrayItems = [
{
id: 1,
name: "test",
resized: false
},
{
id: 2,
name: "test1",
resized: false
},
{
id: 3,
name: "test2",
resized: false
}
];
const [items, setItems] = useState(arrayItems);
const [title, setTitle] = useState(false);
const parentRef = useRef(null);
const itemsRef = useRef(null);
useEffect(() => {
if (
parentRef?.current.getBoundingClientRect().bottom -
itemsRef?.current.getBoundingClientRect().height <=
itemsRef?.current.getBoundingClientRect().top
) {
setTitle(true);
} else {
setTitle(false);
}
}, [parentRef, items]);
const handleClick = () => {
const maxValue = Math.max(...items.map((item) => item.id)) + 1;
setItems((prev) => [
...prev,
{ id: maxValue, name: "testValue", resized: false }
]);
};
const handleDelete = () => {
setItems((prev) => prev.slice(0, prev.length - 1));
};
const handleResize = (item) => {
setItems((prev) =>
prev.map((itemOld) => {
if (itemOld.id === item.id) {
return itemOld.resized === true
? { ...itemOld, resized: false }
: { ...itemOld, resized: true };
} else {
return itemOld;
}
})
);
};
console.log(items);
return (
<div className="App">
<button onClick={handleClick}>Add new</button>
<button onClick={handleDelete}>Delete last</button>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div ref={parentRef} className="container">
{items?.map((item) => {
return (
<div
onClick={() => handleResize(item)}
style={{ height: item.resized ? "70px" : "20px" }}
key={item.id}
className="container-item"
>
<p>{item.name}</p>
</div>
);
})}
<div ref={itemsRef} id="title-div">
{title ? "At the bottom" : "Not at the bottom"}
</div>
</div>
</div>
);
}
styles.css
.App {
font-family: sans-serif;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 1rem;
}
* {
margin: 0;
padding: 0;
}
.container {
width: 600px;
height: 300px;
background-color: gray;
display: flex;
flex-direction: column;
}
.container-item {
width: 100%;
background-color: hotpink;
}
#title-div {
width: 100%;
padding: 1rem;
background-color: blue;
color: white;
}
What I want to make
As the title suggest I want to see if the div is at the bottom of the container/parent div. That is it, and other items in that parent div, cannot interfere with this div, in sense that adding, resizing, deleting those items, will not suddenly change the position of the div that I want to analyse (to see if it is at the bottom of the screen)
I have come up with my own solution and it works always. I just have to deduct the "top" from parentsRef and "top" from the itemsRef, and add to that the clientHeight of the itemsRef. This way it will always be at the bottom of the container, doesnt matter if I delete the items, resize them etc.
The code
useEffect(() => {
if (
parentRef?.current.clientHeight <=
itemsRef?.current.getBoundingClientRect().top -
parentRef?.current.getBoundingClientRect().top +
itemsRef?.current.clientHeight
) {
setTitle(true);
} else {
setTitle(false);
}
}, [parentRef, items, itemsRef]);

Filtering Mapped React Table Data with JSON Data

I successfully mapped JSON inventory data into a React Table. My goal is to use buttons to filter this table and render the table results based on the filter conditions. How do I do this?
Button Filters Component:
import React from 'react'
import Button from './Button'
const Form = ({reqType, setReqType, color, setColor, handleColorChange}) => {
return (
<form onSubmit={(e)=> e.preventDefault()}>
<Button
buttonText="Red"
>
</Button>
<Button
buttonText="White"
/>
</form>
)
}
export default Form
Table Component:
import React from 'react'
import Row from './Row'
const Table = ({ wines }) => {
return (
<table >
<tbody >
{wines.map((wine, key) =>(
<Row wine={wine} key={key}/>
))}
</tbody>
</table>
)
}
export default Table
Row Component:
import React from 'react'
import Cell from './Cell'
const Row = ({ wine }) => {
return (
<tr>
{Object.entries(wine).map(([key, value]) => {
return (
<Cell key={key} cellData={JSON.stringify(value)}/>
)
} ) }
</tr>
)
}
export default Row
Cell Component:
import React from 'react'
const Cell = ({cellData,wine}) => {
return (
<td >
{cellData}
</td>
)
}
export default Cell
App Component:
<Form
reqType={reqType}
setReqType={setReqType}
color={color}
setColor={setColor}
handleColorChange={handleColorChange}/>
<Table wines={wines}/>
I mentioned that you need a state for the filtered data. You don't. But you do need two - one to hold the full data set, and another to maintain the colour. You can then use a function to filter out objects based on their colour.
So I've played around with your code, removed a bunch of things to make this example readable, added a Button component, added a reset button, and added a basic dataset.
Hopefully you can see how it all fits together.
const { useState } = React;
// We passing in the data - you may not being doing this
// your list of wines may come from an API call, but I'm
// keeping this simple
function Example({ data }) {
// Set the wines state to the passed in data,
// and initialise the color state (to undefined)
const [ wines, setWines ] = useState(data);
const [ color, setColor ] = useState();
// Simple function to accept a color
// as an argument and update the state with it
function handleColor(color) {
setColor(color);
}
// If color is undefined return all the wines!
// Otherwise `filter` the wines based on their colour
function filterData(wines, color) {
if (!color) return wines;
return wines.filter(wine => wine.color === color);
}
// Pass down the handleColor function in the Form props,
// and call `filteredData` to have an updated list of wines
// that you pass as a props to the table
return (
<div>
<Form handleColor={handleColor} />
<Table wines={filterData(wines, color)} />
</div>
);
}
// Pass in the `handleColor` function
function Form({ handleColor }) {
// Buttons get their own `handleClick` function
// which extracts the colour from the button's dataset
// and then calls `handleColor` with that color
function handleClick(e) {
const { color } = e.target.dataset;
handleColor(color);
}
// Button components get text, color,
// and the `handleClick` function as props
return (
<div>
<Button
text="Red"
color="red"
handleClick={handleClick}
/>
<Button
text="White"
color="white"
handleClick={handleClick}
/>
<Button
text="Reset"
color=""
handleClick={handleClick}
/>
</div>
);
}
function Button({ text, color, handleClick }) {
return (
<button
data-color={color}
onClick={handleClick}
>{text}</button>
);
}
function Table({ wines }) {
return (
<table>
<tbody>
{wines.map(wine => (
<Row wine={wine} key={wine.id}/>
))}
</tbody>
</table>
);
}
function Row({ wine }) {
return (
<tr>
{Object.entries(wine).map(([key, value]) => {
return <Cell key={value} value={value} />;
})}
</tr>
)
}
function Cell({ value }) {
return <td>{value}</td>;
}
const data = [
{ id: 1, name: 'Plonk', color: 'red' },
{ id: 2, name: 'Rose', color: 'red' },
{ id: 3, name: 'Vanilla', color: 'white' },
{ id: 4, name: 'White', color: 'white' },
{ id: 5, name: 'Plonk', color: 'red' },
{ id: 6, name: 'Steve', color: 'white' }
];
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
button { margin-right: 0.2em; }
table { border-collapse: collapse; border: 1px solid #676767; margin-top: 1em; }
table td { padding: 0.4em; border: 1px solid #efefef; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

How to drag and drop html element in inner card or box?

I am trying to drag and drop any html element in nested level of container.
First level of drag and drop of elements are working but nested level is not working.
Nested level means "Dropping button inside card element which also an element".
I am taking card as control and container.
I am developing in reactjs, react-dnd.
Code :
app.js
const App = props =>{
const [controlsList, setControlList]= useState([
{ email_txt }, { button }, { card } , {textarea } ...
])
return (
<>
<div className="draggable">
{
controlsList.map(({_id, type, title}, index)=>{
<ControlsAndContainers _id={_id} type={type} title={title} />
})
}
</div>
<div className="droppable">
<DropBox/>
</div>
</>
)
}
ControlsAndContainer.js
import { useDrag } from 'react-dnd'
const ControlsAndContainer = ({_id, type, title })=>{
const [ {opacity}, drag ] = useDrag(()=>({
type,
item: { _id, type, title },
end: (item, monitor)=>{
//
},
collect: (monitor) =>({
opacity: monitor.isDragging()? 0.4 : 1
})
}), [title, type]);
const box_style = {
cursor: 'move', border: '1px dashed gray'
}
return (
<div ref={drag} style={{ ...box_style, opacity}}>
{title}
</div>
)
}
dropbox.js
import { useDrop } from 'react-dnd'
const DropBox = () =>{
let temp =[];
const [dataState, setDataState] = useState([]);
const [{isOver }, drop] = useDrop(()=> ({
accept: ['button', 'email', 'card', 'textarea'],
drop(item, monitor){
temp.push(item);
setDataState(temp);
},
collect:(monitor)=>{
isOver: monitor.isOver(), ​
​}
​}), []);
​const ButtonControl = () => {
​return ( <div> <button>Button</button> </div>)
​}
​.... email, textarea
​// card code is from react-bootsrap
​const CardControl = () => {
​<Card style={{ width: '18rem' }}>
​<Card.Header>Header</Card.Header>
​<Card.Body>
​Drop other element here
​</Card.Body>
​<Card.Footer>Footer</Card.Footer>
​</Card>
​}
​return (
​<div ref={drop}>
​dataState.map((data,index)=>{
​let container;
​switch(data.type){
​case 'button': container=<ButtonControl />
​break;
​case 'button': container=<CardControl />
​break;
​default: break;
​}
​return (
​<> <div key={data._id}> { container } </div></>
​)
​})
​</div>
​)
}
I am trying to drag and drop button inside "Card" control which is not working but card drag and drop is working and outside the card is also working.
What I am missing ?
Please somebody help
I solved this problem. There are two ways to solve it.
create 2nd drop ref, I mean
const [, nestedDrop] = useDrop(()=>{ accept, drop, ... }));
use nestedDrop inside inner container like this ,
​<Card.Body>
​<div ref={nestedDrop}></div>
</Card.Body>
way is inspired from this official example :
nested drop area
you can customize nested dropbox according to your need.

Reactjs, Styling each element of array with different styles

I want to create an array with some elements and then iterate (with .forEach or .map) it.
but each of array elements must have different style. for example I have an array like that:
["johnny red", "Stephanie blue", "Hans green", "Fifty Shades Of grey :d"]
also, I want reuse some of array's individual elements in another component.
how can i do that?
(imagine that last word in each element is a individual style e.g. background-color or color,)
Can you help me?
please give me an advice, what would I do?
There are many ways to achieve this, the key difference is how you are representing your value-style pairs.
Eventually, you need to map the values to their styles.
For example:
const myStyles = {
'johnny red': { color: 'red' },
'Stephanie blue': { color: 'blue' },
'Hans green': { color: 'green' },
'Fifty Shades Of grey :d': { color: 'pink' }
};
export default function App() {
return (
<FlexBox>
<FlexItem>
{Object.entries(myStyles).map(([key, style]) => (
<div key={key} style={style}>
{key}
</div>
))}
</FlexItem>
</FlexBox>
);
}
It's not very clear what you want, but you can attach styles to components like this:
const values = [
{
text: "one",
css: { color: 'blue' }
},
{
text: "two",
css: { color: 'red' }
},
];
const Test = props => {
const { values } = props;
return (
<div>{values.map(
(value, i) => <div key={i} style={value.css}>{value.text}</div>
)}</div>
);
}
ReactDOM.render(
<Test values={values} />,
document.getElementById("react")
);
<div id="react"></div>
<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>
You mean something like this? The snippet is below and here is the codepen texample: https://codepen.io/tormod17/pen/apvXoW?editors=0010
Also, have you considered incorporating styled components with react? It could help with this project. https://www.styled-components.com/docs/basics
{children.map(element => {
return (
<element.type
{...element.props}
style={{
height: '33%',
}}
/>
)
})}
This is from another project:
import * as React from "react";
import { IAddressListProps } from '../../detailPage/components/interfaces/IAddressListProps';
import { config } from "../../config";
export class PrintAddressList extends React.Component<IAddressListProps, {}>{
constructor(props: IAddressListProps) {
super(props);
}
private createAddressCard = () => {
let parent = [];
this.props.items.map((address) => {
parent.push(<div style={{ padding: '10px', border: '1px solid #ccc', margin: '5px' }}>
<label>{config.colNames.addressList.clmnAddressType}: </label>
<label>{address.Adressart}</label><br />
<label>{config.colNames.addressList.clmnCompanyName}: </label>
<label>{address.Firma}</label><br />
<label>{config.colNames.addressList.clmnPlaceName}: </label>
<label>{address.Ort}</label><br />
<label>{config.colNames.addressList.clmnZipCode}: </label>
<label>{address.PLZ}</label><br />
<label>{config.colNames.addressList.clmnTelephone}: </label>
<label>{address.Telefon}</label><br />
</div>);
});
return parent;
}
public render(): React.ReactElement<IAddressListProps> {
return (
<div>
{this.createAddressCard()}
</div>
);
}
}

Categories