I am working my way through the ReactSortable examples (react-sortablejs), but I cant figure out how to prevent adding duplicate objects when cloning from one list to another:
const [state, setState] = useState([
{ id: 0, name: "shrek", ...defs },
{ id: 1, name: "fiona", ...defs }
]);
const [state2, setState2] = useState([
]);
...
<div>
<ReactSortable
group={{ name: "cloning-group-name", pull: "clone" , put: false }}
animation={200}
delayOnTouchStart={true}
delay={2}
list={state}
setList={setState}
clone={item => ({ ...item })}
sort= {false}
>
{state.map(item => (
<div key={item.id}>{item.name}</div>
))}
</ReactSortable>
<ReactSortable
// here they are!
group={{ name: "cloning-group-name"}}
delayOnTouchStart={true}
delay={2}
list={state2}
setList={setState2}
onAdd = { }
>
{state2.map(item => (
<div key={item.id}>{item.name}</div>
))}
</ReactSortable>
I've been trying to use the onAdd, but I just can't figure out the logic. Thanks in advance.
Related
This is my checkbox components for multi selection.
const MultiselectCheckbox = ({ options, onChange, limitedCount }) => {
const [data, setData] = React.useState(options);
const toggle = index => {
const newData = [...data];
newData.splice(index, 1, {
label: data[index].label,
checked: !data[index].checked
});
setData(newData);
onChange(newData.filter(x => x.checked));
};
return (
<>
{data.map((item, index) => (
<label key={item.label}>
<input
readOnly
type="checkbox"
checked={item.checked || false}
onClick={() => toggle(index)}
/>
{item.label}
</label>
))}
</>
);
};
const options = [{ label: 'Item One' }, { label: 'Item Two' }];
ReactDOM.render(
<MultiselectCheckbox
options={options}
onChange={data => {
console.log(data);
}}
/>,
document.getElementById('root')
);
I want to limit the items I can choose by putting limitedCount in my code.
props for example
limitedSelectCount = 1
Only one check box can be selected
limitedSelectCount = n
Multiple n check boxes available
You can add a condition inside the toggle function.
const handleOptionSelection = (optionKey: string, isChecked: boolean) => {
let selectedOptionKeysCopy = [...selectedOptionKeys];
if (isChecked && selectedOptionKeysCopy.length <= maxSelections) {
selectedOptionKeysCopy = unique([...selectedOptionKeysCopy, optionKey]);
} else {
selectedOptionKeysCopy = selectedOptionKeysCopy.filter(
(selectedOptionKey) => selectedOptionKey !== optionKey
);
}
onChangeSelected(selectedOptionKeysCopy);
};
Here is a sample for reference.
its very easy just put a condition in your toggle function to check if
the length of data array is not greater or equal to the limitedCount. Also
check if the limitedCount prop has been passed to the component or
not.
UPDATE
Also just you need to see if the user has checked the option or unchecked the option, so pass a check into toggle function.
const MultiselectCheckbox = ({ options, onChange, limitedCount }) => {
const [data, setData] = React.useState(options);
const toggle = (check,value) => {
// add below line to code
if(limitedCount && data.length>=limitedCount && check) return;
const newData = [...data];
newData.splice(index, 1, {
label: data[index].label,
checked: !data[index].checked
});
setData(newData);
onChange(newData.filter(x => x.checked));
};
return (
<>
{data.map((item, index) => (
<label key={item.label}>
<input
readOnly
type="checkbox"
checked={item.checked || false}
onClick={(e) => toggle(e.target.checked,index)}
/>
{item.label}
</label>
))}
</>
);
};
const options = [{ label: 'Item One' }, { label: 'Item Two' }];
ReactDOM.render(
<MultiselectCheckbox
options={options}
onChange={data => {
console.log(data);
}}
/>,
document.getElementById('root')
);
I am trying to render some array/object values in a Modal with React. The problem is when I trigger the modal, values from all modals are rendered.
I searched and learned that it's because I need to store the value ID in state and then tell react to trigger that specific ID not all. But I don't know how to apply it.
Here's a basic example:
const items = [{id: 1, name: 'John', friends: ['Michael', 'Tom']},{id: 2, name:
'Robert', friends: ['Jim', 'Max']}]
const [show, setShow] = useState(false);
<>
{items.map((i) => {
<div key={i.id}>
<h1>{i.name}</h1>
<button onClick={() => setShow(true)}>View Friends</button>
</div>;
})}
{show ? <div>{items.map((i) => i.friends)}</div> : null} **I know this is wrong**
</>
So I was wondering how can I store the ID to let React know that I want that specific item to render and not all.
Save id in state
const [show, setShow] = useState(null);
...
<button onClick={() => setShow(i.id)}>View Friends</button>
...
{show !== null && <div>{ items.find(i => i.id === show).friends }</div>}
You have to save the selection id in the component state.
const items = [{id: 1, name: 'John', friends: ['Michael', 'Tom']},{id: 2, name:
'Robert', friends: ['Jim', 'Max']}]
const [show, setShow] = useState(false);
const [id, setId] = useState(null);
<>
{items.map((i) => {
<div key={i.id}>
<h1>{i.name}</h1>
// set the user's selection into another local state
<button onClick={() => { setShow(true); setId(i.id)}}>View Friends</button>
</div>;
})}
// filter the current records for the selected id
{show && <div>{items.filter(obj => obj.id === id)?[0].friends}</div>}
</>
First store the selected record in state like
const [selectedRecord,setSelectedRecord] = useState(null);
now when you click on button update selected record like this
<button onClick={() => { setShow(true); setSelectedRecord(i)}}>View Friends</button>
and use it to render inside modal like this
{show ? <div>name :{selectedRecord.name} Friends :{ selected.friends.join(',')}</div> : null}
Finally your code should be like this
const items = [{id: 1, name: 'John', friends: ['Michael', 'Tom']},{id: 2, name:
'Robert', friends: ['Jim', 'Max']}]
const [show, setShow] = useState(false);
const [selectedRecord,setSelectedRecord] = useState(null);
<>
{
items.map((i) => {
<div key={i.id}>
<h1>{i.name}</h1>
// set the user's selection into another local state
<button onClick={() => { setShow(true); setSelectedRecord(i)}}>View Friends</button>
</div>;
})}
{show ? <div>name :{selectedRecord.name} Friends :{ selected.friends.join(',')}</div> : null}
</>
I'm displaying different cars and a button to add or remove the selections the user has made. How do I get the buttons to change state individually? As of now, it changes the state of all the buttons to one value.
const cars = [
{ name: "Benz", selected: false },
{ name: "Jeep", selected: false },
{ name: "BMW", selected: false }
];
export default function App() {
const isJeepSelected = true;
const isBenzSelected = true;
return (
<div className="App">
{cars.map((values, index) => (
<div key={index}>
<Item
isBenzSelected={isBenzSelected}
isJeepSelected={isJeepSelected}
{...values}
/>
</div>
))}
</div>
);
}
const Item = ({ name, isBenzSelected, isJeepSelected }) => {
const [toggle, setToggle] = useState(false);
const handleChange = () => {
setToggle(!toggle);
};
if (isBenzSelected) {
cars.find((val) => val.name === "Benz").selected = true;
}
console.log("cars --> ", cars);
console.log("isBenzSelected ", isBenzSelected);
console.log("isJeepSelected ", isJeepSelected);
return (
<>
<span>{name}</span>
<span>
<button onClick={handleChange}>
{!toggle && !isBenzSelected ? "Add" : "Remove"}
</button>
</span>
</>
);
};
I created a working example using Code Sandbox. Could anyone please help?
There's too much hardcoding here. What if you had 300 cars? You'd have to write 300 boolean useState hook calls, and it still wouldn't be dynamic if you had an arbitrary API payload (the usual case).
Try to think about how to generalize your logic rather than hardcoding values like "Benz" and Jeep. Those concepts are too closely-tied to the arbitrary data contents.
cars seems like it should be state since you're mutating it from React.
Here's an alternate approach:
const App = () => {
const [cars, setCars] = React.useState([
{name: "Benz", selected: false},
{name: "Jeep", selected: false},
{name: "BMW", selected: false},
]);
const handleSelect = i => {
setCars(prevCars => prevCars.map((e, j) =>
({...e, selected: i === j ? !e.selected : e.selected})
));
};
return (
<div className="App">
{cars.map((e, i) => (
<div key={e.name}>
<Item {...e} handleSelect={() => handleSelect(i)} />
</div>
))}
</div>
);
};
const Item = ({name, selected, handleSelect}) => (
<React.Fragment>
<span>{name}</span>
<span>
<button onClick={handleSelect}>
{selected ? "Remove" : "Add"}
</button>
</span>
</React.Fragment>
);
ReactDOM.createRoot(document.querySelector("#app"))
.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
Consider generating unique ids for elements rather than using indices or assuming the name is unique. crypto.randomUUID() is a handy way to do this.
I have a data set where each object has a heading and an items array with multiple values. I need to be able to filter on the items while maintaining their heading. The filter component below works without the headings. The data output I want is something like:
If I filter on 'ap...'
Output:
fruits
apple
const FilterList = () => {
const [searchTerm, setSearchTerm] = useState("")
const [searchResults, setSearchResults] = useState([])
const data = [
{
heading: 'fruits',
items: [
{ item: 'apple' },
{ item: 'orange' },
{ item: 'peach' }
]
},
{
heading: 'veggies'
items: [
{ item: 'carrot' },
{ item: 'broccoli' },
{ item: 'spinach' },
]
}
]
const handleChange = e => {
setSearchTerm(e.target.value)
}
useEffect(() => {
let results = []
if (data && Object.prototype.hasOwnProperty.call(data[0], "heading")) {
data.forEach(item => {
results = item.values.filter(value =>
value.value.toLowerCase().includes(searchTerm)
)
})
setSearchResults(results)
}
}, [searchTerm])
return (
<div>
<input
type="text"
placeholder={'placeholder}
value={searchTerm}
onChange={handleChange}
/>
<Typography>
<strong>{heading}</strong>
</Typography>
<List>
{searchResults.map((value, i) => (
<ListItem key={i}>
{value.value}
</ListItem>
))}
</List>
</div>
)
}
Modify the useEffect :
useEffect(() => {
console.log("changed");
const newSearchResults = data.map(value => ({
heading: value.heading,
items: value.items.filter(item => item.item.includes(searchTerm))
}));
setSearchResults(newSearchResults);
}, [searchTerm]);
Change your return to:
return (
<div>
<input
type="text"
placeholder={"placeholder"}
value={searchTerm}
onChange={handleChange}
/>
<div>
{searchResults.map(value => {
if (value.items.length !== 0) {
return (
<>
<Typography>
<strong>{value.heading}</strong>
</Typography>
<List>
{value.items.map((item, i) => (
<ListItem key={i}>{item.item}</ListItem>
))}
</List>
</>
);
}
})}
</div>
</div>
const data = [{heading:'fruits',items:[{item:'apple'},{item:'orange'},{item:'peach'}]},{heading:'veggies',items:[{item:'carrot'},{item:'broccoli'},{item:'spinach'},{item: 'peach'}]}]
const filterData = (data, val) => {
const result = []
data.forEach(obj => {
let r = obj.items.filter(item => item.item.includes(val))
if(r.length > 0) {
result.push({
...obj,
items: r
})
}
})
return result
}
console.log(filterData(data, "ap"))
console.log(filterData(data, "pe"))
Hope this helps.
Created a sandbox: https://codesandbox.io/s/strange-robinson-tvl7d?file=/src/App.js
Explanation
In essence, two changes are required:
need to restructure your searchResults to have the same structure as data, with heading and items. Instead of just pushing the items found, we specify the corresponding heading also, in the searchResults array
// new filtering function
useEffect(() => {
const results = [];
data.forEach(cate => {
const { heading, items } = cate;
// remains as is
const filterdItems = items.filter(item =>
item.item.toLowerCase().includes(searchTerm)
);
// only if filtered items are found
// add BOTH category & filtered items to results
if (filterdItems.length) {
results.push({
heading,
items: filterdItems
});
}
});
setSearchResults(results);
}, [searchTerm]);
And then, update the render block to loop through the categories array and then the items inside each category
// updated render
{
searchResults.map((resultCate, i) => {
return (
<div>
<strong>{resultCate.heading}</strong>
{resultCate.items.map((item, idx) => (
<div key={idx}>{item.item}</div>
))}
</div>
);
})}
I want to filter over an array using react hooks. It should be a fairly straight forward task, but I am assuming it is something to do with hooks being updated asynchronously, although this could be wrong.
I am fairly stumped, but have included a code sandbox and my code below:
const teams_data = [
"tottenham",
"arsenal",
"man utd",
"liverpool",
"chelsea",
"west ham"
];
function App() {
const [teams, setTeams] = React.useState(teams_data);
const [search, setSearch] = React.useState("");
return (
<div className="App">
<input
onChange={e => {
const test = teams.filter(team => {
return team.toLowerCase().includes(e.target.value.toLowerCase());
});
console.log("test: ", test);
// uncomment line below and teams is logged as I want
setTeams(test);
setSearch(e.target.value);
}}
type="text"
value={search}
/>
{teams.map(team => (
<p>{team}</p>
))}
</div>
);
}
You need to filter the original data :
const test = teams_data.filter(team => {
return team.toLowerCase().includes(e.target.value.toLowerCase());
});
https://codesandbox.io/s/thirsty-austin-uqx8k
You just need to add another state for search results
const [data , setData] = useState(teams);
const [query, setQuery] = useState('')
const[res , setRes] = useState([]);
return (
<div className="App container">
<form onSubmit = {(e) => e.preventDefault()}>
<input type = "search" className = "srh" placeholder = "search about..."
onChange = {(e) => {
const test = data.filter(team => {
return (
team.toLowerCase().includes(e.target.value.toLowerCase())
)
})
setRes(test)
if(e.target.value === '') setRes([])
}}
/>
</form>
<div>
{
res.map((item , i) => (
<p key = {i}>{item}</p>
))
}
</div>
</div>
);
I've made custom hook.
It receives the array as a first param
the search variable as a second
and the property you want to filter by
I hope it's helpfull
export function useSearch(array: any[], search: string, field: string) {
const filteredArray = array.filter((entry) => {
if (search === "") return entry;
else if (
entry[field].toLocaleLowerCase().includes(search.toLocaleLowerCase())
)
return entry;
});
return {
filteredArray
};
}
Them apply the filtered array to your map function
import { useSearch } from "./useSearch";
import { useState } from "react";
const array = [
{
id: 1,
name: "Humberto Guenzo Yoshimoto"
},
{
id: 2,
name: "Diego Braga"
},
{
id: 3,
name: "Hudson Teixeira"
},
{
id: 4,
name: "Matheus Doimo"
}
];
type FilteredArrayTypes = {
id: number;
name: string;
};
export default function App() {
const [searchByFullName, setSearchByFullName] = useState("");
const { filteredArray } = useSearch(array, searchByFullName, "name");
return (
<div className="App">
<h1>Search list</h1>
<input
onChange={(e) => setSearchByFullName(e.target.value)}
type="text"
value={searchByFullName}
placeholder="search"
/>
{filteredArray.map((entry: FilteredArrayTypes) => {
return (
<ul>
<li>{entry.name}</li>
</ul>
);
})}
</div>
);
}
Here goes a sandbox with the code: here