How to make multiple inputs target the same source? - admin-on-rest

How can I create a custom input where several inputs affect only one source?
I have a custom time input as "hours:minutes:seconds" that should save the time as seconds.
What I have so far:
// calling the custom input with the expected source
<TimeInput source="time_A" />
// in the TimeInput component
<span>
<Field component={NumberInput} name="hh" value={this.state.hh} onChange={this.handleChange} />
<Field component={NumberInput} name="mm" value={this.state.mm} onChange={this.handleChange} />
<Field component={NumberInput} name="ss" value={this.state.ss} onChange={this.handleChange} />
</span>
The handleChange method parses the entered value according to the name of the Field and should update the original source (in this case: "time_A"). That update is what I really can't figure out how to do.
I think the solution would be using the this.props.input.onChange but I must be doing something wrong because my this.props.input is undefined.
Any thoughts?

I am using the below component to capture date and time inputs from the user. You can probably use the same strategy to concatenate all your inputs.
class DateTimePicker extends Component {
constructor(props) {
super(props);
this.state = {
date: null,
time: null
};
}
handleDateInput = (event, date) => {
this.setState({
date: date
})
}
handleTimeInput = (event, time) => {
this.setState({
time: time
})
}
componentDidUpdate(prevProps, prevState) {
const {setSchedule, channel} = this.props
//check for state update before actually calling function otherwise permanent re-renders will happen and page will lock`
//https://stackoverflow.com/questions/44713614/react-code-locking-in-endless-loop-no-while-loops-involved/44713764?noredirect=1#comment76410708_44713764
if (prevState.date !== this.state.date || prevState.time !== this.state.time) {
if (this.state.date && this.state.time) {
setSchedule(convertISTtoUTC(concatenateDateAndTime(this.state.date, this.state.time)), channel)
}
}
}
render() {
//id must be provided to date and time mui components - https://github.com/callemall/material-ui/issues/4659
return (
<div >
</div>
<DatePicker minDate={new Date()} id="datepick" autoOk={true} onChange={this.handleDateInput} />
<TimePicker id="timepick" autoOk={true} pedantic={true} onChange={this.handleTimeInput} />
</div>
</div>
)
}
}
Below is the function to concatenate the date and time and create 1 object from it. You will probably be passing it a DD, MM, YY object and creating the date from it.
export default (date, time) => {
return new Date(
date.getFullYear(),
date.getMonth(),
date.getDate(),
time.getHours(),
time.getMinutes()
)
}

Related

How to filter through content of component with a string passed through the props?

With a text field in the parent component, I want to filter through multiple child components where the string is being passed through the props. The child components have been outputted through a map function which is importing data from an API into the props also. I have the user input console logging after being passed as props to the child (searchTerm). My problem is that I can't hide the display of individual components based on the user's input. The trouble I'm having is that when one decides to hide itself - they all do. I've tried indexOf and have found include() to be more useful. I'm worried about my approach to the problem and would appreciate some wisdom from more seasoned react developers.
//parent component
render() {
return (
<React.Fragment>
<input type="text" className = {styles.searchField} id="searchField" placeholder = "Search..." onChange = {this.getUserInput}/>
<div className={styles.container}>
{this.state.importedBooks.map((element, index) => (
<Tile key ={index} searchTerm ={this.state.searchTerm} buy ={element.amazon_product_url}
img ={element.book_image} summary ={element.description} url={element.book_image}
book_author ={element.author} book_title ={element.title} />
))}
</div>
</React.Fragment>);
}
}
//child component
class Tile extends React.Component {
public state:{display:boolean} = {display:true};
public getContainerContent = () => {
const containers: NodeListOf<Element> | null = document.querySelectorAll("#container");
containers.forEach(element => {
if (element.innerHTML.toLocaleLowerCase().includes(this.props.searchTerm) !== false) {
this.setState({display:true});
}
else if (this.props.searchTerm == "") {
this.setState({display:true});
}
else {
this.setState({ display: false });
}
})
};
public componentWillReceiveProps = () => {
this.getContainerContent();
}
render() {
const renderContainer = this.state.display ? styles.container : styles.hideDisplay;
return (
<div className={styles.container} id="container">
<h1>{this.props.book_title}</h1>
<h2>{this.props.book_author}</h2>
<img src = {this.props.img} alt="book cover"/>
<p>{this.props.summary}</p>
Purchase
</div> );
}
}
I think the problem stems from the getContainerContent method in your child component.
Why would a single child component be concerned with data that belongs to other child components?
A way you can solve this is to first declare in the parent component which props are you going to match the filter against when displaying child components.
Then, you could iterate over these declared props and decide whether the component should display a child component or not.
// parent comp
private shouldDisplayElement (element, searchTerm: string): boolean {
const propsToMatchFiltersAgainst = ['book_title', 'book_author', 'buy', /* etc... */];
let shouldDisplay = false;
for (const p of propsToMatchFiltersAgainst) {
if (`${element[p]}`.toLowerCase().includes(searchTerm.trim())) {
shouldDisplay = true;
}
}
return shouldDisplay;
}
// parent's render fn
<div className={styles.container}>
{this.state.importedBooks.map((element, index) => (
this.shouldDisplayElement(element, this.state.searchTerm)
? <Tile
key={index}
buy={element.amazon_product_url}
img={element.book_image}
summary={element.description}
url={element.book_image}
book_author={element.author} book_title={element.title}
/>
: null
))}
</div>
This way, given the current situation, you don't need to use the getContainerContent method in your child component anymore.

