How to clear fields array in redux form - reactjs

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 [];
};

Related

Limit selection of checkboxes react

What should I do if I would like to limit the selected checkboxes to 3, which you cannot select more than 3 checkboxes. By referring this link https://codesandbox.io/s/wild-silence-b8k2j?file=/src/App.js
How to add some code to limit the selection here?
I am trying to play around this but didn’t figure out
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
// allow only 3 elements
if (updatedCheckedState.filter(v => v).length >= 4) {
return
}
setCheckedState(updatedCheckedState);
const totalPrice = updatedCheckedState.reduce(
(sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
},
0
);
setTotal(totalPrice);
};
Try this, you need to check length of selected array and checked status of current input
const handleOnChange = (position, e) => {
if (checkedState.filter((i) => i).length >= 3 && e.target.checked) return;
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
const totalPrice = updatedCheckedState.reduce((sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
}, 0);
setTotal(totalPrice);
};
<input
type="checkbox"
id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={(e) => handleOnChange(index, e)}
/>
codesandbox
Let's start from the state array...
As argument, use a function instead a direct value. It works the same, but it prevents to recreate uselessly an array (then fill it), which will be trashed on every render.
const [checkedState, setCheckedState] = useState(
() => new Array(toppings.length).fill(false)
);
Then, count how many are currently selected:
const selectedCount = checkedState.reduce((sum, currentState, index) => {
if (currentState === true) {
return sum + 1;
}
return sum;
}, 0);
Finally, prevent further selections on the checkboxes which are still selectable:
<input
type="checkbox"
id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={(e) => handleOnChange(index, e)}
disabled={!checkedState[index] && selectedCount >= 3}
/>

React button to show an element

I'm pulling countries from the Restcountries API and if the current state of the array has more than one or less than or equal to ten countries, I want to list the country names along with a 'show' button next to each one. The show button should display what's in the return (render) of my Country function. In the App function, I wrote a handler for the button named handleViewButton. I'm confused on how to filter the element in the Countries function in the else conditional statement in order to display the Country. I tried passing handleViewButton to the Button function, but I get an error 'Uncaught TypeError: newSearch.toLowerCase is not a function'. I really just want to fire the Country function to display the country button that was pressed.
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import axios from 'axios';
const Country = ({country}) => {
return (
<>
<h2>{country.name}</h2>
<p>capital {country.capital}</p>
<p>population {country.population}</p>
<br/>
<h3>languages</h3>
{country.languages.map(language => <li key={language.name}>{language.name}</li>)}
<br/>
<img src={country.flag} alt="country flag" style={{ width: '250px'}}/>
</>
);
}
const Countries = ({countries, handleViewButton}) => {
const countriesLen = countries.length;
console.log(countriesLen)
if (countriesLen === 0) {
return (
<p>Please try another search...</p>
);
} else if (countriesLen === 1) {
return (
<ul>
{countries.map((country, i) => <Country key={i} countriesLen={countriesLen} country={country}/>)}
</ul>
);
} else if (countriesLen > 10) {
return (
<div>
<p>Too many matches, specify another filter</p>
</div>
);
} else {
return (
<ul>
{countries.map((country, i) => <li key={i}>{country.name}<Button handleViewButton={handleViewButton}/></li>)}
</ul>
)
};
};
const Button = ({handleViewButton}) => {
return (
<button onClick={handleViewButton}>Show</button>
);
};
const Input = ({newSearch, handleSearch}) => {
return (
<div>
find countries <input value={newSearch} onChange={handleSearch}/>
</div>
);
};
function App() {
const [countries, setCountries] = useState([]);
const [newSearch, setNewSearch] = useState('');
const handleSearch = (event) => {
const search = event.target.value;
setNewSearch(search);
};
const handleViewButton = (event) => {
const search = event.target.value;
setNewSearch(countries.filter(country => country === search));
};
const showCountrySearch = newSearch
? countries.filter(country => country.name.toLowerCase().includes(newSearch.toLowerCase()))
: countries;
useEffect(() => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(res => {
setCountries(res.data);
console.log('Countries array loaded');
})
.catch(error => {
console.log('Error: ', error);
})
}, []);
return (
<div>
<Input newSearch={newSearch} handleSearch={handleSearch}/>
<Countries countries={showCountrySearch} handleViewButton={handleViewButton}/>
</div>
);
};
export default App;
you can use a displayCountry to handle the country that should be displayed. Most often you would use an id, but I'm using here country.name since it should be unique.
Then you would use matchedCountry to find against your list of countries.
After that, a onHandleSelectCountry to select a given country. if it's already selected then you could set to null to unselect.
Finally, you would render conditionally your matchedCountry:
const Countries = ({countries}) => {
const [displayCountry, setDisplayCountry] = useState(null);
const countriesLen = countries.length;
const matchedCountry = countries.find(({ name }) => name === displayCountry);
const onHandleSelectCountry = (country) => {
setDisplayCountry(selected => {
return selected !== country.name ? country.name : null
})
}
if (countriesLen === 0) {
return (
<p>Please try another search...</p>
);
} else if (countriesLen === 1) {
return (
<ul>
{countries.map((country, i) => <Country key={i} countriesLen={countriesLen} country={country}/>)}
</ul>
);
} else if (countriesLen > 10) {
return (
<div>
<p>Too many matches, specify another filter</p>
</div>
);
} else {
return (
<>
<ul>
{countries.map((country, i) => <li key={i}>{country.name}<Button handleViewButton={() => onHandleSelectCountry(country)}/></li>)}
</ul>
{ matchedCountry && <Country countriesLen={countriesLen} country={matchedCountry}/> }
</>
)
};
};
I can only help to point out some guidelines.
First: The button does not have value attribute. Hence what you will get from event.target.value is always blank.
const Button = ({handleViewButton}) => {
return (
<button onClick={handleViewButton}>Show</button>
);};
First->Suggestion: Add value to the button, of course you need to pass the value in.
const Button = ({handleViewButton, value}) => {
return (
<button onClick={handleViewButton} value={value}>Show</button>
);};
Second: To your problem 'Uncaught TypeError: newSearch.toLowerCase is not a function'. Filter always returns an array, not a single value. if you do with console or some sandbox [1,2,3].filter(x=>x===2) you will get [2] not 2.
const handleViewButton = (event) => {
const search = event.target.value;
setNewSearch(countries.filter(country => country === search));
};
Second->Suggestion: To change it to get the first element in array, since country(logically) is unique.
const result = countries.filter(country => country === search)
setNewSearch(result.length>0?result[0]:"");
A better approach for array is find, which always return first result and as a value. E.g. [1,2,2,3].find(x=>x===2) you will get 2 not [2,2] or [2].
countries.find(country => country === search)

