Modifying a value in a nested object [closed] - javascript

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have a nested object:
{
id: "id",
name: "Name",
type: "SC",
allgemein: {
charname: "Name",
spieler: "Jon",
},
eigenschaften: {
lebenspunkte: "30",
},
talente: {},
zauber: {},
}
With my form I'm trying to create a new object. Most of it works, but in the function handleSubmit, I'm trying to set the nested spieler to "TEST".
import React from "react";
import { TextField, Button } from "#material-ui/core/";
export default class extends React.Component {
state = this.getInitState();
getInitState() {
const { charakterID } = this.props;
return charakterID
? charakterID
: {
name: "",
allgemein: {
charname: "",
spieler: "",
},
eigenschaften: {},
talente: {},
zauber: {},
};
}
componentWillReceiveProps({ charakterID }) {
this.setState({
...charakterID,
});
}
handleChange = (n) => ({ target: { value } }) => {
this.setState({
[n]: value,
});
};
handleChangeAllg = (n) => ({ target: { value } }) => {
this.setState((prevState) => ({
...prevState,
allgemein: {
...prevState.allgemein,
charname: value,
},
}));
};
handleSubmit = () => {
this.props.onSubmit({
id: this.state.name.toLocaleLowerCase().replace(/ /g, "-"),
type: "SC",
allgemein: {spieler: "TEST"},
...this.state,
});
this.setState(this.getInitState());
};
render() {
const {
name,
allgemein: { charname },
} = this.state,
{ charakterID } = this.props;
console.log("fired");
console.log(this.props.onCreate);
return (
<form>
<TextField
label="name"
value={name}
onChange={this.handleChange("name")}
margin="dense"
fullWidth
/>
<br />
<TextField
label="charname"
value={charname}
onChange={this.handleChangeAllg("charname")}
margin="dense"
fullWidth
/>
<br />
<Button color="primary" variant="contained" onClick={this.handleSubmit}>
{charakterID ? "Edit" : "Neu"}
</Button>
</form>
);
}
}
It wont work and I don't know why. Can you help me?

Try if this works
handleSubmit = () => {
this.props.onSubmit({
...this.state, // Changed Position
id: this.state.name.toLocaleLowerCase().replace(/ /g, "-"),
type: "SC",
allgemein: {...this.state.allgemein,spieler: "TEST"},
});
this.setState(this.getInitState());
};

Related

Updating redux state by a local state of checkbox items

