Toggle the button name on click in ReactJS - javascript

I have an array of objects. So for every object, which has subItems, I'm trying to add a button to it. On click of the particular button, the name of the particular button should toggle between 'Expand' and 'Hide'. I'm displaying them using map.
export default function App() {
const [button,setButton] = useState([
{pin: 'Expand', id: 0},
{pin: 'Expand', id: 1},
{pin: 'Expand', id: 2},
]);
const dataObj = [
{
title: 'Home'
},
{
title: 'Service',
subItems: ['cooking','sleeping']
},
{
title: 'Contact',
subItems: ['phone','mobile']
}
];
const expandFunc = (ind) => {
// toggle logic here
}
return (
<div className="App">
{
dataObj.map((arr,ind) => (
<div>
<span>{arr.title}:</span>
{
// console.log(ind)
arr.subItems &&
<button onClick={() => expandFunc(ind)}>{button[ind].pin}</button>
}
</div>
))
}
</div>
);
}
This is how the output looks -
If I click on service button, then the button name should toggle between 'expand' and 'hide'. Could someone help me with this?

You need to update your state by determining new pin based on current state, try using Array.map:
const expandFunc = (ind) => {
const togglePin = oldPin => oldPin === "Expand" ? "Hide" : "Expand";
const updatedButtons = button.map((btn, index) =>
ind === index ? { ...btn, pin: togglePin(btn.pin) } : btn);
setButton(updatedButtons);
}

You can use something like this, I'll also suggest combining dataObj into button state, and using key while mapping elements in React helps to skip them in the rendering process making your site faster.
export default function App() {
const [button, setButton] = useState([{
expanded: false,
id: 0
},
{
expanded: false,
id: 1
},
{
expanded: false,
id: 2
},
]);
const dataObj = [{
title: 'Home'
},
{
title: 'Service',
subItems: ['cooking', 'sleeping']
},
{
title: 'Contact',
subItems: ['phone', 'mobile']
}
];
const toggleExpandFunc = useCallback((ind) => {
// toggle logic here
setButton([...button.map(btn => btn.id === ind ? { ...btn,
expanded: !btn.expanded
} : btn)]);
}, [button]);
return ( <
div className = "App" > {
dataObj.map((arr, ind) => ( <
div >
<
span > {
arr.title
}: < /span> {
// console.log(ind)
arr.subItems &&
<
button onClick = {
() => toggleExpandFunc(ind)
} > {
button[ind].expanded ? 'Expanded' : 'Hide'
} < /button>
} <
/div>
))
} <
/div>
);
}

You can also need another state like Toggle and expnadFunc can be handled like this;
const expandFunc = (ind) => {
let tmp = [...button];
tmp.map((arr, index) => {
if (index === ind) {
return (arr.pin = arr.pin === 'Toggle' ? 'Expand' : 'Toggle');
}
return arr;
});
setButton(tmp);
};

Related

Why am I not able to post my data with react?

For now, only apply:0 data are displayed by default in my table.I'm trying to send to my API when you click on Apply button, a list of all ids applied and not applied for instance : data:{apply: [3], notApply:[1,2]}.
Hereapply field in my data means the value is equal to 1 --> not displayed in the table; and notApply - i.e equal to 0 --> displaying data.
So when I click on the toggle button under the show column, it should turns to 1 the statusapply (not displaying the row data in my table which is not the case). The Cancel button sends nothing to my API.
Here what I've tried (but not working):
export default function MenuDisplayApi() {
const { menuId } = useParams();
const { match } = JsonData;
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus] = useState([]);
const [applyStatus, setApplyStatus]=useState(false)
useEffect(() => {
axios.post(url,{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const [data, setData]= useState({
notApply:data.notApply,
apply: data.apply
})
function submit(e){
e.preventDefault()
axios.post(url,{
notApply:data.notApply,
apply: data.apply
})
.then(res => {
console.log(res)
})
}
// useEffect(() => {
// const data = {
// notApply:notApply,
// apply: apply
// }
// console.log('check', data)
// }, [])
// function handle(e){
// const newdata={...data}
// newdata[e.target.id]=e.target.value
// setData(newdata)
// console.log(newdata)
// }
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
if (menus.apply === 0) {
setApplyStatus(true)
}
if (menus.apply === 1) {
setApplyStatus(false)
}
return (
<div>
<button type="submit" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
<Table
data={matchData && (applyStatus ? true : i.apply !== 1)}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
// ...other menus
{
"_id": 3,
"name": "Cucumber Soup ",
"description": "Cucumber Soup",
"dishes": [
{
"meat": "N/A",
"vegetables": "cucumber"
}
],
"taste": "Medium",
"comments": "2/4",
"price": "Medium",
"availability": 1,
"trust": 1,
"status": "Not started",
"apply": 0
}
]
Please check my codeSandbox. It works fine on local json files, so you can have a better understanding regarding what I'm trying.
Please see this picture for better understanding :

Set acitve classname to multiple items in react js (map) and remove

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>
);
})
}
);

