I have a simple component that renders a menu with items. What I am trying to do is have a value called isLoggedIn that accesses the value of the Italian Food item, change it's value to true and later hide the Italian Food item. Currenly my code works, the Italian Restaurant item gets hidden, but is there a better way to access the available property, change it based on a condition and to hide the element? Here is my code:
import React, { useState, useEffect } from 'react';
import { withRouter } from 'react-router-dom';
import {
Drawer,
DrawerContent,
DrawerItem,
} from '#progress/kendo-react-layout';
import { Button } from '#progress/kendo-react-buttons';
const CustomItem = (props) => {
const { visible, ...others } = props;
const [isLoggedIn, setIsLoggedIn] = React.useState(
props.available ? false : true
);
const arrowDir = props['data-expanded']
? 'k-i-arrow-chevron-down'
: 'k-i-arrow-chevron-right';
React.useEffect(() => {
setIsLoggedIn(props.available);
}, [props.available]);
return (
<React.Fragment>
{isLoggedIn === false ? null : (
<DrawerItem {...others}>
<span className={'k-icon ' + props.icon} />
<span className={'k-item-text'}>{props.text}</span>
{props['data-expanded'] !== undefined && (
<span
className={'k-icon ' + arrowDir}
style={{
position: 'absolute',
right: 10,
}}
/>
)}
</DrawerItem>
)}
</React.Fragment>
);
};
const DrawerContainer = (props) => {
const [drawerExpanded, setDrawerExpanded] = React.useState(true);
const [items, setItems] = React.useState([
{
text: 'Education',
icon: 'k-i-pencil',
id: 1,
selected: true,
route: '/',
},
{
separator: true,
},
{
text: 'Food',
icon: 'k-i-heart',
id: 2,
['data-expanded']: true,
route: '/food',
},
{
text: 'Japanese Food',
icon: 'k-i-minus',
id: 4,
parentId: 2,
route: '/food/japanese',
},
{
text: 'Italian Food',
icon: 'k-i-minus',
id: 5,
parentId: 2,
route: '/food/italian',
available: false,
},
{
separator: true,
},
{
text: 'Travel',
icon: 'k-i-globe-outline',
['data-expanded']: true,
id: 3,
route: '/travel',
},
{
text: 'Europe',
icon: 'k-i-minus',
id: 6,
parentId: 3,
route: '/travel/europe',
},
{
text: 'North America',
icon: 'k-i-minus',
id: 7,
parentId: 3,
route: '/travel/america',
},
]);
const handleClick = () => {
setDrawerExpanded(!drawerExpanded);
};
const onSelect = (ev) => {
const currentItem = ev.itemTarget.props;
const isParent = currentItem['data-expanded'] !== undefined;
const nextExpanded = !currentItem['data-expanded'];
const newData = items.map((item) => {
const {
selected,
['data-expanded']: currentExpanded,
id,
...others
} = item;
const isCurrentItem = currentItem.id === id;
return {
selected: isCurrentItem,
['data-expanded']:
isCurrentItem && isParent ? nextExpanded : currentExpanded,
id,
...others,
};
});
props.history.push(ev.itemTarget.props.route);
setItems(newData);
};
const data = items.map((item) => {
const { parentId, ...others } = item;
if (parentId !== undefined) {
const parent = items.find((parent) => parent.id === parentId);
return { ...others, visible: parent['data-expanded'] };
}
return item;
});
return (
<div>
<div className="custom-toolbar">
<Button icon="menu" look="flat" onClick={handleClick} />
<span className="title">Categories</span>
</div>
<Drawer
expanded={drawerExpanded}
mode="push"
width={180}
items={data}
item={CustomItem}
onSelect={onSelect}
>
<DrawerContent>{props.children}</DrawerContent>
</Drawer>
</div>
);
};
export default withRouter(DrawerContainer);
If I understood your request properly you want to calculate the isLoggedIn property based on props.available, right? If this is correct then you may just use the useMemo hook in the following way:
const CustomItem = (props) => {
const { visible, ...others } = props;
const isLoggedIn = React.useMemo(() => {
return !props.available
});
const arrowDir = props['data-expanded']
? 'k-i-arrow-chevron-down'
: 'k-i-arrow-chevron-right';
return (
<React.Fragment>
{isLoggedIn === false ? null : (
<DrawerItem {...others}>
<span className={'k-icon ' + props.icon} />
<span className={'k-item-text'}>{props.text}</span>
{props['data-expanded'] !== undefined && (
<span
className={'k-icon ' + arrowDir}
style={{
position: 'absolute',
right: 10,
}}
/>
)}
</DrawerItem>
)}
</React.Fragment>
);
};
Here the doc of the hook if you want to go deeper.
Related
I need to set the active classname to multiple onclick items inside a .map
I need the list of active items that were clicked
The items that were clicked will be highlighted in yellow, and when i click the same item again it should be removed from active list items.
const [data, setData] = useState([]);
const [activeIndicies, setActiveIndicies] = useState(() =>
data?.map(() => false)
);
useEffect(() => {
// This data is coming from the API response
const data = [
{ id: 1, name: "one" },
{ id: 2, name: "two" },
{ id: 3, name: "three" }
];
setData(data);
}, []);
return statement
onClick={() => {
setActiveIndicies(
activeIndicies.map((bool, j) => (j === index ? true : bool))
);
}}
Code Sandbox
Thank you.
try this one:
import "./styles.css";
import React, { useState, useEffect } from "react";
export default function App() {
const [data, setData] = useState([
{ id: 1, name: "one", active: false },
{ id: 2, name: "two", active: false },
{ id: 3, name: "three", active: false }
]);
return (
<div className="App">
<h2>Set active className to multiple items on .map</h2>
{data?.map((item, index) => {
return (
<p className={data[index].active ? "selected" : "notselected"}
onClick={() => {
setData((prevState) =>
_.orderBy(
[
...prevState.filter((row) => row.id !== item.id),
{ ...item, active: !item.active }
],
["name"],
["asc"]
)
);
}}
>
{item.name}
</p>
);
})}
</div>
);
}
You can acheive this by simply making some minor changes to your code:
// Changing the state value to an object so that it can
// store the active value for exact item ids
const [activeIndicies, setActiveIndicies] = useState({});
Then inside of .map()
....
// Checking if there is any value for the item id which is being mapped right now.
const selected = activeIndicies[item.id];
return (
<p
className={selected ? "selected" : "notselected"}
onClick={() => {
/* Then setting the state like below where it toggles
the value for particular item id. This way if item is
selected it will be deselected and vice-versa.
*/
setActiveIndicies((prevState) => {
const newStateValue = !prevState[item.id];
return { ...prevState, [item.id]: newStateValue };
});
}}
// Key is important :)
key={item.id}
>
{item.name}
</p>
);
Hello, friends!
I solved this problem in a more convenient way for me )
const data = [
{ id: 1, name: "Ann", selected: true },
{ id: 2, name: "Serg", selected: false },
{ id: 3, name: "Boris", selected: true },
];
//you don't even need to have a boolean field in the object -
//it will be added by itself on the first click on the element
// const data = [{ name:"Ann", id:1}, { name:"Serg", id:2 },{ name:"Boris", id:3 },]
const [users, setUsers] = useState(data); // no square brackets-[data]
function handleActive(item) {
setUsers((prev) => {
return prev.map((itemName) => {
if (itemName.name === item.name) {
return { ...itemName, selected: !itemName.selected };
// itemName.selected = !itemName.selected // or so
}
return itemName;
});
});
}
return (
{
users.map((item, i) => {
// let current = item.selected === true
// or so -> className={current === true ? 'users': ''}
return (
<div
onClick={() => handleActive(item)}
className={item.selected === true ? "active" : ""}
key={i}
>
{item.name}
</div>
);
})
}
);
I used Ant table to show some information.
https://codesandbox.io/s/proud-architecture-lsb85?file=/src/index.js
I want to customize the position of the checkbox for row selection.
In this application, you can see the header in the following order of checkbox, Name, Age, Address but I want to swap checkbox and Name.
You can add checkbox columns and customize render and titleRender of it to checkbox and then handle the events. if you incounter performance issue you have to add some memoization on columns or evenet handlers.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import { Table, Button, Checkbox } from "antd";
const data = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`
});
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowKeys: [], // Check here to configure the default column
loading: false,
allChecked: false
};
this.columns = [
{
title: "Name",
dataIndex: "name"
},
{
dataIndex: "checked",
title: () => {
return (
<Checkbox
checked={this.state.allChecked}
onChange={(e) => this.selectAll(e)}
></Checkbox>
);
},
render: (text, rec, index) => {
return (
<Checkbox
checked={
this.state.selectedRowKeys.includes(rec.key) ||
this.state.allChecked
}
onChange={(e) => this.onChange(e, rec)}
></Checkbox>
);
}
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
}
start = () => {
this.setState({ loading: true });
// ajax request after empty completing
setTimeout(() => {
this.setState({
selectedRowKeys: [],
loading: false
});
}, 1000);
};
onChange = (e, rec) => {
const checked = e.target.checked;
if (checked) {
this.setState((state) => ({
...state,
selectedRowKeys: [...state.selectedRowKeys, rec.key]
}));
} else {
this.setState((state) => ({
...state,
selectedRowKeys: [
...state.selectedRowKeys.filter((item) => item !== rec.key)
]
}));
}
};
selectAll = (e) => {
const checked = e.target.checked;
if (checked) {
this.setState((state) => ({
...state,
allChecked: true
}));
} else {
this.setState((state) => ({
...state,
allChecked: false
}));
}
};
onSelectChange = (selectedRowKeys) => {
console.log("selectedRowKeys changed: ", selectedRowKeys);
this.setState({ selectedRowKeys });
};
render() {
const { loading, selectedRowKeys } = this.state;
const hasSelected = selectedRowKeys.length > 0;
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
onClick={this.start}
disabled={!hasSelected}
loading={loading}
>
Reload
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} items` : ""}
</span>
</div>
<Table columns={this.columns} dataSource={data} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
First time poster so let me know if more information is need.
Trying to figure out why my global state using context API is being updated even when my setSate method is commented out. I thought i might have been mutating the state directly accidently but I dont believe I am
"specialModes" in actionOnClick() is the state in question
const SpecialFunctions: FC = (props: Props) => {
const { currentModeContext, specialModesContext: specialActionsContext, performCalc, inputValueContext } = useContext(AppContext)
const { specialModes, setSpecialModes } = specialActionsContext
const { currentMode, setCurrentMode } = currentModeContext
const { inputValue, setInputValue } = inputValueContext
const categoryOnClick = (index: number) => {
setCurrentMode(specialModes[index])
console.log(specialModes[index].title);
}
const actionOnClick = (action: IAction) => {
let newAction = action
newAction.value = performCalc()
let newSpecialModes = specialModes.map((mode) => {
if (mode === currentMode) {
let newMode = mode
newMode.actions = mode.actions.map((element) => {
if (element === action) {
return newAction
}
else return element
})
return newMode
}
else return mode
})
//setSpecialModes(newSpecialModes)
}
let headings = specialModes.map((categorgy, index) => {
return <Heading isActive={categorgy === currentMode ? true : false} onClick={() => categoryOnClick(index)} key={index}>{categorgy.title}</Heading>
})
let actions = currentMode.actions.map((action, index) => {
return (
<Action key={index} onClick={() => actionOnClick(action)}>
<ActionTitle>{action.title}</ActionTitle>
<ActionValue>{action.value}</ActionValue>
</Action>
)
})
return (
<Wrapper>
<Category>
{headings}
</Category>
<ActionsWrapper toggleRadiusCorner={currentMode === specialModes[0] ? false : true}>
{actions}
</ActionsWrapper>
</Wrapper>
)
}
Context.tsx
interface ContextType {
specialModesContext: {
specialModes: Array<ISpecialModes>,
setSpecialModes: React.Dispatch<React.SetStateAction<ISpecialModes[]>>
},
currentModeContext: {
currentMode: ISpecialModes,
setCurrentMode: React.Dispatch<React.SetStateAction<ISpecialModes>>
},
inputValueContext: {
inputValue: string,
setInputValue: React.Dispatch<React.SetStateAction<string>>
},
inputSuperscriptValueContext: {
inputSuperscriptValue: string,
setInputSuperscriptValue: React.Dispatch<React.SetStateAction<string>>
},
performCalc: () => string
}
export const AppContext = createContext({} as ContextType);
export const ContextProvider: FC = ({ children }) => {
const [SpecialModes, setSpecialModes] = useState([
{
title: 'Rafter',
actions: [
{
title: 'Span',
active: false
},
{
title: 'Ridge Thickness',
active: false
},
{
title: 'Pitch',
active: false
}
],
},
{
title: 'General',
actions: [
{
title: 'General1',
active: false
},
{
title: 'General2',
active: false
},
{
title: 'General3',
active: false
}
],
},
{
title: 'Stairs',
actions: [
{
title: 'Stairs1',
active: false
},
{
title: 'Stairs2',
active: false
},
{
title: 'Stairs3',
active: false
}
],
}
] as Array<ISpecialModes>)
const [currentMode, setCurrentMode] = useState(SpecialModes[0])
const [inputValue, setInputValue] = useState('0')
const [inputSuperscriptValue, setInputSuperscriptValue] = useState('')
const replaceCharsWithOperators = (string: string): string => {
let newString = string.replaceAll(/\s/g, '') // delete white space
newString = newString.replace('×', '*')
newString = newString.replace('÷', '/')
console.log(string)
console.log(newString)
return newString
}
const performCalc = (): string => {
let originalEquation = `${inputSuperscriptValue} ${inputValue} =`
let equation = inputSuperscriptValue + inputValue
let result = ''
equation = replaceCharsWithOperators(equation)
result = eval(equation).toString()
setInputSuperscriptValue(`${originalEquation} ${result}`)
setInputValue(result)
console.log(result)
return result
}
return (
<AppContext.Provider value={
{
specialModesContext: {
specialModes: SpecialModes,
setSpecialModes: setSpecialModes
},
currentModeContext: {
currentMode,
setCurrentMode
},
inputValueContext: {
inputValue,
setInputValue
},
inputSuperscriptValueContext: {
inputSuperscriptValue,
setInputSuperscriptValue
},
performCalc
}}>
{children}
</AppContext.Provider>
)
}
In your mode.actions.map() function you are indirectly changing actions field of your original specialModes array.
To fix this problem you need to create shallow copy of specialModes array Using the ... ES6 spread operator.
const clonedSpecialModes = [...specialModes];
let newSpecialModes = clonedSpecialModes.map((mode) => {
// rest of your logic here
})
I need to increase or decrease state value in catalog > spec > units, if I click on increase button the number in units should increase by one and if I click on decrease button it should decrease by one, I'd tried by setting state in the render, but it didn't work and I think this is not a good practice. How can I create a function to setState of units without declaring it inside the render method?
Here is an example of my code:
export default class Order extends Component {
constructor(props) {
super(props);
this.state = {
catalog: [
{
photo: 'https://via.placeholder.com/400x400',
title: 'My title',
description: 'Bla bla bla...',
spec: { size: 'FAM', units: 1, price: 999999, id: 'CMB0', selectedIndicator: '', isSelected: false, name: 'A simple name' },
isCombo: true
},
],
}
}
}
render(){
return(
{this.state.catalog.map((item, index) => {
<div key={index}>
<strong>{item.title}</strong>
<span>{item.spec.units}</span>
<button onClick={() => item.spec.units + 1}>increase</button>
<button onClick={() => item.spec.units - 1}>decrease</button>
</div>})
}
)
}
Try this
increase = title => {
const newCatalogState = this.state.catalog.map(item => {
if (item.title === title) {
return {
...item,
spec: {
...item.spec,
units: item.spec.units + 1
}
};
}
return item;
});
this.setState({
catalog: newCatalogState
});
};
decrease = title => {
const newCatalogState = this.state.catalog.map(item => {
if (item.title === title) {
return {
...item,
spec: {
...item.spec,
units: item.spec.units - 1
}
};
}
return item;
});
this.setState({
catalog: newCatalogState
});
};
<button onClick={() => this.increase(item.title)}>increase</button>
<button onClick={() => this.decrease(item.title)}>decrease</button>
you can check here codesandbox hope it helps
Try this:
export default class Order extends Component {
constructor(props) {
super(props);
this.state = {
catalog: [
{
photo: 'https://via.placeholder.com/400x400',
title: 'My title',
description: 'Bla bla bla...',
spec: { size: 'FAM', units: 1, price: 999999, id: 'CMB0', selectedIndicator: '', isSelected: false, name: 'A simple name' },
isCombo: true
},
],
}
}
}
const updateUnits = (index, value) => {
const { catalog } = this.state
catalog[index].spec.units += value
this.setState({catalog})
}
render(){
return(
{ this.state.catalog.map((item, index) => {
<div key={index}>
<strong>{item.title}</strong>
<span>{item.spec.units}</span>
<button onClick={() => this.updateUnits(index, 1)}>increase</button>
<button onClick={() => this.updateUnits(index, -1)}>decrease</button>
</div>})
}
)
}
I'm creating a custom dropdown list, where a button (Trigger) plays the role as the dropdown's trigger. Here I'm trying to change the dropdown title into the name of any selected options. To do this, I store the new selected value in selectedOption and use them to replace the title. However receive an error of: Cannot read property 'label' of undefined.
How to resolve and make the dropdown works?
Really appreciate any enlightenment! Thank you
const Dropdown = props => {
const { onChange, label, disabled } = props;
const options = [
{ value: '0', label: 'All Flavour' },
{ value: '1', label: 'Strawberry' },
{ value: '2', label: 'Rum Raisin' },
{ value: '3', label: 'Hazelnut' },
{ value: '4', label: 'Chocochip' },
{ value: '5', label: 'Coffee' },
];
const [open, setOpen] = useState(false);
const handleTriggerClick = useCallback(() => setOpen(prev => !prev), []);
const handleChange = useCallback(
newValue => {
if (!disabled) {
onChange(newValue);
setOpen(false);
}
},
[onChange]
);
const selectedOption = options.find(option => option.label === label);
const displayMenu = open && !disabled;
return (
<>
<Container>
<OutletIcon />
<Trigger
disabled={disabled}
title={selectedOption.label || ''}
onClick={handleTriggerClick}
>
<TriggerText>{selectedOption.label || ''}</TriggerText>
<SortIcon />
</Trigger>
<DropdownMenu isDisplayed={displayMenu}>
{options.map(option => {
const isSelected = option.label === label;
const otherProps = {};
if (!isSelected) {
otherProps.onClick = () => handleChange(option.label);
}
return (
<DropdownMenuItem
key={option.value}
title={option.label}
selected={isSelected}
{...otherProps}
>
<DropdownMenuItemText onClick={handleTriggerClick}>
{option.label}
</DropdownMenuItemText>
<GreenCheckIcon />
</DropdownMenuItem>
);
})}
</DropdownMenu>
</Container>
</>
);
};
Hereby is the props declaration
Dropdown.defaultProps = {
disabled: false,
onChange: () => {},
label: '',
};
Dropdown.propTypes = {
disabled: PropTypes.bool,
onChange: PropTypes.func,
label: PropTypes.string,
};