How to load virtual dom once the data from api are loaded React

I think that my dom loads faster than data do.
I use useEffect to grab my data and store in localStorage
But when my data are loaded I have to refresh the page one more time to display my fetched data
How can I make my dom wait for the data. I tried to use useState and check whether we get the data from API, I put setLoadDom(true) in my useEffect if statement and then display dom if(loadDom) is true otherwise I show Loading...
From api I get
[
{firstName, lastName, id, completed},
....
{}
]
sortInOrder - get data and transforms it in array where indexes are [a,b,c,d,...] and the value is an array of all objects where lastName === to the letter. tmp['a'] = [{lastName: "Amanda"...},...]
toggleCheckbox - easy to understand, after I toggled checkbox I use localstorage to save my 'checked' status
Functional component
function App() {
const ALPHABET = "abcdefghijklmnopqrstuvwxyz".split("");
const [employeesData, setEmployeesData] =
useState(JSON.parse(localStorage.getItem('data')) ?? []);
const [alphabeticalOrder, setAlphabeticalOrder] = useState([]);
const sortInOrder = () => {
let tmp = []
for(let i = 0; i < ALPHABET.length; i++) {
tmp[ALPHABET[i]] = []
for(let j = 0; j < employeesData.length; j++) {
if(employeesData[j]['lastName'][0].toLowerCase() === ALPHABET[i]) {
tmp[ALPHABET[i]].push(employeesData[j])
}
}
}
for(let i = 0; i < ALPHABET.length; i++) {
if (tmp[ALPHABET[i]].length === 0) {
tmp[ALPHABET[i]] = ['-']
}
}
return tmp
}
const toggleCheckbox = (e, el) => {
let checked = e.target.checked;
setEmployeesData(
employeesData.map(person => {
if(person['id'] === el['id']) {
person['completed'] = checked;
}
return person;
})
)
localStorage.setItem('data', JSON.stringify(employeesData))
setAlphabeticalOrder(sortInOrder())
}
useEffect(() => {
axios.get('example.com')
.then(res => {
let data = JSON.parse(localStorage.getItem('data')) ?? []
if(res.data.length !== data.length) {
localStorage.setItem('data', JSON.stringify(res.data.map(el => ({...el, completed: false}))))
}
})
.catch(e => console.log('Error occured', e))
setAlphabeticalOrder(sortInOrder())
}, [])
return (
<div className="container">
<div className="wrapper">
<div className="employees">
<div className="employees_title align">Employees</div>
<div className="employees_info_block">
{ALPHABET.map((letter, i) => (
<div className="employees_block" key={letter}>
<div className="employee_letter" key={i}>{letter}</div>
{[...alphabeticalOrder[letter] ?? []].map(el => {
if (el[0] != '-') {
return (
<div className="info_field" key={el["id"]}>
<input type="checkbox" checked={el['completed'] ?? false}
onChange={(e) => toggleCheckbox(e, el)}/>
<div className="info_field_text">{el["firstName"]} {el["lastName"]}</div>
</div>
)
} else {
return (
<div>
-
</div>
)
}
})
}
<br/>
</div>
))}
</div>
</div>
<div className="birthday">
<div className="birthday_title align">Employes birthday</div>
</div>
</div>
</div>
);
}
could you try using setEmployeesData after:
localStorage.setItem('data', JSON.stringify(res.data.map(el => ({...el, completed: false}))))