Better way to access property in React.useState and update it?

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.

Context state being updated unexpectedly - React Typescript

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
})

How to scroll a list item into view using scrollintoview method using reactjs?

i want to move a list item into view using scrollIntoView method using reactjs.
What i am trying to do?
i have an array of objects stored in variable some_arr and i display those values in a dropdown menu. when user presses down key then the dropdown item gets highlighted. also using up arrow key to navigate up in the dropdown menu.
and clicking enter key will select the dropdown item and replaces the value in the input field.
I have implemented code below and it works fine. but when user presses down arrow key and highlighted dropdown menu is not in view i want it to be visible to user.
So to implement that i have used ref (this.dropdown_item_ref) to the dropdown item. however this ref always points to last item in the dropdown menu. meaning consider i have
some_arr = [
{
id:1,
name: somename,
},
{
id: 2,
name: fname,
},
{
id: 3,
name: lname, //ref is always pointing to this item
},
],
so here the ref is always pointing to lname in the dropdown menu.
Below is what i have tried and is not working,
class Dropdownwithinput extends React,PureComponent {
constructor(props) {
super(props);
this.list_item_ref = React.createRef();
this.state = {
input_val: '',
dropdown_values: [],
dropdown_item_selection: 0,
};
}
componentDidMount = () => {
const values = [
{
id:1,
name: somename,
},
{
id: 2,
name: fname,
},
{
id: 3,
name: lname, //ref is always pointing to this item
},
],
this.setState({dropdown_values: values});
}
handle_key_down = (event) => {
if (this.state.dropdown_values > 0) {
if (event.keyCode === 38 && this.state.dropdown_item_selection
> 0) {
this.setState({dropdown_item_selection:
(this.state.dropdown_item_selection - 1) %
this.state.dropdown_values.length});
this.list_item_ref.current.scrollIntoView();
} else if (event.keyCode === 40) {
this.setState({dropdown_item_selection:
(this.state.dropdown_values_selection + 1) %
this.state.dropdown_values.length});
this.list_item_ref.current.scrollIntoView();
}
if (event.keyCode === 13) {
event.preventDefault();
const selected_item =
this.state.dropdown_values[this.state.user_selection];
const text = this.replace(this.state.input_val,
selected_item);
this.setState({
input_val: text,
dropdown_values: [],
});
}
}
replace = (input_val, selected_item) => {
//some function to replace value in input field with the
//selected dropdown item
}
render = () => {
return (
<input
onChange={this.handle_input_change}
onKeyDown={this.handle_key_down}/>
<div>
{this.state.dropdown_values.map((item, index) => (
<div key={index} className={"item" + (index ===
this.state.dropdown_item_selection ? ' highlight'
: '')}>
{item.name}
</div>
))}
</div>
)
};
}
}
Could someone help me fix this. thanks.
I have adapted a bit your code:
import React from "react";
class Example extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
const dropdownValues = Array.from({ length: 100 }, (_, k) => k).reduce(
(acc, curr) => {
return acc.concat([{ id: curr, name: `${curr}.so` }]);
},
[]
);
this.state = {
input_val: "",
dropdownValues,
selectedItem: 0
};
this.listRefs = dropdownValues.reduce((acc, current, index) => {
acc[index] = React.createRef();
return acc;
}, {});
}
componentDidMount() {
window.addEventListener("keydown", this.handleKeyDown);
}
componentWillUnmount() {
window.removeEventListener("keydown", this.handleKeyDown);
}
componentDidUpdate(prevProps, prevState) {
if (prevState.selectedItem !== this.state.selectedItem) {
this.listRefs[this.state.selectedItem].current.scrollIntoView();
}
}
handleKeyDown = event => {
const keyCodes = {
up: 38,
down: 40
};
if (![38, 40].includes(event.keyCode)) {
return;
}
this.setState(prevState => {
const { dropdownValues, selectedItem } = prevState;
let nextSelectedItem;
if (keyCodes.up === event.keyCode) {
nextSelectedItem =
dropdownValues.length - 1 === selectedItem ? 0 : selectedItem + 1;
}
nextSelectedItem =
selectedItem === 0 ? dropdownValues.length - 1 : selectedItem - 1;
return { ...prevState, selectedItem: nextSelectedItem };
});
};
replace = (input_val, selected_item) => {
//some function to replace value in input field with the
//selected dropdown item
};
render() {
return (
<>
<input
onChange={this.handle_input_change}
onKeyDown={this.handle_key_down}
/>
<button
type="button"
onClick={() => this.setState({ selectedItem: 50 })}
>
Focus element 50
</button>
<div ref={this.listRef}>
{this.state.dropdownValues.map((item, index) => (
<div key={index} ref={this.listRefs[index]}>
<div
style={
this.state.selectedItem === index
? { background: "yellow" }
: {}
}
>
{item.name}
</div>
</div>
))}
</div>
</>
);
}
}
export default Example;

Categories