I have a rules component where any number of rules can be added and that added rules will be shown either along with the additional add feature for the rules. There is no problem while adding, removing the rule field but when posting the data to the server, the data gets posted but one more extra empty field is generated in the client side. I don't know how and why its happening.
Here is the code
const _id = 0;
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
}
return s4() + s4();
}
const removeByKey = (obj, deleteKey) =>
Object.keys(obj).filter(key => key !== deleteKey).reduce((result, current) => {
result[current] = obj[current];
return result;
}, {});
class Rules extends React.PureComponent {
constructor(props, context) {
super(props, context);
this.state = {
rules: { ruleAndregulations: {} },
};
}
componentDidMount() {
this.props.loadRules();
}
componentWillReceiveProps(nextProps) {
if (nextProps.rules.size && nextProps.rules !== this.props.rules) {
nextProps.rules
.entrySeq()
.map(([key, value]) => {
console.log('key', key, value);
this.setState(
state => ({
rules: {
...state.rules,
ruleAndregulations: {
...state.rules.ruleAndregulations,
[value.get('_id')]: { _id: value.get('_id'), value: value.get('value') }
}
}
})
);
})
.toArray();
}
}
handleAddRules = e => {
this.setState({
rules: {
...this.state.rules,
ruleAndregulations: {
...this.state.rules.ruleAndregulations,
[guid()]: {
_id: guid(),
value: ''
}
}
}
});
};
handleRemoveRules = (e, num) => {
this.setState({
rules: {
...this.state.rules,
ruleAndregulations: removeByKey(this.state.rules.ruleAndregulations, num)
}
});
};
handleChange = e => {
this.setState({
rules: {
...this.state.rules,
ruleAndregulations: {
...this.state.rules.ruleAndregulations,
[e.target.name]: {
_id: e.target.name,
value: e.target.value
}
}
}
});
};
handleSave = e => {
e.preventDefault();
const ruleObj = { rule_regulations: [] };
const { ruleAndregulations } = this.state.rules;
Object.keys(ruleAndregulations).map(val =>
ruleObj.rule_regulations.push(ruleAndregulations[val].value)
);
this.props.postRules(ruleObj);
};
render() {
const { rules } = this.state;
console.log('state', this.state);
return (
<div className="basic-property">
Add Rules and Regulations
<span className="circleInputUi" onClick={this.handleAddRules}>
+
</span>
{rules &&
rules.ruleAndregulations &&
<RulesInputContainer
handleChange={this.handleChange}
handleRemoveRules={this.handleRemoveRules}
handleSave={this.handleSave}
value={rules.ruleAndregulations}
/>}
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Rules);
reducer for posting is
case POST_RULES_SUCCESS:
return state
.merge({
loading: false,
error: null,
response: action.response.message
})
.update('rules', rules => rules.push(fromJS(action.response.data)));
for code in more detail
https://gist.github.com/MilanRgm/7f744ee27df7e5d17410ffcba99fb89c
After posting I get the following data in componentWillReceiveProps
When using concat
a) When very first rule is added
b) When second rule added
It looks like you are pushing a List into a List, instead of concating it. In your reducer, try
.update('rules', rules => rules.concat(fromJS(action.response.data)));
Related
I am using React-autosuggest in my code but the issue i am facing is this that whenever i am clikcing any suggestion i am getting error that
Uncaught TypeError: Cannot read property 'trim' of undefined
Here is my code
var subjectsToBeSearched= []
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0
? []
:subjectsToBeSearched.filter(
lang => lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
};
const getSuggestionValue = suggestion => suggestion.name;
`
const renderSuggestion = suggestion => <div>{suggestion.name}</div>;
export default class Searchbar extends Component {
state = {
language_id: "",
subjects: [],
value: "",
suggestions: []
};
onChange = event => {
this.setState({
value: event.target.value
},()=>console.log(this.state.value));
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
componentWillMount() {
let languageid = localStorage.getItem("language_id");
var userdata = window.localStorage.getItem("userdata");
if (languageid == null) {
localStorage.setItem("language_id", 0);
}
this.setState({ language_id: languageid, userdata: JSON.parse(userdata) });
this.getAllSubjects();
}
getAllSubjects = async () => {
let details = {
language_id: this.state.language_id
};
this.setState({
response: fetch("http://18.221.47.207:3000/get_subjects", {
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Cache-Control": "max-age=31536000"
}
})
.then(response => response.json())
.then(responseJson => {
this.setState(
{
subjects: responseJson
},
() => {
let subjectsToBeFind = this.state.subjects.map(item => {
return { id: item.subject_id, name: item.subject_name };
});
subjectsToBeSearched=subjectsToBeFind
}
);
})
.catch(error => {
this.setState({
loading: false
});
swal("Warning!", "Check your network!", "warning");
console.log(error);
})
});
};
render() {
const { value, suggestions } = this.state;
const inputProps = {
value,
onChange: this.onChange
};
return (
<div className={`${styles.InputHeaderSearchDiv} `}>
<Autosuggest
inputProps={inputProps}
className={`${styles.InputHeaderSearch}`}
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
// alwaysRenderSuggestions={true}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
/>
<div className={`${styles.SearchIcon}`}>
<img src={SearchIcon} alt="search" />
</div>
</div>
);
}
}
So when i click on any suggestion i just need to console and do some stuff but right away it throws error
When you click a suggestion, the value of the "value" key in the inputProps is undefined thats the reason that you are getting a cannot trim error.
A workaround I did is add a props called "onSuggestionSelected" ( Documentation for onSuggestionSelected found here ) and added a function that set the value of the "value" key in the inputProps to whatever value you want your input tag should have after the click event.
THIS IS HOW MY AUTOSUGGEST LOOKSLIKE
HERE IS MY FUNCTION
in my case simply setting value of inputProps to string did it
<Autosuggest
inputProps: {
value: whateverValue.toString()
I want to add new Objects when user click on checkbox. For example , When user click on group , it will store data {permission:{group:["1","2"]}}. If I click on topgroup , it will store new objects with previous one
{permission:{group:["1","2"]},{topGroup:["1","2"]}}.
1st : The problem is that I can not merge new object with previous one . I saw only one objects each time when I click on the group or topgroup.
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
Object.assign(prevState.permission, { [value]: this.state.checked });
});
});
};
<CheckboxGroup
options={options}
value={checked}
onChange={this.onChange(this.props.label)}
/>
Here is my codesanbox:https://codesandbox.io/s/stackoverflow-a-60764570-3982562-v1-0qh67
It is a lot of code because I've added set and get to set and get state. Now you can store the path to the state in permissionsKey and topGroupKey. You can put get and set in a separate lib.js.
In this example Row is pretty much stateless and App holds it's state, this way App can do something with the values once the user is finished checking/unchecking what it needs.
const Checkbox = antd.Checkbox;
const CheckboxGroup = Checkbox.Group;
class Row extends React.Component {
isAllChecked = () => {
const { options, checked } = this.props;
return checked.length === options.length;
};
isIndeterminate = () => {
const { options, checked } = this.props;
return (
checked.length > 0 && checked.length < options.length
);
};
render() {
const {
options,
checked,
onChange,
onToggleAll,
stateKey,
label,
} = this.props; //all data and behaviour is passed by App
return (
<div>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={e =>
onToggleAll(e.target.checked, stateKey)
}
checked={this.isAllChecked()}
>
Check all {label}
</Checkbox>
<CheckboxGroup
options={options}
value={checked}
onChange={val => {
onChange(stateKey, val);
}}
/>
</div>
</div>
);
}
}
//helper from https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9
const REMOVE = () => REMOVE;
const get = (object, path, defaultValue) => {
const recur = (current, path) => {
if (current === undefined) {
return defaultValue;
}
if (path.length === 0) {
return current;
}
return recur(current[path[0]], path.slice(1));
};
return recur(object, path);
};
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path) => {
if (path.length === 1) {
return setKey(
current,
path[0],
callback(current[path[0]])
);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1))
);
};
return recur(object, path, callback);
};
class App extends React.Component {
state = {
permission: { group: [] },
topGroup: [],
some: { other: [{ nested: { state: [] } }] },
};
permissionsKey = ['permission', 'group']; //where to find permissions in state
topGroupKey = ['topGroup']; //where to find top group in state
someKey = ['some', 'other', 0, 'nested', 'state']; //where other group is in state
onChange = (key, value) => {
//use set helper to set state
this.setState(set(this.state, key, arr => value));
};
isIndeterminate = () =>
!this.isEverythingChecked() &&
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result || get(this.state, key).length,
false
);
toggleEveryting = e => {
const checked = e.target.checked;
this.setState(
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
set(result, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
),
this.state
)
);
};
onToggleAll = (checked, key) => {
this.setState(
//use set helper to set state
set(this.state, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
)
);
};
isEverythingChecked = () =>
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result &&
get(this.state, key).length ===
this.plainOptions.length,
true
);
plainOptions = [
{ value: 1, name: 'Apple' },
{ value: 2, name: 'Pear' },
{ value: 3, name: 'Orange' },
];
render() {
return (
<React.Fragment>
<h1>App state</h1>
{JSON.stringify(this.state)}
<div>
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={this.toggleEveryting}
checked={this.isEverythingChecked()}
>
Toggle everything
</Checkbox>
</div>
{[
{ label: 'group', stateKey: this.permissionsKey },
{ label: 'top', stateKey: this.topGroupKey },
{ label: 'other', stateKey: this.someKey },
].map(({ label, stateKey }) => (
<Row
key={label}
options={this.plainOptions}
// use getter to get state selected value
// for this particular group
checked={get(this.state, stateKey)}
label={label}
onChange={this.onChange} //change behaviour from App
onToggleAll={this.onToggleAll} //toggle all from App
//state key to indicate what state needs to change
// used in setState in App and passed to set helper
stateKey={stateKey}
/>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.js"></script>
<div id="root"></div>
I rewrite all the handlers.
The bug in your code is located on the usage of antd Checkbox.Group component with map as a child component, perhaps we need some key to distinguish each of the Row. Simply put them in one component works without that strange state update.
As the demand during communication, the total button is also added.
And, we don't need many states, keep the single-source data is always the best practice.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Checkbox } from "antd";
const group = ["group", "top"];
const groupItems = ["Apple", "Pear", "Orange"];
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
constructor() {
super();
this.state = {
permission: {}
};
}
UNSAFE_componentWillMount() {
this.setDefault(false);
}
setDefault = fill => {
const temp = {};
group.forEach(x => (temp[x] = fill ? groupItems : []));
this.setState({ permission: temp });
};
checkLength = () => {
const { permission } = this.state;
let sum = 0;
Object.keys(permission).forEach(x => (sum += permission[x].length));
return sum;
};
/**
* For total
*/
isTotalIndeterminate = () => {
const len = this.checkLength();
return len > 0 && len < groupItems.length * group.length;
};
onCheckTotalChange = () => e => {
this.setDefault(e.target.checked);
};
isTotalChecked = () => {
return this.checkLength() === groupItems.length * group.length;
};
/**
* For each group
*/
isIndeterminate = label => {
const { permission } = this.state;
return (
permission[label].length > 0 &&
permission[label].length < groupItems.length
);
};
onCheckAllChange = label => e => {
const { permission } = this.state;
const list = e.target.checked ? groupItems : [];
this.setState({ permission: { ...permission, [label]: list } });
};
isAllChecked = label => {
const { permission } = this.state;
return !groupItems.some(x => !permission[label].includes(x));
};
/**
* For each item
*/
isChecked = label => {
const { permission } = this.state;
return permission[label];
};
onChange = label => e => {
const { permission } = this.state;
this.setState({ permission: { ...permission, [label]: e } });
};
render() {
const { permission } = this.state;
console.log(permission);
return (
<React.Fragment>
<Checkbox
indeterminate={this.isTotalIndeterminate()}
onChange={this.onCheckTotalChange()}
checked={this.isTotalChecked()}
>
Check all
</Checkbox>
{group.map(label => (
<div key={label}>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate(label)}
onChange={this.onCheckAllChange(label)}
checked={this.isAllChecked(label)}
>
Check all
</Checkbox>
<CheckboxGroup
options={groupItems}
value={this.isChecked(label)}
onChange={this.onChange(label)}
/>
</div>
</div>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Try it online:
Please try this,
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
permission : { ...prevSatate.permission , { [value]: this.state.checked }}
});
});
};
by using spread operator you can stop mutating the object. same way you can also use object.assign like this.
this.setState(prevState => {
permission : Object.assign({} , prevState.permission, { [value]: this.state.checked });
});
And also i would suggest not to call setState in a callback. If you want to access the current state you can simply use the current checked value which you are getting in the function itself.
so your function becomes ,
onChange = value => checked => {
this.setState({ checked });
this.setState(prevState => {return { permission : { ...prevSatate.permission, { [value]: checked }}
}});
};
Try the following
//Inside constructor do the following
this.state = {checkState:[]}
this.setChecked = this.setChecked.bind(this);
//this.setChecked2 = this.setChecked2.bind(this);
//Outside constructor but before render()
setChecked(e){
this.setState({
checkState : this.state.checkState.concat([{checked: e.target.id + '=>' + e.target.value}])
//Id is the id property for a specific(target) field
});
}
//Finally attack the method above.i.e. this.setChecked to a form input.
Hope it will address your issues
I found a lot of solutions about this problem but none of them work.
I have a view which renders dynamically components depending on the backend response
/**
* Module dependencies
*/
const React = require('react');
const Head = require('react-declarative-head');
const MY_COMPONENTS = {
text: require('../components/fields/Description'),
initiatives: require('../components/fields/Dropdown'),
vuln: require('../components/fields/Dropdown'),
severities: require('../components/fields/Dropdown'),
};
const request = restclient({
timeout: 5000,
baseURL: '/api',
});
const { DropdownItem } = Dropdown;
class CreateView extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
states: props.states,
error: props.error,
spinner: true,
state: props.state,
prevState: '',
components: [],
};
this.handleChange = this.handleChange.bind(this);
this.getRequiredFields = this.getRequiredFields.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
this.changeState = this.changeState.bind(this);
this.loadComponents = this.loadComponents.bind(this);
}
componentDidMount() {
this.loadComponents();
}
onChangeHandler(event, value) {
this.setState((prevState) => {
prevState.prevState = prevState.state;
prevState.state = value;
prevState.spinner = true;
return prevState;
}, () => {
this.getRequiredFields();
});
}
getRequiredFields() {
request.get('/transitions/fields', {
params: {
to: this.state.state,
from: this.state.prevState,
},
})
.then((response) => {
const pComponents = this.state.components.map(c => Object.assign({}, c));
pComponents.forEach((c) => {
c.field.required = 0;
c.field.show = false;
});
response.data.forEach((r) => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
ob.field.required = r.required;
ob.field.show = true;
}
});
this.setState({
components: pComponents,
fields: response.data,
spinner: false,
});
})
.catch(err => err);
}
loadComponents() {
this.setState((prevState) => {
prevState.components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
};
return {
field, component: MY_COMPONENTS[k],
};
});
return prevState;
});
}
handleChange(field, value) {
this.setState((prevState) => {
prevState[field] = value;
return prevState;
});
}
changeState(field, value) {
this.setState((prevState) => {
prevState[`${field}`] = value;
return prevState;
});
}
render() {
const Components = this.state.components;
return (
<Page name="CI" state={this.props} Components={Components}>
<Script src="vendor.js" />
<Card className="">
<div className="">
<div className="">
<Spinner
show={this.state.spinner}
/>
{Components.map((component, i) => {
const Comp = component.component;
return (<Comp
key={i}
value={this.state[component.field.name]}
field={component.field}
handleChange={this.handleChange}
modal={this.state.modal}
changeState={this.changeState}
/>);
})
}
</div>
</div>
</div>
</Card>
</Page>
);
}
}
module.exports = CreateView;
and the dropdown component
const React = require('react');
const request = restclient({
timeout: 5000,
baseURL: '/api',
});
const { DropdownItem } = Dropdown;
class DrpDwn extends React.Component {
constructor(props) {
super(props);
this.state = {
field: props.field,
values: [],
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('state', this.state.field);
console.log('prevState', prevState.field);
console.log('prevProps', prevProps.field);
console.log('props', this.props.field);
}
render() {
const { show } = this.props.field;
return (show && (
<div className="">
<Dropdown
className=""
onChange={(e, v) => this.props.handleChange(this.props.field.name, v)}
label={this.state.field.name.replace(/^./,
str => str.toUpperCase())}
name={this.state.field.name}
type="form"
value={this.props.value}
width={100}
position
>
{this.state.values.map(value => (<DropdownItem
key={value.id}
value={value.name}
primary={value.name.replace(/^./, str => str.toUpperCase())}
/>))
}
</Dropdown>
</div>
));
}
module.exports = DrpDwn;
The code actually works, it hide or show the components correctly but the thing is that i can't do anything inside componentdidupdate because the prevProps prevState and props are always the same.
I think the problem is that I'm mutating always the same object, but I could not find the way to do it.
What I have to do there is to fill the dropdown item.
Ps: The "real" code works, i adapt it in order to post it here.
React state is supposed to be immutable. Since you're mutating state, you break the ability to tell whether the state has changed. In particular, i think this is the main spot causing your problem:
this.setState((prevState) => {
prevState.components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
}; return {
field, component: MY_COMPONENTS[k],
};
});
return prevState;
});
You mutate the previous states to changes its components property. Instead, create a new state:
this.setState(prevState => {
const components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
};
return {
field, component: MY_COMPONENTS[k],
};
});
return { components }
}
You have an additional place where you're mutating state. I don't know if it's causing your particular problem, but it's worth mentioning anyway:
const pComponents = [].concat(this.state.components);
// const pComponents = [...this.state.components];
pComponents.forEach((c) => {
c.field.required = 0;
c.field.show = false;
});
response.data.forEach((r) => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
ob.field.required = r.required;
ob.field.show = true;
}
});
You do at make a copy of state.components, but this will only be a shallow copy. The array is a new array, but the objects inside the array are the old objects. So when you set ob.field.required, you are mutating the old state as well as the new.
If you want to change properties in the objects, you need to copy those objects at every level you're making a change. The spread syntax is usually the most succinct way to do this:
let pComponents = this.state.components.map(c => {
return {
...c,
field: {
...c.field,
required: 0,
show: false
}
}
});
response.data.forEach(r => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
// Here it's ok to mutate, but only because i already did the copying in the code above
ob.field.required = r.required;
ob.field.show = true;
}
})
I am using React and Redux in order to manage the state of a list of checkboxes. What I have so far is working but eslint is sending me back an error:
assignment to property of function parameter
So I think I am doing something wrong.
This is the component:
import { toggleCheckboxAction, setCheckboxesToChecked } from '../actions/cancellations';
const CheckboxList = ({
columnsFilterHandler,
setCheckboxesToCheckedHandler,
checkboxes,
t,
}) => {
const onChange = (value, id, event) => {
const item = checkboxes.slice().find(idx => idx.id === id);
if (item) {
item.checked = !item.checked;
columnsFilterHandler(value, id, event.target.value);
return { checkboxes };
}
return item;
};
const setCheckboxesToTrue = () => {
const items = checkboxes.filter(checkbox => checkbox.checked === false);
items.map(item => {
if (item) {
item.checked = !item.checked; // LINE THROWING THE WARNING
setCheckboxesToCheckedHandler(item.checked);
}
return item;
});
};
return (
<>
<ToolbarTitle title="Columns" />
<ToolbarOption>
<Button kind="ghost" small onClick={setCheckboxesToTrue}>
{t('cancellations.resetDefault')}
</Button>
</ToolbarOption>
{checkboxes.map(checkbox => (
<ToolbarOption>
<Checkbox
key={checkbox.id}
id={checkbox.id}
labelText={checkbox.labelText}
value={checkbox.value}
checked={checkbox.checked}
onChange={onChange}
/>
</ToolbarOption>
))}
</>
);
};
CheckboxList.propTypes = {
columnsFilterHandler: PropTypes.func.isRequired,
setCheckboxesToCheckedHandler: PropTypes.func.isRequired,
checkboxes: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
};
const mapStateToProps = state => ({
checkboxes: state.cancellations.checkboxes,
});
export default compose(
connect(
mapStateToProps,
dispatch => ({
columnsFilterHandler: (value, isChecked, valueName) => {
dispatch(toggleCheckboxAction(value, isChecked, valueName));
},
setCheckboxesToCheckedHandler: isChecked => {
dispatch(setCheckboxesToChecked(isChecked));
},
}),
),
)(translate()(CheckboxList));
On the onChange function is where I play with the checkboxes, setting them to checked or !checked.
The issue comes from the function setCheckboxesToTrue on the line item.checked = !item.checked;
This is the action:
const setCheckboxesToChecked = isChecked => ({
type: ActionTypes.CHECKED_ALL_CHECKBOXES,
payload: { isChecked },
});
And this is the reducer to set them true or false:
[ActionTypes.TOGGLE_CHECKBOX](state, action) {
return {
...state,
checkboxes: initialState.checkboxes.map(checkbox => {
if (checkbox.id !== action.payload.id) {
return checkbox;
}
return {
...checkbox,
checked: !checkbox.checked,
};
}),
};
},
And this should be the reducer to set them all to true:
[ActionTypes.CHECKED_ALL_CHECKBOXES](state, action) {
return {
...state,
checkboxes: initialState.checkboxes.map(checkbox => {
if (checkbox.id !== action.payload.id) {
return checkbox;
}
return {
...checkbox,
checked: checkbox.checked,
};
}),
};
},
Do you guys know a better way to do what I am attempting to do or something to fix the error I am getting?
So, ESLint is complaining for exactly the reason it says: you are modifying the value of a function parameter. While this is perfectly valid JS and will work, it's highly discouraged because it makes it very easy to create bugs. Specifically:
items.map(item => {
if (item) {
item.checked = !item.checked; // HERE, you modify `item` which is the parameter passed to the map callback function
setCheckboxesToCheckedHandler(item.checked);
}
return item;
});
What you should do (and have done elsewhere in your code so you might have just forgotten to here):
items.map(item => {
const newItem = { ...item }; // Create a copy of `item` so that you don't need to modify the one coming in as the function parameter
if (item) {
newItem.checked = !newItem.checked;
setCheckboxesToCheckedHandler(newItem.checked);
}
return newItem;
});
I want to have a dynamic number of input field and also able to remove it. I can do it if this is only client side but I am getting the data from server and the issue is that I am not getting the id from the server, and that I need to be able to change the number of fields on the fly in the UI.
since I am also getting some fields from the server the manually added id was not longer in sync with the # of items.
How do i achieve this?
Here is my code
let _id = 0;
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
}
return s4() + s4();
}
class Rules extends React.PureComponent {
constructor(props, context) {
super(props, context);
this.state = {
rules: {
rule_regulations: {}
},
numberOfInput: []
};
}
componentDidMount() {
this.props.loadRules();
}
componentWillReceiveProps(nextProps) {
if (nextProps.rules.size && nextProps.rules !== this.props.rules) {
nextProps.rules
.entrySeq()
.map(([key, value]) => {
this.setState(state => ({
rules: {
...state.rules,
rule_regulations: {
...state.rules.rule_regulations,
[key]: value
}
},
numberOfInput: [...state.numberOfInput, guid()]
}));
})
.toArray();
}
}
handleChange = e => {
const { value, name } = e.target;
this.setState({
rules: {
...this.state.rules,
rule_regulations: {
...this.state.rules.rule_regulations,
[name]: value
}
}
});
};
handleAddRules = e => {
this.setState({
numberOfInput: [...this.state.numberOfInput, guid()]
});
};
handleRemoveRules = (e, num) => {
e.preventDefault();
this.setState({
numberOfInput: this.state.numberOfInput.filter(input => input !== num)
});
};
handleSave = e => {
e.preventDefault();
const obj = {
rule_regulations: Object.values(this.state.rules.rule_regulations)
};
this.props.postRules(obj);
};
render() {
const { numberOfInput } = this.state;
return (
<div className="basic-property">
<span className="circleInputUi" onClick={this.handleAddRules}>
+
</span>
<RulesInputContainer
numberOfInput={numberOfInput}
handleChange={this.handleChange}
handleRemoveRules={this.handleRemoveRules}
handleSave={this.handleSave}
value={this.state.rules.rule_regulations}
/>
</div>
);
}
}
const RulesInputContainer = props => {
return (
<div className="rules-input">
{props.value &&
Object.keys(props.value).map(key =>
<RulesInput
key={key}
num={key}
value={props.value}
handleChange={props.handleChange}
handleRemoveRules={props.handleRemoveRules}
/>
)}
<button className="button" onClick={props.handleSave}>
Save
</button>
</div>
);
};
export default RulesInputContainer;
const RulesInput = props => {
return (
<form className="form">
<InputField
label={`Rule ${props.num + 1}`}
type="text"
name={`${props.num}`}
value={props.value[props.num] || ""}
onChange={props.handleChange}
/>
<Button onClick={e => props.handleRemoveRules(e, props.num)}>
Remove
</Button>
</form>
)
}
I want the synchronization with server data and client
You can store returned data from your server into your component state.
componentDidMount() {
this.loadRules();
}
loadRules() {
let rules = this.props.loadRules();
this.setState({rules});
}
You can keep the synchronization by setting your state with data you received from your server.
In addition, it's not related to your question. Since RulesInputContainer and RulesInput are presentational components I think you can combine those into 1 component.