Storing array objects using index on radio button click in react [duplicate]

This question already has answers here:
How to sort an array of integers correctly
(32 answers)
Closed 2 years ago.
How to store radio button values in ascending order of index in an array in react?
let questions;
class App extends Component {
constructor(props) {
super(props);
this.state = {
btnDisabled: true,
questions: [],
};
this.changeRadioHandler = this.changeRadioHandler.bind(this);
this.submitHandler = this.submitHandler.bind(this);
}
changeRadioHandler = (event) => {
const qn = event.target.name;
const id = event.target.value;
let text = this.props.data.matrix.options;
let userAnswer = [];
for (let j = 0; j < text.length; j++) {
userAnswer.push(false);
}
const option = text.map((t, index) => ({
text: t.text,
userAnswer: userAnswer[index],
}));
const elIndex = option.findIndex((element, i) => element.text === id);
let options = [...option];
options[elIndex] = {
...options[elIndex],
userAnswer: true,
};
const question = {
id: event.target.value,
qn,
options,
};
if (this.state.questions.some((question) => question.qn === qn)) {
questions = [
...this.state.questions.filter((question) => question.qn !== qn),
question,
];
} else {
questions = [...this.state.questions, question];
}
console.log(questions);
this.setState({ questions });
if (questions.length === text.length) {
this.setState({
btnDisabled: false,
});
}
};
submitHandler = () => {
console.log("btn clkd", questions);
};
render() {
return (
<div class="matrix-bd">
{this.props.data.header_text && (
<div class="header-qn">
<h5>{this.props.data.header_text} </h5>
</div>
)}
{this.props.data.matrix && (
<div class="grid">
{this.props.data.matrix.option_questions.map((questions, j) => {
return (
<div class="rows" key={j}>
<div class="cell main">{questions.text}</div>
{this.props.data.matrix.options.map((element, i) => {
return (
<div class="cell" key={i}>
<input
type="radio"
id={"rad" + j + i}
name={questions.text}
value={element.text}
onChange={this.changeRadioHandler}
></input>
<label htmlFor={"rad" + j + i}>{element.text}</label>
</div>
);
})}
</div>
);
})}
</div>
)}
<div class="buttonsubmit text-right">
<button
type="button"
id="QstnSubmit"
name="QstnSubmit"
class="btn btn-primary"
disabled={this.state.btnDisabled}
onClick={this.submitHandler}
>
{this.props.labels.LBLSUBMIT}
</button>
</div>
</div>
);
}
}
export default App;
I have added my code where the array is unordered with respect to index, I want to order the array using qn(const qn = event.target.name;) object. Like, how it came from the database, likewise in the same order, it should go into the db.
You can use the sort() method, then have a callback function with two arguments and return them in an accending order, like this:
ans.sort((a, b) =>{return a-b});

Group checkbox in Antd by using array object

