How to make several buttons simultaneously active but with some conditions? - javascript

I have a problem. I want to make buttons section, where user can click buttons to filter some content. When user click on 'all' button, all other should be turn off (change its color to initial, not active) in this moment. Also, user can check multiple buttons.
I can't get how to do this.
Example of JSON:
{
title: 'All',
id: 53,
},
{
title: 'Im a parent',
icon: <Parent />,
id: 0,
},
{
title: 'I live here',
icon: <ILiveHere />,
id: 2,
},
example of code: https://codesandbox.io/s/sleepy-haze-35htx?file=/src/App.js
Its wrong, I know. I tried some solutions, but I guess I can't get how to do it correctly.
With this code I can do active multiple buttons, but I can't get how to make conditions like
if (item.title === 'all){
TURN_OFF_ANY_OTHER_BTNS
}
I guess I should store checked buttons in temporary array to make these operations.
Will be really thankfull for help.

Is this something you would like?
const SocialRole = ({ item, selected, setSelected }) => {
let style =
[...selected].indexOf(item.id) !== -1
? { color: "red" }
: { color: "blue" };
return (
<button
style={style}
onClick={() => {
if (item.id === 53) {
setSelected(null);
} else {
setSelected(item.id);
}
}}
>
{item.icon}
<h1>{item.title}</h1>
</button>
);
};
export default function App() {
// We keep array of selected item ids
const [selected, setSelected] = useState([roles[0]]);
const addOrRemove = (item) => {
const exists = selected.includes(item);
if (exists) {
return selected.filter((c) => {
return c !== item;
});
} else {
return [...selected, item];
}
};
return (
<div>
{roles.map((item, index) => (
<SocialRole
key={index}
item={item}
selected={selected}
setSelected={(id) => {
if (id === null) setSelected([]);
else {
setSelected(addOrRemove(id));
}
}}
/>
))}
</div>
);
}

If I understand your problem, I think this is what you are looking for:
const roles = [
{
title: "All",
id: 53
},
{
title: "I live here",
id: 0
},
{
title: "I live here too",
id: 2
}
];
// button
const SocialRole = ({ item, selected, setSelected }) => {
const isActive = selected === item.title || selected === 'All';
return (
<button
style={isActive ? { color: "red" } : { color: "blue" }}
onClick={() => setSelected(item.title)}
>
{item.icon}
<h1>{item.title}</h1>
</button>
);
};
export default function App() {
const [selected, setSelected] = useState(roles[0].title);
return (
<div>
{roles.map((item, index) => (
<SocialRole
key={index}
item={item}
selected={selected}
setSelected={setSelected}
/>
))}
</div>
);
}
The problem was you were setting a new state into each button, when you should just use the state from the App.

Related

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

REACT-SELECT defaultValue in CustomDropdown not working

I want the default value of my dropdown to be defaultValue={item.taste} (value from match.json) but it's not working... (go to /menu/Menu1 and Pea Soup)
import Select from "react-select";
export default function CustomDropdown({ style, options, defaultValue }) {
return (
<div style={style}>
<Select options={options} defaultValue={defaultValue} />
</div>
);
}
MenuItemDisplay:
export default function MenuItemDisplay() {
const { menuId, itemId } = useParams();
const { match } = JsonData;
const matchData = match.find((el) => el._id_menu === menuId)?._ids ?? [];
const item = matchData.find((el) => el._id === itemId);
const styles = {
select: {
width: "100%",
maxWidth: 150
}
};
const TASTE = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
...
return (
<>
<div className="TextStyle">
{"Taste "}
<CustomDropdown
style={styles.select}
options={TASTE}
defaultValue={item.taste}
//The default value is not working only if it's
//TASTE[0]
/>
</div>
...
</>
);
}
Here the link for the code
As defaultValue you need to pass one of the objects of the TASTE array. You can do this:
<CustomDropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find(t => t.label === item.taste)}
/>

react-beautiful-dnd: Prevent flicker when drag and drop a lists