there are similiar questions in stackoverflow but I I did not find what I was looking for.
I have a donorDonationForm which is a class componenet that connected to the redux state. The porpuse of that componenet is to collect inormation about a person that want to donate electronics items. At this point, I want to save those items in an array (maybe with an object in the future).
my redux state save the donor info and the reducer looks like this:
import {CHANGE_INPUT_FIELD} from '../utils/constants';
const initialStateInputs = {
// update the state
donorFields: {
name: '',
phone: '',
area: '',
yeshuv: '',
address: ''
// dateOfOffer: ''
},
donationFields: {
// donorID: '',
// vulonteerID: '',
type: [],
quantity: 1,
status: 'NOT_HANDLED',
comments: ''
// lastDateHandled: ''
}
// }, items: [ //need to add quantity
// {id: 1, name: "LAPTOP", isChecked: false, label: 'מחשב'},
// {id: 2, name: "HEADPHONES", isChecked: false, label: 'אוזניות'},
// {id: 3, name: "OTHER", isChecked: false, label: 'אחר'},
// ]
}
export const donorDonationInputsReducer = ( state = initialStateInputs, action={} ) => {
switch(action.type) {
case CHANGE_INPUT_FIELD:
return Object.assign( {}, state,
{
donorFields : {...state.donorFields,...action.payload},
donationFields: {...state.donationFields,...action.payload},
// items : {...state.items,...action.payload},
// isChecked: action.payload
})
default:
return state;
}
}
As you can see the items is commented by now, and I am managing the state of the item in a local state, and that how the comp looks like:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { setInputField } from '../actions/formAction';
import CheckBox from '../components/CheckBox/CheckBox';
import FormInput from '../components/FormInput/FormInput';
import {selectAreasOptions_2} from '../utils/constants';
import "./form.css";
const mapStateToProps = (state) => {
return {
donorFields: state.donorDonationInputsReducer.donorFields,
donationFields: state.donorDonationInputsReducer.donationFields
}
}
const mapDispatchToProps = dispatch => {
return {
onInputChange: event => {
const {name, value} = event.target;
dispatch(setInputField( { [name]:value} ) )
}
}
}
class donorDonationForm extends Component {
constructor() {
super();
this.state = {
items: [
{id: 1, name: "LAPTOP", isChecked: false, label: 'מחשב'},
{id: 2, name: "HEADPHONES", isChecked: false, label: 'אוזניות'},
{id: 3, name: "OTHER", isChecked: false, label: 'אחר'},
]
,
type: []
}
}
handleCheckChieldElement = (event) => {
let {items, type} = this.state;
let arr = [];
items.forEach(item => {
if (item.name === event.target.value) {
item.isChecked = event.target.checked;
// console.log(`item.name :${item.name }`);
// console.log(`event.target.value :${event.target.value}`);
// console.log(`event.target.checked :${event.target.checked}`);
}
})
items.map(item => item.isChecked ? arr.push(item.name) : null)
this.setState({items: [...items], type: [...arr]});
}
onButtonSubmit = (event) => {
console.log(this.props.donorFields);
event.preventDefault();
fetch('http://localhost:8000/api/donor', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
...this.props.donorFields
})
})
.then(response => response.json())
.then(resp => console.log(resp))
.catch( err => console.log(err) )
}
// componentDidUpdate(prevProps, prevState) {
// const {items, type} = this.state;
// // const type = [];
// if (prevState.items !== items) {
// console.log('items state has changed');
// items.map (item => item.isChecked ?
// this.setState({type: [...type,item.name]}) : null)
// // if (item.isChecked) { type.push(item.name) } ;
// console.log(type);
// }
// }
render() {
console.log(this.state.items);
console.log(this.state.type);
const { onInputChange } = this.props;
return (
<div>
<h1 className="pt4"> פרטי תורם</h1>
<form className=" black-80 pt2" >
<section className=" grid-container">
<FormInput
id="name"
name="name"
type="text"
onInputChange={onInputChange}
label="שם "
required
/>
<FormInput
id="phone"
name="phone"
type="tel"
onInputChange={onInputChange}
label="מספר טלפון "
required
/>
<FormInput
id="address"
name="address"
type="text"
onInputChange={onInputChange}
label="כתובת "
required
/>
<FormInput
id="yeshuv"
name="yeshuv"
type="text"
onInputChange={onInputChange}
label="עיר "
required
/>
<FormInput
id="comments"
name="comments"
onInputChange={onInputChange}
label="הערות "
required
/>
<FormInput
id="area"
name="area"
onInputChange={onInputChange}
label="איזור "
select={selectAreasOptions_2}
/>
{/* type */}
<div className="measure-narrow">
<label htmlFor="type" className="f5 b db mb2">מעוניין לתרום
<span className="normal black-60"> *</span>
</label>
{
this.state.items.map( (item, i) => {
return (
<CheckBox
key={i}
onChange={this.handleCheckChieldElement}
checked={ item.isChecked }
value= {item.name}
label = {item.label}
/>
);
})
}
</div>
</section>
<input type="submit" value="שלח"
className="b bg-light-blue pa2 hover pointer"
onClick={this.onButtonSubmit}
/>
</form>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(donorDonationForm);
My main goal is that the type array - the final donation, will update the redux state before submitting this form. I tried with componentDidUpdate but didn't make it. What is the best way for tracking the checked items, updating the array and then update the type array which is the final donation in the redux state? should I do that in the onButtonSubmit method - before sending the data to the server (and thats way saving the looping over the items array for searching the checked elements) ?
Better approach would be do inside onButtonSubmit
Let me briefly explain the tasks:
inputChangeHandler to update this.state.items
Go with the final this.state.items value Array of items inside onButtonSubmit
After getting API response update the application level Redux state with Array of items.
Note: Dispatch the action. Reducer will update the Redux state. Following code will do this:
// Action
export const setItems = (data) => (dispatch) => {
dispatch({type: 'SET_ITEMS', payload: data})
}
// mapDispatchToProps
const mapDispatchToProps = (dispatch) =>
bindActionCreators(
{
setItems,
...others
},
dispatch
)
// onSubmitButton
onButtonSubmit = (event) => {
console.log(this.props.donorFields);
event.preventDefault();
fetch('http://localhost:8000/api/donor', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
...this.props.donorFields
})
})
.then(response => this.props.setItems(response.json())) // will update the state.
.then(resp => console.log(resp))
.catch( err => console.log(err) )
}
// Reducer
export const donorDonationInputsReducer = ( state = initialStateInputs, action={} ) => {
switch(action.type) {
case CHANGE_INPUT_FIELD:
return Object.assign( {}, state,
{
donorFields : {...state.donorFields,...action.payload},
donationFields: {...state.donationFields,...action.payload},
// items : {...state.items,...action.payload},
// isChecked: action.payload
})
case SET_ITEMS:
return {
...state,
items: action.payload
}
default:
return state;
}
}
That's it.
Happy Coding :)