Here is the original example of group checkbox of antd that I need and its fine:
const plainOptions = ['Apple', 'Pear', 'Orange'];
const defaultCheckedList = ['Apple', 'Orange'];
class App extends React.Component {
state = {
checkedList: defaultCheckedList,
indeterminate: true,
checkAll: false,
};
onChange = checkedList => {
this.setState({
checkedList,
indeterminate: !!checkedList.length && checkedList.length < plainOptions.length,
checkAll: checkedList.length === plainOptions.length,
});
};
onCheckAllChange = e => {
this.setState({
checkedList: e.target.checked ? plainOptions : [],
indeterminate: false,
checkAll: e.target.checked,
});
};
render() {
return (
<div>
<div style={{ borderBottom: '1px solid #E9E9E9' }}>
<Checkbox
indeterminate={this.state.indeterminate}
onChange={this.onCheckAllChange}
checked={this.state.checkAll}
>
Check all
</Checkbox>
</div>
<br />
<CheckboxGroup
options={plainOptions}
value={this.state.checkedList}
onChange={this.onChange}
/>
</div>
);
}
}
My question is how can I replace the plainOptions and defaultCheckedList by object array instead of simple array and using attribute name for this check boxes?
For example this object:
const plainOptions = [
{name:'alex', id:1},
{name:'milo', id:2},
{name:'saimon', id:3}
];
const defaultCheckedList = [
{name:'alex', id:1},
{name:'milo', id:2}
];
I want to use attribute name as the key in this example.
Problem solved. I should use "Use with grid" type of group checkbox. It accepts object array. The only think I could do was creating a function that inject "label" and "value" to my object. It makes some duplicates but no problem.
function groupeCheckboxify(obj, labelFrom) {
for (var i = 0; i < obj.length; i++) {
if (obj[i][labelFrom]) {
obj[i]['label'] = obj[i][labelFrom];
obj[i]['value'] = obj[i][labelFrom];
}
if (i == obj.length - 1) {
return obj;
}
}
}
// for calling it:
groupeCheckboxify( myObject , 'name');
I'd this same problem and couldn't find any answer on the entire web. But I tried to find a good way to handle it manually.
You can use this code:
import { Checkbox, Dropdown } from 'antd';
const CheckboxGroup = Checkbox.Group;
function CheckboxSelect({
title,
items,
initSelectedItems,
hasCheckAllAction,
}) {
const [checkedList, setCheckedList] = useState(initSelectedItems || []);
const [indeterminate, setIndeterminate] = useState(true);
const [checkAll, setCheckAll] = useState(false);
const onCheckAllChange = (e) => {
setCheckedList(e.target.checked ? items : []);
setIndeterminate(false);
setCheckAll(e.target.checked);
};
const onChangeGroup = (list) => {
if (hasCheckAllAction) {
setIndeterminate(!!list.length && list.length < items.length);
setCheckAll(list.length === items.length);
}
};
const updateItems = (el) => {
let newList = [];
if (el.target.checked) {
newList = [...checkedList, el.target.value];
} else {
newList = checkedList.filter(
(listItem) => listItem.id !== el.target.value.id,
);
}
setCheckedList(newList);
};
useEffect(() => {
setCheckedList(initSelectedItems);
}, []);
const renderItems = () => {
return (
<div classname="items-wrapper">
{hasCheckAllAction ? (
<Checkbox
indeterminate={indeterminate}
onChange={onCheckAllChange}
checked={checkAll}
>
All
</Checkbox>
) : null}
<CheckboxGroup onChange={onChangeGroup} value={checkedList}>
<>
{items.map((item) => (
<Checkbox
key={item.id}
value={item}
onChange={($event) => updateItems($event)}
>
{item.name}
</Checkbox>
))}
</>
</CheckboxGroup>
</div>
);
};
return (
<Dropdown overlay={renderItems()} trigger={['click']}>
<div>
<span className="icon icon-arrow-down" />
<span className="title">{title}</span>
</div>
</Dropdown>
);
}
It looks like the only difference you are talking about making is using an array of objects instead of strings? If that's the case, when looping through the array to create the checkboxes, you access the object attributes using dot notation. It should look something like this if I understand the problem correctly.
From CheckboxGroup component:
this.props.options.forEach(el => {
let name = el.name;
let id = el.id;
//rest of code to create checkboxes
or to show an example in creating components
let checkboxMarkup = [];
checkboxMarkup.push(
<input type="checkbox" id={el.id} name={el.name} key={`${el.id} - ${el.name}`}/>
);
}
'el' in this case refers to each individual object when looping through the array. It's not necessary to assign it to a variable, I just used that to show an example of how to access the properties.

Resources