I'm using this react-beautiful-dnd library to be able to reorder lists. However, even though I'm able to drag and drop and re-order, there is a flicker when I try to reorder lists.
You can see in the video:
enter image description here
Here is my code:
I added sorting to the array by order before mapping.
const Board = () => {
const [currentId, setCurrentId] = useState(null);
const lists = useSelector((state) => state.lists);
const dispatch = useDispatch();
const classes = useStyles();
useEffect(() => {
dispatch(getLists());
}, [currentId, dispatch]);
const onDragEnd = (result) => {
const { destination, source, draggableId, type } = result;
if(!destination) return;
const droppableIdStart = source.droppableId;
const droppableIdEnd = destination.droppableId;
const droppableIndexStart = source.index;
const droppableIndexEnd = destination.index;
const newState = [...lists];
// drag lists
if(type === 'list') {
const dragList = newState.splice(droppableIndexStart, 1);
newState.splice(droppableIndexEnd, 0, ...dragList);
// update order list to be index
newState.forEach((list, index) => {
list.order = index;
dispatch(updateList(list._id , { ...list }));
});
}
return newState;
}
// Arranging lists by order
const newArrange = (a,b) => {
return (a.order - b.order);
}
lists.sort(newArrange);
return (
<>
<DragDropContext onDragEnd={onDragEnd} >
<div>
<h1>Board</h1>
<Droppable droppableId="all-lists" direction="horizontal" type="list">
{ provided => (
<div className={classes.listContainer} {...provided.droppableProps} ref={provided.innerRef} >
{ lists.map((list, index) =>
(user?.result?.googleId === list?.creator || user?.result?._id === list?.creator) ?
<List key={list._id} title={list.title} cards={list.cards} currentId={list._id} index={index} /> :
null
)}
{addListFlag && (
<InputItem
value={listData.title}
btnText={"Add list"}
type={"list"}
placeholder={"Enter a list title..."}
changedHandler={handleChange}
closeHandler={closeHandlerBtn}
addItem={submitHandler}
/>
)}
{!addListFlag && (
<AddBtn btnText={"Add another list"} type={"list"} handleClick={handleAddition} />
)}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
</DragDropContext>
</>
)
}
export default Board;
Sample of data:
{
_id: "6163cdd306d27b",
title: "a",
name: "first one",
order: "0",
cards:[
{
id: "0",
text: "a1",
_id: {_id: "61d0ca2c20d27e"}
},
{
id: "1",
text: "a2",
_id: {_id: "616541ec90630"}
},
{
id: "2",
text: "a3",
_id: {_id: "61606e609"}
}
]
}
Thank :)
It has probably todo with your getLists() call in the useEffect hook.
Is there an async function behind it? Do you get your lists from a server? If so, I suppose that useEffect is firing twice (once on drag end and once when it gets back the data from the backend or whatever getLists is doing), which leads to the flickering.
It would probably help to know what exactly getLists is.

Change active state in a list using useState

Component
Here is an example of code. What I want is to toggle the active state ( true / false) for each individual list item when I clicked it. I don't want to change them all at once.
Any help with this? Thanks in advance.
import React, { useState } from "react";
const App = () => {
const [active, setActive] = useState({});
const items = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' }
];
handleClick = (index) => {
...
}
const list = items.map( (item, index) => {
return(
<li
key={index}
onClick={() => handleClick(index)}
className={active ? "active" : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
}
export default App;
First option - Introducing isActive property for each elements
I would introduce in the items an active property for each item and manipulate them based on the clicked item. Which needs to be added to useState where you can update the isActive property with .map().
Similarly like the following:
const [items, setItems] = useState([
{ name: 'Item 1', isActive: true },
{ name: 'Item 2', isActive: false },
{ name: 'Item 3', isActive: false }
]);
handleClick = (index) => {
setItems(prevItems =>
prevItems.map((e, i) => ({...e, isActive: i === index}))
);
}
const list = items.map( (item, index) => {
return(
<li
key={index}
onClick={() => handleClick(index)}
className={item.isActive ? 'active' : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
Second option - store the index for the clicked active element
Storing the index for the clicked element helps you the identify in .map() which one is the active one. So with a simple check with i === index you can add active class to the <li> element.
You can create a state for index with useState as the following:
const [index, setIndex] = useState(0);
const items = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' }
];
handleClick = (i) => {
setIndex(i);
}
const list = items.map( (item, i) => {
return(
<li
key={i}
onClick={() => handleClick(i)}
className={i === index ? 'active' : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
I hope this helps!
Added comments inline.
const App = () => {
// active is active item, initially '' means, nothing selected.
const [active, setActive] = useState("");
const items = [{ name: "Item 1" }, { name: "Item 2" }, { name: "Item 3" }];
const list = items.map(({ name }) => (
<li
key={name /* avoid using index for key */}
onClick={() => setActive(name)}
className={active === name ? "active" : ""}
>
{name}
</li>
));
// return the Element, not {}
return (
<>
<ul>{list}</ul>
</>
);
};
export default App;

ReactJS: Adding multiple input fields of different types on click

I've created a React app for a school project that can add multiple types of input fields to a view by clicking a button (sort of like Wordpress Gutenberg).
Currently, I can add one of each type of item onto the view. However, if I click the button again, it erases the current text that was added. I'd like the ability to click the button to add as many fields as I'd like on click.
Also, the items are only added into the view in the order they were created meaning, even if I choose photo first and I click headline after, it (headline) will appear at the top of the list above the initial item.
I've had a look at these solutions (which were pretty good) but they didn't provide what I need.
Dynamically adding Input form field issue reactjs
and "update delete list elements using unique key": https://www.youtube.com/watch?v=tJYBMSuOX3s
which was closer to what I needed to do.
Apologies in advance for the length of the code,(there are two other related components for text input and an editform). I'm sure there is a much more simple way to do this. I haven't been able to find an npm package or solution to this specific problem online and am open to a simpler solution.
Edit.jsx
export default class Edit extends React.Component {
state = {
texts: {
hl: '',
shl: '',
txt: '',
photo: []
},
coms: {
hl: false,
shl: false,
txt: false,
photo: null
},
labels: {
// Replace with icons
hl: 'Headline',
shl: 'Sub',
txt: 'Text Area',
photo: 'Photo'
},
selectedItem: '',
}
componentDidMount() {
const saveData = localStorage.getItem('saveData') === 'true';
const user = saveData ? localStorage.getItem('user') : '';
this.setState({ user, saveData });
}
createPage = async () => {
await this.props.postPage(this.state.texts)
}
// add options
addOptions = (item) => {
const { coms } = this.state
coms[item] = !coms[item]
this.setState({ coms: coms })
}
// ADD TEXT
addTxt = () => {
this.setState({ texts: [...this.state.texts, ""] })
}
enableAllButtons = () => {
this.setState({ selectedItem: '' })
}
handleChange = (e, index) => {
this.state.texts[index] = e.target.value
//set the changed state.
this.setState({ texts: this.state.texts })
}
setDisable = (selectedItem) => {
this.setState({ selectedItem })
}
handleRemove = () => {
// this.state.texts.splice(index, 1)
this.setState({ texts: this.state.texts })
}
handleSubmit = (e) => {
console.log(this.state, 'all text')
}
handleChange = (e, item) => {
let { texts } = this.state
texts[item] = e.target.value
//set the changed state.
this.setState({ texts })
console.log(texts)
}
render() {
const { coms, labels, selectedItem, texts } = this.state
let buttons = Object.keys(coms)
let showItems = Object.keys(coms).filter(key => coms[key] === true)
return (
<div>
<InnerHeader />
{/* Make a route for edit here */}
<Route path='/edit/form' render={() => (
<EditForm
texts={texts}
coms={coms}
labels={labels}
addOptions={this.addOptions}
setDisable={this.setDisable}
selectedItem={selectedItem}
showItems={showItems}
handleChange={this.handleChange}
enableAllButtons={this.enableAllButtons}
/>
)} />
{/* Make route for preview */}
<Route path='/edit/preview' render={(props) => (
<Preview
{...props}
createPage={this.createPage}
/>
)}
/>
</div>
)
}
}
AddText.jsx:
export default class AddText extends Component {
state = {
}
// ADD TEXT
addTxt(item) {
const {
addOptions } = this.props
addOptions(item)
}
render() {
const { coms, labels } = this.props
const { selectedItem } = this.props
let buttons = Object.keys(coms)
console.log('here', selectedItem)
return (
<div>
<Card>
<Card.Body>
{
buttons.map((item, index) => <button
value={(selectedItem === "") ? false : (selectedItem === item) ? false : true} key={index} onClick={() => this.addTxt(item)}>
{labels[item]}
</button>
)
}
</Card.Body>
</Card>
</div>
)
}
}
EditForm.jsx
export default function EditForm(props) {
return (
<div>
<div className='some-page-wrapper-sm'>
<div className="dash-card-sm">
<button><Link to={{
pathname: '/edit/preview',
item: props.texts
}}>preview</Link></button>
<br />
<br />
<AddText
coms={props.coms}
labels={props.labels}
addOptions={props.addOptions}
setDisable={props.setDisable}
selectedItem={props.selectedItem}
/>
<div>
{
props.showItems.map((item, index) => {
return (
<InputFieldComponent
// setDisable={props.setDisable}
onChangeText={(e) => props.handleChange(e, item)}
enableAllButtons={props.enableAllButtons}
key={index}
item={item}
labels={props.labels}
texts={props.texts}
/>
)
})
}
</div>
</div>
</div>
</div>
)
}
InputFieldComponent.jsx
export default class InputFieldComponent extends React.Component {
setWrapperRef = (node) => {
this.wrapperRef = node;
}
render() {
const { labels, item, onChangeText, texts } = this.props
return (
<div>
<textarea
className="txt-box"
ref={this.setWrapperRef}
onChange={onChangeText}
placeholder={labels[item]}
value={texts[item]} />
</div>
)
}
}

Categories