How to do a search in the state (react)

I am able to store a data array containing the login and pass objects. I created an input field in which I write what I want to find in the state. How can I filter the state and display only matching items?
Constructor
class List extends Component {
constructor(props) {
super(props);
this.state = {
data: [
{ login: "login", pass: "pass" },
{ login: "login2", pass: "pass2" }
],
login: "",
pass: "",
find: ""
};
Adding and displaying data
add(e) {
e.preventDefault();
this.setState({
[e.target.name]: e.target.value
});
}
show(e) {
e.preventDefault();
if (!this.state.login.length || !this.state.pass.length) {
return;
} else {
const newUser = {
login: this.state.login,
pass: this.state.pass
};
this.setState(state => ({
data: state.data.concat(newUser)
}));
}
}
Search
filterUsers(e) {
let { data } = this.state;
//console.log(this.temp.login);
this.setState({
find: e.currentTarget.value
});
}
Render
<input onInput={this.filterUsers.bind(this)} />
<div>{this.state.find}</div>
{this.state.data.map((val, index) => (
<>
<td>{val.login}</td>
<td>{val.pass}</td>
<br />
<div />
</>
))}
What property are you filtering on? Login?
I recommend creating a filtered data array in your state so you don't modify the original.
filterUsers(event)
{
let filteredArray = this.state.data.filter((user) =>
{
user.login === event.currentTarget.value;
//or if you want partial matches
//user.login.includes(event.currentTarget.value)
})
this.setState({
filteredList: filteredArray
});
}
If you want to have your state.data remained untouched you can filter out your search-term in the render method.
filterUsers(e) {
this.setState({
find: e.currentTarget.value
});
}
<input onInput={this.filterUsers.bind(this)} />
<div>{this.state.find}</div>
{this.state.data.filter(x => x.login.includes(this.state.find)).map((val, index) => (
<>
<td>{val.login}</td>
<td>{val.pass}</td>
<br />
<div />
</>
))}

React: How to add an array of Ids to the state object from nested objects

I have one form with some textboxes, in another component I have a table with a row selection.
When the button in the bottom is clicked I should send the parameters I already sent but additionally in the webapi I should receive a List with the ids selected.
My main component is this:
import React, { Component } from 'react';
import { Input} from 'antd';
import Form from '../../components/uielements/form';
import Button from '../../components/uielements/button';
import Notification from '../../components/notification';
import { adalApiFetch } from '../../adalConfig';
import ListPageTemplatesWithSelection from './ListPageTemplatesWithSelection';
const FormItem = Form.Item;
class CreateCommunicationSiteCollectionForm extends Component {
constructor(props) {
super(props);
this.state = {Title:'',Url:'', SiteDesign:'', Description:'',Owner:'',Lcid:''};
this.handleChangeTitle = this.handleChangeTitle.bind(this);
this.handleValidationCommunicationSiteUrl = this.handleValidationCommunicationSiteUrl.bind(this);
this.handleChangeCommunicationSiteUrl = this.handleChangeCommunicationSiteUrl.bind(this);
this.handleChangeSiteDesign = this.handleChangeSiteDesign.bind(this);
this.handleChangeDescription = this.handleChangeDescription.bind(this);
this.handleChangeOwner = this.handleChangeOwner.bind(this);
this.handleChangelcid = this.handleChangelcid.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChangeTitle(event){
this.setState({Title: event.target.value});
}
handleValidationCommunicationSiteUrl(rule, value, callback){
const form = this.props.form;
const str = form.getFieldValue('communicationsiteurl');
var re = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]#!\$&'\(\)\*\+,;=.]+$/i;
if (str && !str.match(re)) {
callback('Communication site url is not correctly formated.');
}
else {
callback();
}
}
handleChangeCommunicationSiteUrl(event){
this.setState({Url: event.target.value});
}
handleChangeSiteDesign(event){
this.setState({SiteDesign: event.target.value});
}
handleChangeDescription(event){
this.setState({Description: event.target.value});
}
handleChangeOwner(event){
this.setState({Owner: event.target.value});
}
handleChangelcid(event){
this.setState({Lcid: event.target.value});
}
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
let data = new FormData();
//Append files to form data
//data.append(
const options = {
method: 'post',
body: JSON.stringify(
{
"Title": this.state.Title,
"Url": this.state.Url,
"SiteDesign": this.state.SiteDesign,
"Description": this.state.Description,
"Owner": this.state.Owner,
"Lcid": this.state.Lcid
}),
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
adalApiFetch(fetch, "/SiteCollection/CreateCommunicationSite", options)
.then(response =>{
if(response.status === 201){
Notification(
'success',
'Communication Site created',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Site collection not created',
error
);
console.error(error);
});
}
});
}
render() {
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 14 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 14,
offset: 6,
},
},
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Title" hasFeedback>
{getFieldDecorator('Title', {
rules: [
{
required: true,
message: 'Please input your communication site title',
}
]
})(<Input name="title" id="title" onChange={this.handleChangeTitle} />)}
</FormItem>
<FormItem {...formItemLayout} label="Communication Site Url" hasFeedback>
{getFieldDecorator('communicationSiteUrl', {
rules: [
{
required: true,
message: 'CommunicationSite site collection url',
},
{
validator: this.handleValidationCommunicationSiteUrl
}
]
})(<Input name="communicationsSiteUrl" id="communicationsSiteUrl" onChange={this.handleChangeCommunicationSiteUrl} />)}
</FormItem>
<FormItem {...formItemLayout} label="Site Design" hasFeedback>
{getFieldDecorator('sitedesign', {
rules: [
{
required: true,
message: 'Please input your site design',
}
]
})(<Input name="sitedesign" id="sitedesign" onChange={this.handleChangeSiteDesign} />)}
</FormItem>
<FormItem {...formItemLayout} label="Description" hasFeedback>
{getFieldDecorator('description', {
rules: [
{
required: true,
message: 'Please input your description',
}
],
})(<Input name="description" id="description" onChange={this.handleChangeDescription} />)}
</FormItem>
<FormItem {...formItemLayout} label="Owner" hasFeedback>
{getFieldDecorator('owner', {
rules: [
{
required: true,
message: 'Please input your owner',
}
],
})(<Input name="owner" id="owner" onChange={this.handleChangeOwner} />)}
</FormItem>
<FormItem {...formItemLayout} label="Lcid" hasFeedback>
{getFieldDecorator('lcid', {
rules: [
{
required: true,
message: 'Please input your lcid',
}
],
})(<Input name="lcid" id="lcid" onChange={this.handleChangelcid} />)}
</FormItem>
<ListPageTemplatesWithSelection />
<FormItem {...tailFormItemLayout}>
<Button type="primary" htmlType="submit">
Create communication site
</Button>
</FormItem>
</Form>
);
}
}
const WrappedCreateCommunicationSiteCollectionForm = Form.create()(CreateCommunicationSiteCollectionForm);
export default WrappedCreateCommunicationSiteCollectionForm;
and the nested component is this
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
class ListPageTemplatesWithSelection extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/PageTemplates", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.Id,
Name: row.Name
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render(){
const columns = [
{
title: 'Id',
dataIndex: 'key',
key: 'key',
},
{
title: 'Name',
dataIndex: 'Name',
key: 'Name',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListPageTemplatesWithSelection;
So basically every time the checkbox on each row is selected or unselected, then on the Parent component state I should add/remove the ID.
So that I can send it to the server when the button is pressed.
But I am not sure how to achieve this.
The best way to pass props from a child to a parent is by lifting the state up.
So the parent component would define a handleRowSelect(ids) function which handles taking the IDs of the currently selected rows. These can then be set in the state.
constructor(props) {
super(props);
this.state = {
selectedRows: [],
....
};
handleRowSelect(ids) {
this.setState({ selectedRows: ids });
}
It would pass the function and the selectedRows to the child component:
<ListPageTemplatesWithSelection onRowSelect={this.handleRowSelect) selectedRows={this.state.selectedRows} />
The child component would then have props called selectedRows and onRowSelect, which would call the handleRowSelect function of the parent. :
const rowSelection = {
selectedRowKeys: this.props.selectedRows,
onChange: (selectedRowKeys) => {
this.props.onRowSelect(selectedRowKeys);
}
};

React js conversion to typescript, adding data to const, interface

I have started working on react typescript. I am creating a drop down component using semantic ui. The problem is that semantic Ui provides code in java script format which is easier to understand. I need to convert the below code to typescript. I am successful in doing some of it but having problem converting handleAddition while adding new value to memberOptions.
Below is the code of JS.
I am not sure if I can use setState in typescript.
const memberOptions = [
{
text: 'Bruce',
value: 'bruce'
},
{
text: 'Clark',
value: 'clark'
},
{
text: 'Diana',
value: 'diana'
},
{
text: 'Peter',
value: 'peter'
}
];
class DropdownExampleAllowAdditions extends Component {
state = { memberOptions }
handleAddition = (e, { value }) => {
this.setState({
memberOptions: [{ text: value, value },
...this.state.memberOptions],
})
}
handleChange = (e, { value }) => this.setState({ currentValues: value })
render() {
const { currentValues } = this.state
return (
<Dropdown
options={this.state.options}
placeholder='Choose Languages'
value={currentValues}
onAddItem={this.handleAddition}
onChange={this.handleChange}
/>`enter code here`
)
}
}
I need to convert handleAddition to typescript. are there some rules regarding them?
Here is the solution. The trick is how you manage the state.
const options = [
{
text: 'Bruce',
value: 'bruce'
},
{
text: 'Clark',
value: 'clark'
},
{
text: 'Diana',
value: 'diana'
},
{
text: 'Peter',
value: 'peter'
}
];
interface Props {
options? : any
}
export default class DropdownExampleAllowAdditions extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
this.state = {options:{}};
}`enter code here`
const handleAddition = (e: {}, {value}: {value:string}) => {
this.setState({
options: [{ text: value, value }, ...this.state.options],
})
}
const handleChange = (e: {}, { value }: { value: string }) =>
form.setFieldValue(fieldName, value);
render() {
const { options } = this.props;
return (
<Dropdown
options={this.state.options}
placeholder='Choose Languages'
value={currentValues}
onAddItem={handleAddition}
onChange={handleChange}
/>`enter code here`
)
}
}

HowTo: update the value of an element in a property

So, I have a property (fields), within which I wish to change the value of an element (countries). Alerting the value of countries currently displays the value 2, but I want to change the value to 100, so that re-alerting fields.countries.value, after the change, displays the new value.
How do I do this?
import type { State } from '../../common/types';
import DynamicField from './DynamicField';
import R from 'ramda';
import React from 'react';
import buttonsMessages from '../../common/app/buttonsMessages';
import linksMessages from '../../common/app/linksMessages';
import { FormattedMessage } from 'react-intl';
import { ValidationError } from '../../common/lib/validation';
import { connect } from 'react-redux';
import { fields } from '../../common/lib/redux-fields';
import {
Block,
Box,
Button,
Checkbox,
FieldError,
Flex,
Form,
Heading,
Input,
PageHeader,
Pre,
Radio,
Select,
Space,
Title,
View,
} from '../app/components';
// The example of dynamically loaded editable data.
// cato.org/publications/commentary/key-concepts-libertarianism
const keyConceptsOfLibertarianism = [
'Individualism',
'Individual Rights',
'Spontaneous Order',
'The Rule of Law',
'Limited Government',
'Free Markets',
'The Virtue of Production',
'Natural Harmony of Interests',
'Peace',
].map((concept, index) => ({
id: index,
name: concept,
}));
// Proof of concept. Country list will be read from firebase
const countryArray = [
{ label: 'Select Country', value: 0 },
{ label: 'France', value: 2 },
{ label: 'England', value: 4 },
{ label: 'Swizterland', value: 8 },
{ label: 'Germany', value: 16 },
{ label: 'Lithuania', value: 32 },
{ label: 'Romania', value: 64 },
].map((countryName, index) => ({
id: index,
name: countryName,
}));
// Dynamically create select list
const countryOptions = [];
countryArray.map(countryItem =>
countryOptions.push({ label: countryItem.name.label, value: countryItem.name.value }),
);
// Proof of concept. Country list will be read from firebase
const cityArray = [
{ label: 'Select City', value: 0 },
{ label: 'London', value: 50 },
{ label: 'Paris', value: 75 },
].map((cityName, index) => ({
id: index,
name: cityName,
}));
// Dynamically create select list
const cityOptions = [];
cityArray.map(cityItem =>
cityOptions.push({ label: cityItem.name.label, value: cityItem.name.value }),
);
// Proof of concept. Country list will be read from firebase
const gymArray = [
{ label: 'Select Gym', value: 0 },
{ label: 'Virgin Sport', value: 23 },
{ label: 'Sports Direct', value: 45 },
].map((gymName, index) => ({
id: index,
name: gymName,
}));
// Dynamically create select list
const gymOptions = [];
gymArray.map(gymItem =>
gymOptions.push({ label: gymItem.name.label, value: gymItem.name.value }),
);
type LocalState = {
disabled: boolean,
error: ?Object,
submittedValues: ?Object,
};
class FieldsPage extends React.Component {
static propTypes = {
fields: React.PropTypes.object.isRequired,
dynamicFields: React.PropTypes.object,
// getCities: React.PropTypes.object,
};
state: LocalState = {
disabled: false,
error: null,
submittedValues: null,
};
onFormSubmit = () => {
const { dynamicFields, fields } = this.props;
const values = {
...fields.$values(),
concepts: {
...dynamicFields,
},
};
// This is just a demo. This code belongs to Redux action creator.
// Disable form.
this.setState({ disabled: true });
// Simulate async action.
setTimeout(() => {
this.setState({ disabled: false });
const isValid = values.name.trim();
if (!isValid) {
const error = new ValidationError('required', { prop: 'name' });
this.setState({ error, submittedValues: null });
return;
}
this.setState({ error: null, submittedValues: values });
fields.$reset();
}, 500);
};
handleSelectedCountryChange = () => {
// Pass in the selected country value to get associated cites
const { fields, getCities } = this.props;
getCities('country', fields.$values());
};
/*
handleSelectedCityChange = (event => {
// Pass in the selected city value to get associated gyms
this.setState({secondLevel: event.target.value});
});
*/
render() {
const { fields } = this.props;
const { disabled, error, submittedValues } = this.state;
return (
<View>
<Title message={linksMessages.fields} />
<PageHeader
description="New clients enter their gym details here."
heading="New user entry form."
/>
<Form onSubmit={this.onFormSubmit}>
<Input
{...fields.name}
aria-invalid={ValidationError.isInvalid(error, 'name')}
disabled={disabled}
label="Your Name"
maxLength={100}
type="text"
/>
<FieldError error={error} prop="name" />
<Heading alt>Key Concepts of Libertarianism</Heading>
<Block>
<Flex wrap>
{keyConceptsOfLibertarianism.map(item =>
<Box mr={1} key={item.id}>
<DynamicField
disabled={disabled}
item={item}
path={['fieldsPage', 'dynamic', item]}
/>
</Box>,
)}
</Flex>
</Block>
<Block>
<Checkbox
{...fields.isLibertarian}
checked={fields.isLibertarian.value}
disabled={disabled}
label="I'm libertarian"
/>
<Checkbox
{...fields.isAnarchist}
checked={fields.isAnarchist.value}
disabled={disabled}
label="I'm anarchist"
/>
</Block>
<Block>
<Flex>
<Radio
{...fields.gender}
checked={fields.gender.value === 'male'}
disabled={disabled}
label="Male"
value="male"
/>
<Space x={2} />
<Radio
{...fields.gender}
checked={fields.gender.value === 'female'}
disabled={disabled}
label="Female"
value="female"
/>
<Space x={2} />
<Radio
{...fields.gender}
checked={fields.gender.value === 'other'}
disabled={disabled}
label="Other"
value="other"
/>
</Flex>
</Block>
<Block>
<Select
{...fields.countries}
disabled={disabled}
label="Countries"
onChange={this.handleSelectedCountryChange}
options={countryOptions}
/>
</Block>
<Block>
<Select
{...fields.cities}
disabled={disabled}
label="Cities"
// onChange={this.handleSelectedCityChange}
options={cityOptions}
/>
</Block>
<Block>
<Select
{...fields.gyms}
disabled={disabled}
label="Gyms"
// onChange={this.handleSelectedCityChange}
options={gymOptions}
/>
</Block>
{/*
Why no multiple select? Because users are not familiar with that.
Use checkboxes or custom checkable dynamic fields instead.
*/}
<Button disabled={disabled} type="submit">
<FormattedMessage {...buttonsMessages.submit} />
</Button>
{submittedValues &&
<Pre>
{JSON.stringify(submittedValues, null, 2)}
</Pre>
}
</Form>
</View>
);
}
}
FieldsPage = fields({
path: 'fieldsPage',
fields: [
'countries',
'cities',
'gyms',
'gender',
'isAnarchist',
'isLibertarian',
'name',
],
getInitialState: () => ({
countries: '0',
cities: '0',
gyms: '0',
gender: 'male',
isAnarchist: false,
isLibertarian: false,
}),
})(FieldsPage);
export default connect(
(state: State) => ({
dynamicFields: R.path(['fieldsPage', 'dynamic'], state.fields),
}),
)(FieldsPage);
=====================================================================
fields.js
/* #flow weak */
import R from 'ramda';
import React from 'react';
import invariant from 'invariant';
import { resetFields, setField } from './actions';
type Path = string | Array<string> | (props: Object) => Array<string>;
type Options = {
path: Path,
fields: Array<string>,
getInitialState?: (props: Object) => Object,
};
const isReactNative =
typeof navigator === 'object' &&
navigator.product === 'ReactNative'; // eslint-disable-line no-undef
// Higher order component for huge fast dynamic deeply nested universal forms.
const fields = (options: Options) => (WrappedComponent) => {
const {
path = '',
fields = [],
getInitialState,
} = options;
invariant(Array.isArray(fields), 'Fields must be an array.');
invariant(
(typeof path === 'string') ||
(typeof path === 'function') ||
Array.isArray(path)
, 'Path must be a string, function, or an array.');
return class Fields extends React.Component {
static contextTypes = {
store: React.PropTypes.object, // Redux store.
};
static getNormalizePath(props) {
switch (typeof path) {
case 'function': return path(props);
case 'string': return [path];
default: return path;
}
}
static getFieldValue(field, model, initialState) {
if (model && {}.hasOwnProperty.call(model, field)) {
return model[field];
}
if (initialState && {}.hasOwnProperty.call(initialState, field)) {
return initialState[field];
}
return '';
}
static lazyJsonValuesOf(model, props) {
const initialState = getInitialState && getInitialState(props);
// http://www.devthought.com/2012/01/18/an-object-is-not-a-hash
return options.fields.reduce((fields, field) => ({
...fields,
[field]: Fields.getFieldValue(field, model, initialState),
}), Object.create(null));
}
static createFieldObject(field, onChange) {
return isReactNative ? {
onChangeText: (text) => {
onChange(field, text);
},
} : {
name: field,
onChange: (event) => {
// Some custom components like react-select pass the target directly.
const target = event.target || event;
const { type, checked, value } = target;
const isCheckbox = type && type.toLowerCase() === 'checkbox';
onChange(field, isCheckbox ? checked : value);
},
};
}
state = {
model: null,
};
fields: Object;
values: any;
unsubscribe: () => void;
onFieldChange = (field, value) => {
const normalizedPath = Fields.getNormalizePath(this.props).concat(field);
this.context.store.dispatch(setField(normalizedPath, value));
};
createFields() {
const formFields = options.fields.reduce((fields, field) => ({
...fields,
[field]: Fields.createFieldObject(field, this.onFieldChange),
}), {});
this.fields = {
...formFields,
$values: () => this.values,
$setValue: (field, value) => this.onFieldChange(field, value),
$reset: () => {
const normalizedPath = Fields.getNormalizePath(this.props);
this.context.store.dispatch(resetFields(normalizedPath));
},
};
}
getModelFromState() {
const normalizedPath = Fields.getNormalizePath(this.props);
return R.path(normalizedPath, this.context.store.getState().fields);
}
setModel(model) {
this.values = Fields.lazyJsonValuesOf(model, this.props);
options.fields.forEach((field) => {
this.fields[field].value = this.values[field];
});
this.fields = { ...this.fields }; // Ensure rerender for pure components.
this.setState({ model });
}
componentWillMount() {
this.createFields();
this.setModel(this.getModelFromState());
}
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(() => {
const newModel = this.getModelFromState();
if (newModel === this.state.model) return;
this.setModel(newModel);
});
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<WrappedComponent {...this.props} fields={this.fields} />
);
}
};
};
export default fields;

Categories

Resources