How to retrieve an input form value with redux-form and formValueSelector

I am trying to retrieve a single input value and log it. This is an edit form with existing name value prop.
I am not setting state 'name' with field input's value for some reason. I am not sure how to structure the connect part which I think is my problem. In particular, I am not clear on how to write mapStateToProps to include both my non-form state and form state.
partial scaled down code:
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { connect } from 'react-redux';
import { updateStable } from '../../actions/index';
const selector = formValueSelector('myEditForm');
class EditStuff extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name
};
}
componentDidMount() {
this.props.initialize({
name: this.props.name || ''
});
}
componentDidUpdate() {
// this.state.name is not getting set from input value
this.props.updateLocalActiveStuffData(this.state.name);
}
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label="Name"
name="name"
class="name"
type="text"
component={renderField}
/>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
displayEditForm: state.displayEditForm, //my own non-form related state
name: selector(state, 'name') //form input 'name'
};
}
export default connect(mapStateToProps, { updateStuff })(
reduxForm({
form: 'myEditForm'
})(EditStuff)
);
I think if you can't retrieve the value it's because the name of your form in reduxForm and the formValueSelector name are different 'StableEditForm' != 'myEditForm'
You have more info on selectors here
If you want to initialise your form with values, you should set it from your state in mapStateToProps with the initialValues props, something like this:
function mapStateToProps(state) {
return {
initialValues: state.displayEditForm, // value of your form
};
}
A great exemple here
I hope this can help you

What is the good way to have a multi input field in Redux Form?

In my project we are building a form with React and Redux-Form. We have a single information that is composed by the value of two inputs. But the values of each input is combined and validated together.
The first implementation try was by connecting each component with Field. It let us update the state properly but we couldn't validate all the values together with the validate prop.
The second Try was using Fieldscomponent but it does not have a validate prop. We thought in create a Pull Request for it but the API for it isn't clear yet, since what we want to validate the combination of the two values and the behavior of the Fields props (such as parse and format) is different, executing the function for each input inside Fields component separately.
I know it is possible to create a component and use Field to connect with the application state, but I didn't want to manage things as the touched prop, or the callbacks to update the state, or other things that I even have noticed, since Redux-Form has all of it done.
The fact is that I end up with an implementation but it didn't looked very elegant. I'd like you to take a look at the implementation and give your opinion, sugest other solutions and even if this solution is not implemented in Redux-Form yet we could maybe open a pull request for that.
Here is an example implementation
Simple form container
import SimpleForm from 'app/simpleForm/components/simpleForm'
import { reduxForm } from 'redux-form'
export default reduxForm({
form: 'simpleForm'
})(SimpleForm)
Simple form component
import React from 'react'
import { Field } from 'redux-form'
import MultiInputText from 'app/simpleForm/components/multiInputText'
const onSubmit = values => alert(JSON.stringify(values))
const validateAddress = (value) => {
if (!value) return 'Address is empty'
if (!value.street) return 'Street is empty'
if (!value.number) return 'Number is empty'
return null
}
const SimpleForm = ({ handleSubmit }) => {
return (
<form onSubmit={ handleSubmit(onSubmit) }>
<Field label="Home Address" name="home" component={MultiInputText} type="text" validate={validateAddress}/>
<Field label="Work Address" name="work" component={MultiInputText} type="text" validate={validateAddress}/>
<button type="submit">Submit</button>
</form>
)
}
export default SimpleForm
MultiInputText component
import React from 'react'
import { Fields, FormSection } from 'redux-form'
const renderAddreessInputs = ({ street, number, error }) => (<div>
<input {...street.input} type="text" />
<input {...number.input} type="text" />
{ street.meta.touched && number.meta.touched && error && <span className="error">{error}</span> }
</div>)
const MultiInputText = (props) => {
const { input: { name }, label, meta: { error }} = props
const names = [
'street',
'number'
]
return (<div>
<label htmlFor={name}>{label}</label>
<FormSection name={name}>
<Fields names={names} component={renderAddreessInputs} error={error}/>
</FormSection>
</div>)
}
export default MultiInputText
I see two options:
1) Use record-level validation.
reduxForm({
form: 'addressForm',
validate: values => {
const errors = {}
if(!home) {
errors.home = 'Address is empty'
}
// etc, etc. Could reuse same code for home and work
return errors
}
})
2) Create a single input that handles a complex value.
<Field name="home" component="AddressInput"/>
...
const AddressInput = ({ input, meta }) =>
<div>
<input
name={`${input.name}.street`}
value={(input.value && input.value.street) || ''}
onChange={event => input.onChange({
...input.value,
street: event.target.value
})}/>
...other inputs here...
</div>
That's total pseudocode, but I hope it gets the point across: a single input can edit a whole object structure.
Personally, I'd choose Option 1, but I prefer record-level validation over field-level validation in general. The nice thing about Option 2 is that a single AddressInput could be reused across the application. The downside is that you don't get specific field-level focus/blur/dirty/pristine state.
Hope that helps...?

