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.
Related
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>
I was trying to implement a slider using react-glider. where I was changing the background color of the slider element when it is clicked. and I am maintaining a state variable selected to keep track of the currently clicked slide inside the parent component and passed that variable and handler function as props inside child component.
So the issue that currently running out here is that when I click on the slider element (which is out of view currently ) the react-glider component re-renders which in turn causes the slider element to go out of view and it displays the slider from the start rather than just displaying the currently clicked one.
So how to prevent parent Component from re-render and only cause the child to re-render based on props passed to it so that the currently clicked slide will not go out of focus.
i have optimized it bit using useCallback and memo but anyhow parent would be re-rendered.and also tried to maintain a ref for keeping track of the clicked slider element but i was not able to implement it .
here is a demo of the issue : https://gifyu.com/image/SwjXG
and code-sandbox link : https://codesandbox.io/s/silly-cray-b2fwbj?file=/src/App.js
import "./styles.css";
import "glider-js/glider.min.css";
import React, { useCallback, useMemo } from "react";
import Glider from "react-glider";
const slides = [
{ index: 1, title: "Slide 1" },
{ index: 2, title: "Slide 2" },
{ index: 3, title: "Slide 3" },
{ index: 4, title: "Slide 4" },
{ index: 5, title: "Slide 5" },
{ index: 6, title: "Slide 6" },
{ index: 7, title: "Slide 7" },
{ index: 8, title: "Slide 8" },
{ index: 9, title: "Slide 9" },
{ index: 10, title: "Slide 10" }
];
export default function App() {
const [selected, setSelected] = React.useState(0);
const handleClick = useCallback((id) => {
if (id !== selected) {
setSelected(id);
}
}, []);
console.log("Parent Re Rendered");
return (
<div className="w-[100%]">
<Glider
slidesToShow={"auto"}
slidesToScroll={"auto"}
draggable
hasDots
scrollLock
>
{slides.map((category, id) => (
<MemoizedCategory
handleClick={handleClick}
key={id}
selected={selected === category.index}
title={category.title}
index={category.index}
/>
))}
</Glider>
</div>
);
}
const MemoizedCategory = React.memo(Category);
function Category({ title, selected, handleClick, index }) {
console.log("Re Rendered ", index);
return (
<div
onClick={() => handleClick(index)}
className={`card ${selected === true ? `bg-blueviolet` : `bg-black`} `}
>
<h1>{title}</h1>
</div>
);
}
#style.css
.App {
font-family: sans-serif;
text-align: center;
}
.card {
display: flex;
height: 100px !important;
width: 200px !important;
background: black;
color: white;
margin: 2px;
}
.bg-blueviolet {
background-color: blueviolet;
}
.bg-black {
background-color: black;
}
I want to filter items by filter method and I did it but it doesn't work in UI but
when I log it inside console it's working properly
I don't know where is the problem I put 2 images
Explanation of this code:
Looping inside currencies_info by map method and show them when I click on it and this completely working then I want filter the list when user enter input inside it I use filter method and this completely working in console not in UI
import React, { useState } from "react";
// Artificial object about currencies information
let currencies_info = [
{
id: 1,
name: "بیت کوین (bitcoin)",
icon: "images/crypto-logos/bitcoin.png",
world_price: 39309.13,
website_price: "3000",
balance: 0,
in_tomans: 0
},
{
id: 2,
name: "اتریوم (ethereum)",
icon: "images/crypto-logos/ethereum.png",
world_price: 39309.13,
website_price: "90",
balance: 0,
in_tomans: 0
},
{
id: 3,
name: "تتر (tether)",
icon: "images/crypto-logos/tether.png",
world_price: 39309.13,
website_price: "5",
balance: 0,
in_tomans: 0
},
{
id: 4,
name: "دوج کوین (dogecoin)",
icon: "images/crypto-logos/dogecoin.png",
world_price: 39309.13,
website_price: "1000000",
balance: 0,
in_tomans: 0
},
{
id: 5,
name: "ریپل (ripple)",
icon: "images/crypto-logos/xrp.png",
world_price: 39309.13,
website_price: "1,108",
balance: 0,
in_tomans: 0
}
];
export default function Buy() {
// States
let [api_data, set_api_data] = useState(currencies_info);
const [currency_icon, set_currency_icon] = useState("");
const [currency_name, set_currency_name] = useState("");
const [currency_price, set_currency_price] = useState(0);
const [dropdown, set_drop_down] = useState(false);
let [search_filter, set_search_filter] = useState("");
// States functions
// this function just toggle dropdown list
const toggle_dropdown = () => {
dropdown ? set_drop_down(false) : set_drop_down(true);
};
// this function shows all currencies inside dropdown list and when click on each item replace
// the currency info and hide dropdown list
const fetch_currency = (e) => {
set_drop_down(false);
currencies_info.map((currency) => {
if (e.target.id == currency.id) {
set_currency_name(currency.name);
set_currency_icon(currency.icon);
set_currency_price(currency.website_price);
}
});
};
// this function filter items base on user input value
const filter_currency = (e) => {
set_search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
api_data = set_search_filter;
console.log(api_data);
};
return (
<div className="buy-page-input" onClick={toggle_dropdown}>
{/* currency logo */}
<div className="currency-logo">
<img src={currency_icon} width="30px" />
</div>
{/* currency name in persian */}
<span className="currency-name">{currency_name}</span>
{/* currency dropdown icon */}
<div className="currency-dropdown">
<img className={dropdown ? "toggle-drop-down-icon" : ""}
src="https://img.icons8.com/ios-glyphs/30/000000/chevron-up.png"
/>
</div>
</div>
{/* Drop down list */}
{dropdown ? (
<div className="drop-down-list-container">
{/* Search box */}
<div className="search-box-container">
<input type="search" name="search-bar" id="search-bar"
placeholder="جستجو بر اساس اسم..."
onChange={(e) => {
filter_currency(e);
}}/>
</div>
{api_data.map((currency) => {
return (<div className="drop-down-list" onClick={(e) => {
fetch_currency(e);}} id={currency.id}>
<div class="right-side" id={currency.id}>
<img src={currency.icon} width="20px" id={currency.id} />
<span id={currency.id}>{currency.name}</span>
</div>
<div className="left-side" id={currency.id}>
<span id={currency.id}>قیمت خرید</span>
<span className="buy-price" id={currency.id}>
{currency.website_price}تومان</span>
</div>
</div>);})}
</div>) : ("")});}
Your search_filter looks redundant to me.
Try to change the filter_currency function like this:
const filter_currency = (e) => {
const search = e.target.value;
const filtered = currencies_info.filter((currency) => {
return currency.name.includes(search);
});
set_api_data(filtered);
};
It looks like you are never setting the api_data after you set the filter state.
Change the following
api_data = set_search_filter
console.log(api_data)
to
api_data = set_search_filter
set_api_data(api_data)
However, it then looks like set_search_filter is never used and only set so to improve this further you could remove that state and just have it set the api_data direct. Something like this:
const filter_currency = (e) => {
const search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1
})
set_api_data(search_filter)
}
Change your state value from string to array of the search_filter like this -
let [search_filter, set_search_filter] = useState([]);
and also it should be like this -
const filter_currency = (e) => {
const filterValues = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
set_search_filter(filtervalues);
set_api_data(filterValues);
console.log(filterValues);
};
and use useEffect with search_filter as dependency, so that every time search_filter value is being set, useEffect will trigger re render, for eg:-
useEffect(()=>{
//every time search_filter value will change it will update the dom.
},[search_filter])
I have an array of objects which I'm rendering by section - see title of each object "Price", "Sectors and Charges" etc.
This populates a mini modal where users can select options to update rendered columns basically a filter.
The selection of the items are working however if I make a selection of the first item "0" all sections with the first option are selected.
How can I store the selection from each object into the selectedOptions array?
Please note I'm using react js and styled components, I've not added the styled component code.
Data:
const columnsData = [
{
title: 'Price',
options: [
{
label: 'Daily Change'
},
{
label: 'Price'
},
{
label: 'Price Date'
},
{
label: 'Volatility Rating'
}
],
},
{
title: 'Sectors and Charges',
options: [
{
label: 'Sector'
},
{
label: 'Asset Class'
},
{
label: 'AMC'
},
],
},
{
title: 'Cumulative Performance',
options: [
{
label: '1 month'
},
{
label: '6 months'
},
{
label: '1 year'
},
],
},
]
Code:
const EditColumns = ({active, onClick}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const update = () => {
onClick();
}
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList>
{item.options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionList>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
export default EditColumns;
Your section lists are all sharing the same state variable, so any changes will be applied to all of them. You could fix this either by constructing a more complex state object which more closely resembles the structure of columnsData, or making each SectionList its own component with its own state. What you decide to do will depend on the degree to which the EditButtons component actually needs access to the whole state.
The second approach might look something like this:
const EditColumns = ({active, onClick}) => {
const update = () => {
onClick();
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList options={item.options}/>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
const SectionList = ({options}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<SectionListContainer>
{options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionListContainer>
)
}
I am trying to use JsonSchema-Form component but i ran into a problem while trying to create a form that, after choosing one of the options in the first dropdown a secondary dropdown should appear and give him the user a different set o options to choose depending on what he chose in the first dropdown trough an API call.
The thing is, after reading the documentation and some examples found here and here respectively i still don't know exactly how reference whatever i chose in the first option to affect the second dropdown. Here is an example of what i have right now:
Jsons information that are supposed to be shown in the first and second dropdowns trough api calls:
Groups: [
{id: 1,
name: Group1}
{id: 2,
name: Group2}
]
User: [User1.1,User1.2,User2.1,User2.2,User3.1,User3.2, ....]
If the user selects group one then i must use the following api call to get the user types, which gets me the the USER json.
Component That calls JSonChemaForm
render(){
return(
<JsonSchemaForm
schema={someSchema(GroupOptions)}
formData={this.state.formData}
onChange={{}}
uiSchema={someUiSchema()}
onError={() => {}}
showErrorList={false}
noHtml5Validate
liveValidate
>
)
}
SchemaFile content:
export const someSchema = GroupOptions => ({
type: 'object',
required: [
'groups', 'users',
],
properties: {
groups: {
title: 'Group',
enum: GroupOptions.map(i=> i.id),
enumNames: GroupOptions.map(n => n.name),
},
users: {
title: 'Type',
enum: [],
enumNames: [],
},
},
});
export const someUISchema = () => ({
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
},
types: {
'ui:options': {
size: {
lg: 15,
},
},
},
});
I am not really sure how to proceed with this and hwo to use the Onchange method to do what i want.
I find a solution for your problem.There is a similar demo that can solve it in react-jsonschema-form-layout.
1. define the LayoutField,this is part of the demo in react-jsonschema-form-layout.To make it easier for you,I post the code here.
Create the layoutField.js.:
import React from 'react'
import ObjectField from 'react-jsonschema-form/lib/components/fields/ObjectField'
import { retrieveSchema } from 'react-jsonschema-form/lib/utils'
import { Col } from 'react-bootstrap'
export default class GridField extends ObjectField {
state = { firstName: 'hasldf' }
render() {
const {
uiSchema,
errorSchema,
idSchema,
required,
disabled,
readonly,
onBlur,
formData
} = this.props
const { definitions, fields, formContext } = this.props.registry
const { SchemaField, TitleField, DescriptionField } = fields
const schema = retrieveSchema(this.props.schema, definitions)
const title = (schema.title === undefined) ? '' : schema.title
const layout = uiSchema['ui:layout']
return (
<fieldset>
{title ? <TitleField
id={`${idSchema.$id}__title`}
title={title}
required={required}
formContext={formContext}/> : null}
{schema.description ?
<DescriptionField
id={`${idSchema.$id}__description`}
description={schema.description}
formContext={formContext}/> : null}
{
layout.map((row, index) => {
return (
<div className="row" key={index}>
{
Object.keys(row).map((name, index) => {
const { doShow, ...rowProps } = row[name]
let style = {}
if (doShow && !doShow({ formData })) {
style = { display: 'none' }
}
if (schema.properties[name]) {
return (
<Col {...rowProps} key={index} style={style}>
<SchemaField
name={name}
required={this.isRequired(name)}
schema={schema.properties[name]}
uiSchema={uiSchema[name]}
errorSchema={errorSchema[name]}
idSchema={idSchema[name]}
formData={formData[name]}
onChange={this.onPropertyChange(name)}
onBlur={onBlur}
registry={this.props.registry}
disabled={disabled}
readonly={readonly}/>
</Col>
)
} else {
const { render, ...rowProps } = row[name]
let UIComponent = () => null
if (render) {
UIComponent = render
}
return (
<Col {...rowProps} key={index} style={style}>
<UIComponent
name={name}
formData={formData}
errorSchema={errorSchema}
uiSchema={uiSchema}
schema={schema}
registry={this.props.registry}
/>
</Col>
)
}
})
}
</div>
)
})
}</fieldset>
)
}
}
in the file, you can define doShow property to define whether to show another component.
Next.Define the isFilled function in JsonChemaForm
const isFilled = (fieldName) => ({ formData }) => (formData[fieldName] && formData[fieldName].length) ? true : false
Third,after you choose the first dropdown ,the second dropdown will show up
import LayoutField from './layoutField.js'
const fields={
layout: LayoutField
}
const uiSchema={
"ui:field": 'layout',
'ui:layout': [
{
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
}
},
{
users: {
'ui:options': {
size: {
lg: 15,
},
},
doShow: isFilled('groups')
}
}
]
}
...
render() {
return (
<div>
<Form
schema={schema}
uiSchema={uiSchema}
fields={fields}
/>
</div>
)
}