On an <EditView /> I am using <ReferenceArrayField /> to display an edit form of checkbox.
I need to fill the form with the data, but the checkbox list is so far implemented using the response entities from the ReferenceArrayField.
I have an the user entity fetched by the <EditView/>
In <EditView/> I am fetching related data using `
I write a custom grid using the <ReferenceArrayField /> data
I want to fill the checked props of my checkbox depending on values present in user.
This would be some trivial controller logic for the UI but I can't find how to do it.
So far I am only able to have one record by scope which means that every data displayed on the UI would be from a single entity and no controller logic can be written in between.
Use case
const CheckboxGrid = ({ ids, data, basePath }) => (
<div style={{ margin: '1em' }}>
{ids.map(id =>
<div key={id}>
<h2><TextField record={data[id]} source="description" /></h2>
<label>
<input type="checkbox" name="authority[]" disabled checked={true} />{' '}
<TextField record={data[id]} source="role.description" />
</label>
{data[id].siteFunctionList.map((siteFunction) => (
<div key={siteFunction.description} className="pl-3">
<h3><TextField record={siteFunction} source="description" disabled /></h3>
<label>
<input type="checkbox" name="authority[]" disabled />{' '}
<TextField record={siteFunction} source="role.description" />
</label>
</div>
))}
</div>
)}
</div>
);
CheckboxGrid.defaultProps = {
data: {},
ids: [],
};
export class AuthorityList extends React.Component {
render() {
const resourceName = 'users';
const resourceId = this.props.match.params.id;
return (
<Edit {...this.props} resource={resourceName} id={resourceId}>
<SimpleForm>
<ReferenceArrayField reference="siteServices" source="siteServiceIdList">
<CheckboxGrid />
</ReferenceArrayField>
</SimpleForm>
</Edit>
);
}
}
The user have a list of values that would match some of the values present in data.
Fact
I do not have access to the <Edit .../> record in the scope of checkbox grid.
I have seen that there is in redux a form['record-form'].values where the entity is store so I could access it with custom selectors:
EX:
const mapStateToProps = createStructuredSelector({
record: makeSelectFormRecordFormValues(),
});
const withConnect = connect(mapStateToProps);
const CheckboxGridConnect = compose(
withConnect,
)(CheckboxGrid);
There is also a store for each resources declared in admin on rest. It store the entities fetched with <ReferenceArrayField /> but I don't see these stores scalable in case we use more than one <ReferenceArrayField /> for the same resource.
Should I directly pick in the store and inject my self the entities ?
Please find below my hacky code for SelectInput. I have made this to basically edit an array of values. The API detects any changes in the array of values and updates the references accordingly. Below is a copy paste job from the AOR core and then some modifications for my use case.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import get from 'lodash.get';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
export class CustomSelectInput extends Component {
/*
* Using state to bypass a redux-form comparison but which prevents re-rendering
* #see https://github.com/erikras/redux-form/issues/2456
*/
state = {
value: this.props.fields.get(this.props.selectionOrder) ? this.props.fields.get(this.props.selectionOrder) : null //selectionOrder is a prop I am passing to the component manually. It is very specific to my use case
}
handleChange = (event, index, value) => {
this.props.fields.remove(this.props.selectionOrder) //using redux-form fields methods to remove items and adding new ones. SelectionOrder now becomes an unfortunate hard coding of the particular value this selectInput is supposed to edit in the array of values
this.props.fields.push(value);
this.setState({ value });
}
renderMenuItem = (choice) => {
const {
optionText,
optionValue,
} = this.props;
const choiceName = React.isValidElement(optionText) ? // eslint-disable-line no-nested-ternary
React.cloneElement(optionText, { record: choice }) :
(typeof optionText === 'function' ?
optionText(choice) :
get(choice, optionText)
);
return (
<MenuItem
key={get(choice, optionValue)}
primaryText={choiceName}
value={choice.name}
/>
);
}
render() {
const {
allowEmpty,
choices,
elStyle,
input,
isRequired,
label,
meta: { touched, error },
options,
resource,
source,
} = this.props;
return (
<SelectField
value={this.state.value}
onChange={this.handleChange}
autoWidth
style={elStyle}
errorText={touched && error}
{...options}
>
{allowEmpty &&
<MenuItem value={null} primaryText="" />
}
{choices.map(this.renderMenuItem)}
</SelectField>
);
}
}
CustomSelectInput.propTypes = {
addField: PropTypes.bool.isRequired,
allowEmpty: PropTypes.bool.isRequired,
choices: PropTypes.arrayOf(PropTypes.object),
elStyle: PropTypes.object,
input: PropTypes.object,
isRequired: PropTypes.bool,
label: PropTypes.string,
meta: PropTypes.object,
options: PropTypes.object,
optionText: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.element,
]).isRequired,
optionValue: PropTypes.string.isRequired,
resource: PropTypes.string,
source: PropTypes.string
};
CustomSelectInput.defaultProps = {
addField: true,
allowEmpty: false,
choices: [],
options: {},
optionText: 'name',
optionValue: 'id'
};
export default CustomSelectInput
Related
I'm very new to coding and trying to figure out an issue I have come across.
I am using axios to pull a json file and store it in a state. (I am also using Redux to populate the form)
Then I am using .map() to dissect the array and show one value from within each object in the array.
example json:
unit :
[
{
designName : x,
quantity : 0,
},
{
designName : y,
quantity : 0,
},
{
designName : z,
quantity : 0,
}
]
I have then added an input to select the quantity of the value mapped and now I want to give that value back to the state, in order to send the entire modified json back to the API with Axios.
I feel like I'm close but I'm unsure what I need to do with the handleQuantity function.
Here's my code:
import React, { Component } from 'react';
import store from '../../redux_store'
import axios from 'axios';
import { Button, Card } from 'react-bootstrap'
import { Link } from 'react-router-dom'
store.subscribe(() => {
})
class developmentSummary extends Component {
constructor(props) {
super(props)
this.state = {
prjName: store.getState()[0].developmentName,
units: []
}
}
componentDidMount() {
axios.get('https://API')
.then(
res => {
console.log(res)
this.setState({
units: res.data.buildings
})
console.log(this.state.units.map(i => (
i.designName
)))
}
)
}
handleQuantity() {
}
render() {
return (
<>
<div className="Text2">
{this.state.prjName}
</div>
<div className="Text2small">
Please select the quantity of buildings from the list below
</div>
<ul>
{this.state.units.map((object, i) => (
<div className="Card-center">
<Card key={i} style={{ width: "50%", justifyContent: "center" }}>
<Card.Body>{object.designName}</Card.Body>
<Card.Footer>
<input
className="Number-picker"
type="number"
placeholder="0"
onChange={this.handleQuantity}
/>
</Card.Footer>
</Card>
</div>
))}
</ul>
Thanks in advance!
You have to pass the change event, unit object and the index to handleQuantity and then paste your changed unit as new object in between unchanged units.
Here is the code:
<input
className="Number-picker"
type="number"
placeholder="0"
onChange={(event) => this.handleQuantity(event, object, i)}
/>;
And the code for handleQuantity
handleQuantity = (event, unit, index) => {
const inputedNumber = +event.target.value; // get your value from the event (+ converts string to number)
const changedUnit = { ...unit, quantity: inputedNumber }; // create your changed unit
// place your changedUnit right in between other unchanged elements
this.setState((prevState) => ({
units: [
...prevState.units.slice(0, index),
changedUnit,
...prevState.units.slice(index + 1),
],
}));
}
Im trying to set a boolean value on an object to submit them all together, all the values comes from a form and most from text inputs, all text inputs are setting correctly except for my boolean element, Im not understanding exactly what Im doing wrong so any help is very appreciated. Heres my code:
import React, {useState} from 'react';
import {Col} from "react-bootstrap";
import EditorElement from "../components/editorElement";
import BootstrapSwitchButton from "bootstrap-switch-button-react";
export default function ListDetails({listdetails, updateData, updatingData, selectedRow}) {
const [input, setInput, bool] = useState(
{
enabled: listdetails.enabled, // <-- Here I set (or want to set) the value of my boolean, the "listdetails" parameter are the default values that comes from initial fetch.
name: listdetails.name,
custom1: listdetails.custom1,
id: selectedRow
}
);
const updateInputs = event => setInput({...input, [event.target.name]: event.target.value});
const updateInputsBoolean = event => setInput({...input, [event.target.name]: event.target.checked}); //<-- Here I updatethe value of my bool element
const{enabled, name, hooktype, custom1, custom2, custom3, custom4, custom5, endpoint} = input; // <-- Here I update all the values
const handelSubmit = evt => {
console.log(input);
updateData(input, bool); //<-- Here I set the collected object and set it but "bool" is not setting
};
function handleBoolean(ev) { // <-- If I run this function in the boolean onChange I get the true or false value on console but I couldnt set it in the updateData (that comes from another component where I have the axios post)
const setBool = ev.toString();
console.log(setBool);
// updateData(setBool);
}
return (
<div>
<Col className={'col-md-6'}>
<EditorElement name='Enable/ Disabled '>
<div>
<BootstrapSwitchButton
checked={enabled === true}
onstyle="primary"
offstyle="danger"
name={'enabled'}
value={enabled}
onChange={e => updateInputsBoolean(e)} // <-- Here is my boolean element that is a bootstrap switch
/>
</div>
{listdetails.enabled ? 'is enabled' : 'is not enabled'}
</EditorElement>
<EditorElement name='Name'>
<input
type={'text'}
defaultValue={name}
name={'name'}
className={'form-control'}
onChange={e => updateInputs(e)}
/>
</EditorElement>
</Col>
<Col className={'col-md-6'}>
<EditorElement name='Custom1'>
<input
type={'text'}
name={'custom1'}
defaultValue={custom1}
className={'form-control'}
onChange={e => updateInputs(e)}
/>
</EditorElement>
</Col>
<Col style={{'marginBottom': '30px', 'marginTop': '20px'}} className={'col-md-12 text-right'}>
<button
style={{'marginTop': '15px', 'marginBottom': '15px'}}
type={'button'}
className={'btn btn-primary'}
onClick={handelSubmit} //<-- Here I handle my submit
>
{updatingData ? 'Updating...' : 'Save'}
</button>
</Col>
</div>
);
}
Thanks in advance for any help!
#DrewReese helped me understand this, I was confusing BootstrapSwitchButton as a normal input and it is not like that. Here is the working code now:
const [input, setInput] = useState(
{
enabled: listdetails.enabled,
name: listdetails.name,
custom1: listdetails.custom1,
id: selectedRow
}
);
const updateInputs = event => setInput({...input, [event.target.name]: event.target.value});
const{enabled, name, custom1} = input;
const handelSubmit = evt => {
console.log(input);
updateData(input);
};
function handleBoolean(ev) {
const setBool = ev.toString();
console.log(setBool);
setInput({...input, enabled: ev}); //<-- I wasn't updating the enabled prop event here and now I make a copy of all inputs and set the event of enabled... VOILA! is working :)
}
Thanks agan #DrewReese for helping me understand that
I'm putting together a little POC, where one piece of it is executing a Search function. The idea is that "Search" will be responsible for the following things:
- Displaying the search input form (e.g., text, date and locations parameters)
- Hit the backend AWS Lambda search API
- Return the result object back to the Search object
- Be able to be re-used on multiple pages
I have two different pages that I want to leverage the "search" functionality, but render the results in different ways.
The search component/form works stand-alone, but I can't figure out how to embed it on the other web pages. Whenever I try to input anything into the "Search form" the console throws the following error:
index.js:2178 Warning: setState(...): Can only update a mounted or mounting > component. This usually means you called setState() on an unmounted component. > This is a no-op.
Please check the code for the Search component.
The "Search" code is below, along with the start of page to display results. I'm a novice to front-end dev so I may be doing something stupid here...looking for input on what I'm doing wrong! Thanks for the help.
import React, { Component } from "react";
import {FormGroup, FormControl, ControlLabel } from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./Home.css";
import DatePicker from 'react-datepicker';
import moment from 'moment';
import PlacesAutocomplete from 'react-places-autocomplete';
import { searchAssets } from "../libs/awsLib";
import 'react-datepicker/dist/react-datepicker.css';
// CSS Modules, react-datepicker-cssmodules.css
import 'react-datepicker/dist/react-datepicker-cssmodules.css';
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
freeText: "",
startDate: null,
endDate: null,
radius: 5,
address: '',
searchResults: null
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleStartDateChange = this.handleStartDateChange.bind(this);
this.handleEndDateChange = this.handleEndDateChange.bind(this);
this.handleLocationChange = this.handleLocationChange.bind(this);
}
// Basic change handling
handleChange = event => {
this.setState({ [event.target.id]: event.target.value });
}
// Location change needed because event.id cannot be taken from places object
handleLocationChange = address => {
console.log("The address has been changed: " + address);
this.setState({ address: address });
}
// I need two separate handlers for each piece of the date range
handleStartDateChange = date => {
this.setState({ startDate: date });
}
handleEndDateChange = date => {
this.setState({ endDate: date });
}
//
handleSubmit = async event => {
event.preventDefault();
console.log(event);
// Construct the query string for AWS search
var queryStatement = {
accountId: 'account1', // Dummy
groupId: 'group1', // Dummy
caseId: '999', // Dummy
freeText: this.state.freeText,
radius: this.state.radius
};
if (this.state.startDate != null) {
queryStatement['startDate'] = this.state.startDate.format("MM/DD/YYYY");
}
if (this.state.endDate != null) {
queryStatement['endDate'] = this.state.endDate.format("MM/DD/YYYY");
}
if (this.state.address !== '') {
queryStatement['address'] = this.state.address
}
console.log(queryStatement);
this.setState({ isLoading: true });
// Submit to the search API and load the Payload as a JSON object
try {
var resultSet;
resultSet = await searchAssets(queryStatement);
if (resultSet['StatusCode'] !== 200) {
console.log("Error in lambda function");
}
console.log(JSON.parse(resultSet['Payload']));
this.setState({
isLoading: false,
searchResults: JSON.parse(resultSet['Payload'])
});
}
catch (e) {
console.log(e);
this.setState({ isLoading: false });
}
}
render() {
const autoLocationProps = {
value: this.state.address,
onChange: this.handleLocationChange,
}
console.log(this.state);
// Only fetch suggestions when the input text is longer than 3 characters.
const shouldFetchSuggestions = ({ value }) => value.length > 3
return (
<div className="Search">
<form onSubmit={this.handleSubmit}>
<FormGroup controlId="freeText" bsSize="large">
<ControlLabel>Enter text to search on</ControlLabel>
<FormControl
type="textarea"
onChange={this.handleChange}
value={this.state.freeText}
placeholder="Enter any values to search on"
/>
</FormGroup>
<FormGroup controlId="address" bsSize="large">
<ControlLabel>Enter search location</ControlLabel>
<PlacesAutocomplete
inputProps={autoLocationProps}
shouldFetchSuggestions={shouldFetchSuggestions}
placeholderText="Start typing an address"
/>
</FormGroup>
<FormGroup controlId="radius" bsSize="large">
<ControlLabel>Enter search radius</ControlLabel>
<FormControl
onChange={this.handleChange}
type="text"
value={this.state.radius}
/>
</FormGroup>
<FormGroup controlId="startDate" bsSize="large">
<ControlLabel>Enter start date</ControlLabel>
<DatePicker
onChange={this.handleStartDateChange}
selected={this.state.startDate}
placeholderText="Enter start date"
isClearable={true}
maxDate={this.state.endDate}
/>
</FormGroup>
<FormGroup controlId="endDate" bsSize="large">
<ControlLabel>Enter end date</ControlLabel>
<DatePicker
onChange={this.handleEndDateChange}
selected={this.state.endDate}
placeholderText="Enter end date"
isClearable={true}
minDate={this.state.startDate}
/>
</FormGroup>
<LoaderButton
block
bsSize="large"
type="submit"
isLoading={this.state.isLoading}
text="Search for files"
loadingText="Searching..."
/>
</form>
</div>
);
}
}
View as a table:
import React, { Component } from "react";
import {Table, thead, tbody, th, tr, td } from "react-bootstrap";
import Search from "./Search";
import "./Home.css";
export default class Viewfiles extends Component {
constructor(props) {
super(props);
}
render() {
// Eventually we will render the results in the table...
var resultsObject = new Search();
return (
<div className="Viewfiles">
{resultsObject.render()}
<hr/>
<Table striped condensed responsive >
<thead>
<tr>
<th>Thumbnail</th>
<th>Checksum</th>
<th>Address</th>
<th>Checksum</th>
<th>Description</th>
</tr>
</thead>
</Table>
</div>
);
}
}
React components are meant to be rendered in JSX, like <Search />. If you instantiate them on your own, React won't know to mount them properly, with state and everything.
I've got a component that creates a field with list of Radio inputs. The created component seems to work, it is rendered correctly, dispatches the proper redux actions, and update it's state properly. However the selected radio input never gets checked in the UI.
import React from 'react';
import Immutable from 'immutable';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Field } from 'react-redux-form/immutable';
const RadioField = ({ fieldId, label, model, hasError, options }) => {
const error = hasError ? 'has-error' : '';
return (
<div className={`form-group ${error}`}>
<label className="control-label" htmlFor={fieldId}>{label}</label>
<Field model={model} id={fieldId} >
{options.map(option => (
<label
key={`${fieldId}-${option.get('id')}`}
htmlFor={`radio-${fieldId}-${option.get('value')}`}
className="radio-inline"
>
<input
id={`radio-${fieldId}-${option.get('value')}`}
value={`${option.get('value')}`}
type="radio"
/>
{option.get('display')}
</label>
))}
</Field>
</div>
);
};
RadioField.defaultProps = {
fieldId: 'radio-field-id',
label: 'Radio Field:',
model: '.radio',
hasError: false,
options: Immutable.List(),
};
RadioField.propTypes = {
fieldId: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
model: PropTypes.string.isRequired,
hasError: PropTypes.bool,
options: ImmutablePropTypes.listOf(
ImmutablePropTypes.shape({
id: PropTypes.number,
display: PropTypes.string,
value: PropTypes.any,
}),
).isRequired,
};
export default RadioField;
My gut tells me it has something to do with the Field not being able to properly locate the input that has the selected value or the redux state isn't getting passed properly to the Field's children Control components.
Any help would be greatly appreciated. Thanks!
After looking into the issue more and it turned out the created immutable Control.radio components weren't getting the modelValue from the app's redux state. Changing the component from a Field with inputs to multiple generic Control components and giving them the proper props for a radio input seemed to fix the issue.
Here's what the code looks like.
import React from 'react';
import Immutable from 'immutable';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Control } from 'react-redux-form/immutable';
const RadioField = ({ fieldId, label, model, hasError, options }) => {
const error = hasError ? 'has-error' : '';
return (
<div className={`form-group ${error}`}>
<label className="control-label" htmlFor={fieldId}>{label}</label>
<div id={fieldId}>
{options.map(option => (
<label key={`${fieldId}-${option.get('id')}`} htmlFor={`${fieldId}-${option.get('id')}`} className="radio-inline">
<Control
id={`${fieldId}-${option.get('id')}`}
value={option.get('value')}
model={model}
isToggle
type="radio"
/>
{option.get('display')}
</label>
))}
</div>
</div>
);
};
RadioField.defaultProps = {
fieldId: 'radio-field-id',
label: 'Radio Field:',
model: '.radio',
hasError: false,
options: Immutable.List(),
};
RadioField.propTypes = {
fieldId: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
model: PropTypes.string.isRequired,
hasError: PropTypes.bool,
options: ImmutablePropTypes.listOf(
ImmutablePropTypes.shape({
id: PropTypes.number,
display: PropTypes.string,
value: PropTypes.any,
}),
).isRequired,
};
export default RadioField;
I'll look into recreating the issue and submit a issue on react-redux-form's github.
I am using react-tagsinput, react-input-autosize, and react-mailcheck to create input tags that also suggests the right domain when the user misspell it in an email address.
I have gotten the react-tagsinput to work with react-input-autosize but when added with react-mailcheck my input form does not work at all, the form is un-clickable and unable to type and text into the field. I'm not getting any errors in the console and i'm not sure what is wrong with my code. I followed what they did in the react-mailcheck documentation: https://github.com/eligolding/react-mailcheck. I was hoping someone could look at it with a fresh pair of eyes to see what I am missing that is making it not work.
Thanks!
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import TagsInput from 'react-tagsinput';
import AutosizeInput from 'react-input-autosize';
import MailCheck from 'react-mailcheck';
class EmailInputTags extends Component {
static propTypes = {
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
};
constructor() {
super();
this.state = { tags: [], inputText: '' };
this.handleChange = this.handleChange.bind(this);
this.handleInputText = this.handleInputText.bind(this);
this.renderInput = this.renderInput.bind(this);
}
handleChange(tags) {
this.setState({ tags });
}
handleInputText(e) {
this.setState({ inputText: e.target.value });
}
renderInput({ addTag, ...props }) {
const { ...other } = props;
return (
<MailCheck email={this.state.inputText}>
{suggestion => (
<div>
<AutosizeInput
type="text"
value={this.state.inputText}
onChange={this.handleInputText}
{...other}
/>
{suggestion &&
<div>
Did you mean {suggestion.full}?
</div>
}
</div>
)}
</MailCheck>
);
}
render() {
const { label, name } = this.props;
return (
<div className="input-tag-field">
<TagsInput inputProps={{ placeholder: '', className: 'input-tag' }} renderInput={this.renderInput} value={this.state.tags} onChange={this.handleChange} />
<label htmlFor={name}>{label}</label>
</div>
);
}
}
export default EmailInputTags;
I have not tried this out.
Try passing as a prop to TagsInput the onChange function.
ie
{... onChange={(e) => {this.setState(inputText: e.target.value}}