In list component, how to implement a component to change the number of results displayed

I was thinking about making a simple component with a Select and the list of results that should be displayed.
After reading the code, that seems impossible, because if I change the url, then update is triggered by componentWillReceiveProps, and this method does not check for a change of perPage
Change the prop perPage of the List component does not work either because the List use this prop only if the query does not already contains perPage
Here is an example of what I want to do :
import { List } from "admin-on-rest";
class SourceList extends Component {
constructor(props) {
super(props);
this.state = {
perPage: 10
};
}
render() {
return (
<div>
<Button
onClick={() => {
this.setState({ perPage: 50 });
}}
/>
<List {...props} perPage={this.state.perPage}>
... Here would be the content of the list
</List>
</div>
);
}
}

the selected option will be restored to unselected status

3sec Demo https://www.youtube.com/watch?v=bo2nNQXbhI8&feature=youtu.be
https://gist.github.com/weichenghsu/407a8862f3382a425fb531b3dedcd6f5
As title, the selected option will be restored to unselected status
And onChange method has no effect for the official tutorial example.
My use case is that when a user picks a value from the dropdown. It should fire an action to fetch other data and render on another form
const chooseTable = ({items, meta:{touched, error}}) => (
<select
onChange={event => {
console.log(this.props.fields);
this.props.tableNameOnChange(event.target.value);
}}>
<option value="">Select</option>
{
items.map((item :any, i: integer) =>
<option key={item.id} value={item.id}>{item.name}</option>
)
}
</select>
)
<Field component={chooseTable}
items={schemaData.tableList}
name="tableName"
>
{/*<option value="#ff0000">Red</option>*/}
{/*<option value="#00ff00">Green</option>*/}
{/*<option value="#0000ff">Blue</option>*/}
</Field>
UIBuilderForm = reduxForm({
form: 'dashbaordUiBuilderForm',
fields: ['tableName']
}
})
(UIBuilderForm as any);
// Decorate with connect to read form values
const selector = formValueSelector('dashbaordUiBuilderForm')
// export default connect(mapStateToProps, mapDispatchToProps)(UIBuilderForm);
export default connect(state => {
const TableSchemaName = selector(state, 'TableSchemaName')
return {
TableSchemaName
}
}
I was banging my head on a similar issue with the react-native picker. Try writing your 'chooseTable' as a component instead of a stateless function and use 'this.state' and 'this.setState' to refer to what value is selected. Here's an example from my picker code:
class ExpirePicker extends React.Component {
constructor(props) {
super(props)
this.state = {
selected: 'ASAP'
}
}
render() {
const { input: { onChange, ...rest }} = this.props
return (
<Picker
style={style.input}
selectedValue={this.state.selected}
onValueChange={(value) => {
this.setState({selected: value})
onChange(value)
}}
{...rest}>
{Object.keys(ExpireTypes).map(renderItem)}
</Picker>
)
}
}
Could you also be using the element's "onChange" event and not binding it to redux-forms "onChange" prop?

Resources