I'm working on creating secret santa app for my christmas party.
For now it works, but I need to add some conditions.
function App() {
var names = ["John", "Martha", "Adam", "Jane", "Michael"];
const shuffle = (arr: string[]) => {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
};
const randomNames = shuffle(names);
const matches = randomNames.map((name, index) => {
return {
santa: name,
receiver: randomNames[index + 1] || randomNames[0],
};
});
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>Secret santa game</p>
<select>
<option>Select your name...</option>
{names.map((name) => (
<option> {name}</option>
))}
</select>
<div>
{matches.map((match) => {
return (
<div>
{match.receiver},{match.santa}
<br />
</div>
);
})}
</div>
</header>
</div>
);
}
export default App;
John and Martha are couple so they will buy themselves presents outside of the party anyway, so if one of them is receiver and another is santa, I want to generate results again so they'll be assigned to someone else.
I'm not sure how I can accomplish this.
You could define your relationships like this:
const couples = { John: "Martha" };
then have a function that verifies that your conditions are met:
const conditions_check = (santa: string, receiver: string) => {
if (couples[santa] === receiver) {
return false;
} else if (couples[receiver] === santa) {
return false;
}
return true;
};
and then finally you can check if your conditions are met while generating your matches and shuffle your pool of names again if needed:
let matches = randomNames.map((name, index) => {
let receiver = randomNames[(index + 1) % randomNames.length];
for (let i = 0; i < 25; i++) {
if (conditions_check(name, receiver)) {
break;
}
randomNames = shuffle(names);
}
return {
santa: name,
receiver: receiver
};
});
You can check out the whole implementation in this playground.
test("remove list when no entries are present", () => {
render(<ListContainer />);
seedEntries();
expect(screen.getByRole("list")).toBeInTheDocument;
screen.getAllByRole("button", { name: /\-/i }).forEach((entry) => {
userEvent.click(entry)
});
expect(screen.queryByRole("list")).toBeNull();
});
Clicking the button calls the following function:
const handleDeleteEntry = (id) => {
if (entries.length === 10) {
setLimitError(false);
}
let filteredEntries = entries.filter((entry) => entry.id !== id);
setEntries(filteredEntries);
};
and my render method looks like this:
{entries.length > 0 ? (
<ul>
{entries.map((entry, index) => {
return (
<li
key={index}
>
<div>
{entry.name}
</div>
<div>
{entry.address}
</div>
<div>
{entry.phoneNumber}
</div>
<button
type="button"
onClick={() => handleDeleteEntry(entry.id)}
>
-
</button>
</li>
);
})}
</ul>
) : null}
const seedEntries = () => {
for (let i = 0; i < 10; i++) {
userEvent.type(screen.getByTestId("input-name"), "testName" + i);
userEvent.type(screen.getByTestId("input-address"), "testAddress" + i);
userEvent.type(screen.getByTestId("input-phone"), "012345678" + i);
userEvent.click(screen.getByRole("button", { name: /\+/i }));
}
};
Is it possible to have my test run so that each of the deletion buttons are clicked reliably? Currently it appears to be missing every 2nd button (I think due to it being too quick, but I've tried playing around with settimeouts and async awaits to no success) my user-event is on v13.5
I am trying to clear checkbox array in fields array from the redux form. How can I do it?
I'm using redux form FieldArray in the format of members:[filterOption:{}, checkBoxOption:{}]. checkBoxOption value depends o filterOption dropdown. Whenever the user selects an option from filterOption in result they get a list of checkbox from which they have to select from the list of checkBoxOption.
Let's say if a user has selected a value from filterOption and checkBoxOption and now they change the value of filterOption in result they will get a new list of an array for checkBoxOption. The values are getting replaced by the new one but they are not getting uncheck.
I am able to clear checkbox array in values array by using fields.get(event).checkBoxOption = {} but unable to find the solution on how to empty fields array.
Can anyone help me out with this?
<ul className="list-group">
{
fields.map((member, index) => (
<li className="list-group filter-select-box" key={index}>
<SelectBox
name={`${member}.filterOption`}
label="Metadata Attribute"
options={attributes.map(attribute => ({
value: attribute.name,
label: attribute.name,
disabled: selectedAttributes.filter(value => value.name === attribute.name).length > 0,
}))}
isChange
handleSelectChange={opt => handleOptionChange(index, opt.value)}
/>
{checkStatus(index) && (
<div className="select-checkbox-option form-group">
{
getCheckboxList(index).map((checkboxItem, x) => (
<CheckBox
key={x}
type="checkbox"
name={`${member}.checkBoxOption.${checkboxItem}`}
label={checkboxItem}
value={`${member}.checkBoxOption.${checkboxItem}`}
id={checkboxItem}
/>
))
}
</div>
)}
</li>
))
}
<li className="list-group filter-select-box">
<button className="add-filter" type="button" onClick={() => fields.push({})}>
<img src={filterPlus} alt="" className="filterPlus" />
Add Filter
</button>
{touched && error && <span>{error}</span>}
</li>
</ul>
the function which is getting checkbox value
const handleOptionChange = (event, nameAttribute) => {
const value = {
index: event,
status: true,
name: nameAttribute,
};
let selectedAttributesStatus = false;
for (let i = 0; i < selectedAttributes.length; i += 1) {
if (value.index === selectedAttributes[i].index) {
selectedAttributes[i].name = value.name;
selectedAttributesStatus = true;
}
}
if (!selectedAttributes.length || !selectedAttributesStatus) {
setSelectedAttributes([...selectedAttributes, value]);
}
setShowOptions([...showOption, value]);
getCategoricalVar(extractorId, nameAttribute)
.then((resp) => {
const newAttributeValue = {
index: event,
value: resp,
};
fields.get(event).checkBoxOption = {};
setSelectedIndex(false);
console.log('fields.get(event).checkBoxOption: ', fields.get(event).checkBoxOption);
let attributeValuesStatus = false;
for (let i = 0; i < attributeValues.length; i += 1) {
if (newAttributeValue.index === attributeValues[i].index) {
attributeValues[i].value = newAttributeValue.value;
attributeValuesStatus = true;
}
}
if (!attributeValues.length || !attributeValuesStatus) {
setAttributeValues([...attributeValues, newAttributeValue]);
}
})
.catch(printError);
};
Function which is setting the value on checkbox
const getCheckboxList = (index) => {
for (let i = 0; i < attributeValues.length; i += 1) {
if (attributeValues[i].index === index) {
return attributeValues[i].value;
}
}
return [];
};
I have an array of HTML elements that I want to render on a page, but depending on the element I'd like to adjust how they get wrapped.
const sections = [
{
id: 'top',
},
{
id: 'left',
},
{
id: 'right',
},
{
id: 'bottom',
}
]
const Element = (props) => {
return <div id={props.id}>hello</div>
}
const ArticleRightRail = (props) =>
<div>
<header>
</header>
<article>
{sections.map((section, i) => <Element key={i} {...section} >hello!</Element> )}
</article>
</div>
In the example above I want any id which is not top or bottom to be rendered within <article>, and anything that is top or bottom to be rendered within <header> tags. What is the best way of handling a situation like this with React?
Use ES6 array filter method to filter sections array as below:
const listForHeader = sections.filter((section, i) => {
return section === "top" || section === "bottom";
});
const listForArticle = sections.filter((section, i) => {
return section != "top" || section != "bottom";
})
Then use above 2 lists in HEADER and ARTICLE tags respectively using array map method.
check this
const listHeader = sections.filter((section, i) => {
return section === "top" || section === "bottom";
});
const listArticle = sections.filter((section, i) => {
return section != "top" || section != "bottom";
})
#JamesIves, the react code is:
const listForHeader = sections.filter((section, i) => {
return section === "top" || section === "bottom";
});
const listForArticle = sections.filter((section, i) => {
return section != "top" || section != "bottom";
})
const ArticleRightRail = (props) =>
<div>
<header>
{listForHeader.map((section, i) => <Element key={i} {...section} >hello!</Element> )}
</header>
<article>
{listForArticle.map((section, i) => <Element key={i} {...section} >hello!</Element> )}
</article>
</div>
This question already has answers here:
How to render react components by using map and join?
(18 answers)
Closed 10 months ago.
I'd like to inject a separator between each element of an array using an array.join-like syntax.
Something like the following:
render() {
let myArray = [1,2,3];
return (<div>
{myArray.map(item => <div>{item}</div>).join(<div>|</div>)}
</div>);
}
I've got it working using a lodash.transform approach, but it feels ugly, I'd like to just do .join(<some-jsx>) and have it insert a separator in between each item.
You can also use reduce to insert the separator between every element of the array:
render() {
let myArray = [1,2,3];
return (
<div>
{
myArray
.map(item => <div>{item}</div>)
.reduce((acc, x) => acc === null ? [x] : [acc, ' | ', x], null)
}
</div>
);
}
or using fragments:
render() {
let myArray = [1,2,3];
return (
<div>
{
myArray
.map(item => <div>{item}</div>)
.reduce((acc, x) => acc === null ? x : <>{acc} | {x}</>, null)
}
</div>
);
}
You can also do it by combining .reduce and React fragments.
function jsxJoin (array, str) {
return array.length > 0
? array.reduce((result, item) => <>{result}{str}{item}</>)
: null;
}
function jsxJoin (array, str) {
return array.length > 0
? array.reduce((result, item) => <React.Fragment>{result}{str}{item}</React.Fragment>)
: null;
}
const element = jsxJoin([
<strong>hello</strong>,
<em>world</em>
], <span> </span>);
ReactDOM.render(element, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Trying to .join with React Elements is probably not going out pan out for you. This would produce the result you describe needing.
render() {
let myArray = [1,2,3];
return (
<div>
{myArray.map((item, i, arr) => {
let divider = i<arr.length-1 && <div>|</div>;
return (
<span key={i}>
<div>{item}</div>
{divider}
</span>
)
})}
</div>
);
}
Using Array.map and React.Fragment you can join each array item with any JSX element you want. In the example below I'm using a <br /> tag but this can be substituted for whatever element you want.
const lines = ['line 1', 'line 2', 'line 3'];
lines.map((l, i) => (
<React.Fragment key={i}>{l}{i < (lines.length - 1) ? <br /> : ''}</React.Fragment>
));
The output would be
line 1 <br />
line 2 <br />
line 3
Btw. you can also supply functions to react. My approach would be .forEach pairs of push(value); push(glue);, and afterwards pop() the trailing glue...
function() {
joinLike = [];
originData.forEach(function(v,i) {
joinLike.push(v);
joinLike.push(<br>);
})
joinLike.pop();
return joinLike;
}
To produce the requested solution, in React join doesn't seem to work so with map and reduce you can do it like this:
render() {
let myArray = [1, 2, 3];
return (
<div>
{myArray
.map(item => <div>{item}</div>)
.reduce((result, item) => [result, <div>|</div>, item])}
</div>
);
}
You could also try flatMap(). Browser support is coming, but until then you could use a polyfill. Only downside is you'd have to lose the last element.
Eg.
{myArray.flatMap(item => [<div>{item}</div>, <div>|</div>]).slice(0, -1)}
Or
{myArray.flatMap((item, i) => [
<div>{item}</div>,
i < myArray.length - 1 ? <div>|</div> : null
])
You can use a function like this one
function componentsJoin(components, separator) {
return components.reduce(
(acc, curr) => (acc.length ? [...acc, separator, curr] : [curr]),
[]
);
}
Or can use a package, found this one
https://www.npmjs.com/package/react-join
If you are using TypeScript, you can simply copy this file and use jsxJoin:
import { Fragment } from "react";
/**
* Join together a set of JSX elements with a separator.
*
* #see https://stackoverflow.com/q/33577448/5506547
*/
function jsxJoin(components: JSX.Element[], separator: any) {
// Just to be sure, remove falsy values so we can add conditionals to the components array
const filtered = components.filter(Boolean);
return filtered.length === 0
? null
: (filtered.reduce(
(acc, curr, i) =>
acc.length
? [
...acc,
// Wrap the separator in a fragment to avoid `missing key` issues
<Fragment key={i}>{separator}</Fragment>,
curr
]
: [curr],
[]
) as JSX.Element[]);
}
export { jsxJoin };
// typescript
export function joinArray<T, S>(array: Array<T>, separator: S): Array<T | S> {
return array.reduce<(T | S)[]>((p, c, idx) => {
if (idx === 0) return [c];
else return [...p, separator, c];
}, []);
}
// javascript
export function joinArray(array, separator) {
return array.reduce((p, c, idx) => {
if (idx === 0)
return [c];
else
return [...p, separator, c];
}, []);
}
// example
console.log(joinArray(["1", "2", "3"], 2));
// -> ["1", 2, "2", 2, "3"]
// example
// privacyViews -> JSX.Element[]
const privacyViews = joinArray(
privacys.value.map(({ key, name }) => {
return (
<button onClick={() => clickPrivacy(key!)} class={Style.privacyBtn}>
{name}
</button>
);
}),
<span class={Style.privacyBtn}>、</span>
);
This will cover all cases
const items = []
if(x1) {
items.push(<span>text1</span>)
}
if(x2) {
items.push(<span>text3</span>)
}
if(x3) {
items.push(<span>text3</span>)
}
return <div>
<>{items.reduce((result, item) => result.length > 0 ? [...result, ', ', item] : [item], [])}</>
</div>