React-Redux how to use reusable checkbox component for displaying data - react-redux

I have been asked to tamper with React-Redux code (knowing very little at the moment) and update a colleague's front-end code. One of the application's functionality, is for the administrator to create alert notifications and distribute them across different departments. These departments are selected with checkboxes and finally with a 'Send' button, they alert everyone involved. The form with all the necessary fields, is saved in the database. The notification details page, has detailed information and the mockup that we are supposed to produce, has the involved departments with the same form of grouped checkeboxes (along with their checked/unchecked status).
My colleague had created a reusable component like so:
import React from "react";
import { connect } from "react-redux";
import {reset, change, registerField } from "redux-form";
import _ from "lodash";
import DepartmentTypeCheckBoxes from "./ThreatTypeCheckBoxes";
import { setNotifView, setNotifViewForm } from "Actions/notifView.action";
import { Label } from "reactstrap";
import { ICustomProps } from "Entities/baseForm";
interface INotificationState {
notifStatus?: boolean;
}
interface IProps extends ICustomProps {
registerField(): void;
resetForm(): void;
changeField(value: any): any;
setNotifView(view: any): void;
setNotifViewForm(form: any): void;
}
class DepartmentType extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
};
this.onFieldChange = this.onFieldChange.bind(this);
}
public componentWillMount() {
this.props.registerField();
}
public onFieldChange() {
if(this.state.status && this.state.status == true){
this.setState({ status: false })
this.props.changeField(false);
}
else{
this.setState({ status: true })
this.props.changeField(true);
}
}
public componentWillReceiveProps(nextProps: IProps , nextState: IState) {
}
public render() {
return (
<div className="form-group">
<div className="f" >
<Label for="type">Department Types</Label>
<div className="">
<div className="">
<DepartmentTypeCheckBoxes id="1" value="option1" label="Development" fieldName="development" formName="CreateAlertNotification"></DepartmentTypeCheckBoxes>
</div>
<div className="">
<DepartmentTypeCheckBoxes id="2" value="option2" label="Human resources" fieldName="humanResources" formName="CreateAlertNotification"></DepartmentTypeCheckBoxes>
</div>
<div className="">
<DepartmentTypeCheckBoxes id="3" value="option3" label="Consultance" fieldTag="consultance" formTag="CreateAlertNotification"></DepartmentTypeCheckBoxes>
</div>
</div>
<div className="">
<div className="">
<div className="">
<DepartmentTypeCheckBoxes id="4" value="option4" label="Logistics" fieldTag="logistics" formTag="CreateAlertNotification"></DepartmentTypeCheckBoxes>
</div>
</div>
<div className="">
{this.props.children && this.props.children}
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state: any, ownProps: ICustomProps) => {
return {
};
};
const mapDispatchToProps = (dispatch: any, ownProps: ICustomProps) => {
const formTag = ownProps.formTag;
const fieldTag = ownProps.fieldTag;
return {
registerField: () => dispatch(registerField(formTag, fieldTag, "FieldArray")),
changeField: (value: any) => dispatch(change(formTag, fieldTag, value, false, false)),
setNotifView: (view: any) => dispatch(setNotifView(view)),
setNotifViewForm: (form: any) => dispatch(setNotifViewForm(form)),
resetFields: () => dispatch(reset("CreateAlertNotification")),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(DepartmentType);
and uses it in the submission form like so:
<Row>
<Col md="6">
{ initialValues.ShowDepartmentBoxes &&
<DepartmentType fieldTag="DepType" formTag="CreateAlertNotification">
<Field name="AnotherCustomField" className="form-control" component={renderField} type="text" label="General Information" />
</DepartmentType>
}
</Col>
<Col md="6">
<AnotherCustomField fieldTag="SomeFieldName" formTag="CreateAlertNotification" Mode="create"/>
</Col>
</Row>
I want to use the same DepartmentType field in my "Notification Details" page, with the values loaded in the notification object from the db. Assuming I have 4 bool values like
notification.IsHumanResourcesAlerted
notification.IsDevelopmentAlerted,
notification.IsLogisticsAlerted,
notification.IsConsultanceAlerted
how will I pass them in the details page that is NOT a form and the "value" in the DepartmentTypeCheckBoxes seems to be predefined?
I have not found anything relevant yet and because we are on a tight schedule, I want to try and come up with a solution as possible.
Any help is appreciated.

I might be misunderstanding the implementation of your form and your details page, but if you need the form to exist exactly as it is selected on your send page you could see how the values of this form are being dispatched. With that information you could build something into an existing reducer for your details page or create a new reducer that holds those values and then use them later on to display your details page.
This would most likely be considered improper usage of Redux store (see https://goshakkk.name/should-i-put-form-state-into-redux/ for why I feel that may be the case). But it would work for your implementation as I understand it.
Edit: I should also mention that to display this data you could just display it as the same form as before, but disable the checkboxes so that the preselected values you imported cannot be changed.

Related

Form-level validation does not behave as expected

Using redux-form 7.0.3, I'm unable to get the Field validate API to do anything.
First, I created a basic, minimal example templated off of the docs.
import React from 'react'
import { Field, reduxForm } from 'redux-form'
// removed validation fns
const required = value => {
console.log('Test of validation fn')
return (value ? undefined : 'Required')
}
// unchanged
const renderField = ({
input,
label,
type,
meta: { touched, error, warning }
}) =>
<div>
<label>
{label}
</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched &&
((error &&
<span>
{error}
</span>) ||
(warning &&
<span>
{warning}
</span>))}
</div>
</div>
// removed fields & made into React Component
class FieldLevelValidations extends Component {
render(){
const { handleSubmit } = this.props
return (
<form onSubmit={handleSubmit}>
<Field
name="test"
type="text"
component={renderField}
label="Test Component"
validate={required}
/>
<div>
<button type="submit">
Submit
</button>
</div>
</form>
)
}
}
export default reduxForm({
form: 'fieldLevelValidation'
})(FieldLevelValidations)
From this I would assume that the forms reducer processes an action that sets a prop that can be accessed in renderField, but it does not. Both error and warning are undefined, and required is never called. Additionally, there is no isValid or the like property set in the store. I don't know if this is a bug or if I'm missing some critical piece, but I would expect more to be happening here. Also, some greater detail in the docs would be nice.

redux-form and react-select with options appearing as user types

I have a redux-form with a react-select. The expected behaviour is that, as I type in the select field, I call the redux action (by using OnInputChange). But I don't know how to call the action. The line that calls the action is commented in the snippet below, because it fails (this.props.getArtistSearch(value)). Any ideas of how to properly call the action as user types in?
class FormApplication extends React.Component {
submit(values) {
this.props.submitForm(values)
}
getArtist(value){
//this.props.getArtistSearch(value) --> props is undefined
console.log(value)
}
render() {
const { handleSubmit } = this.props
return (
<form className='content text padding-top-0' onSubmit={handleSubmit(this.submit.bind(this))}>
<div className='row adjust-form-row'>
<div className='col-md-6 last-lineup'>
<div className='row adjust-form-row'>
<div className='col-md-6'>
<div className='form-group'>
<Field
name='dl_artistname'
options={this.props.gap.artistSearch}
component={props => (
<Select
{...props}
name={props.name}
onInputChange={this.getArtist}
onChange={(value) => {
this.props.requestArtistInstance({id: value.dl_artistid })
return props.input.onChange(value != null ? value.dl_artistid : null)}
}
onBlur={() => props.input.onBlur(props.input.value)}
options={props.options}
//loadOptions={getOptions}
clearable={false}
cache={false}
backspaceRemoves={false}
valueKey='dl_artistid'
labelKey='dl_name'
value={props.input.value || ''}
isLoading={false}
disabled={false}
/>
)}
/>
</div>
</div>
</div>
</div>
</div>
</form>
)
}
}
const mapDispatchToProps = dispatch => ({
getArtistSearch: (text) => {
dispatch(getArtistSearch(text))
},
submitForm: (values) => {
dispatch(submitForm(values))
}
})
Going through your code, I noticed that the custom method you defined, getArtist was not bound to your React context so props will be undefined. Two possible approaches for this are:
1) bind it in the constructor method
constructor(){
super();
this.getArtist = this.getArtist.bind(this);
}
2) Alternatively, bind it in the Select component(Not ideal tho')
onInputChange={this.getArtist.bind(this)}

create a dropdown in react to show/hide fetch data based on object class

Currently I am calling the Riot Games api and displaying all the champions. I have added a filter by name section, but i would like to add a dropdown to sort by champion class. On initial page load I want every champion rendered (which it currently does), but then selecting a class from the dropdown only show the champs that have that class. I can show each champions class with
{champion.table.tags}
and adding the index number or numbers and this is what i want to be able to sort by. If {champion.table.tags[0} or {champion.table.tags[1} = "Fighter" for example. I would like every champion with a tag of "Fighter" to be shown when "Fighter" is selected in the dropdown
I'm pretty new to react and I have built this same feature in Rails without any problem, but I'm struggling with the React way of going about this.
my current code that shows all champs and has a sort function
import React, { Component } from 'react';
import { Link } from 'react-router';
import _ from 'lodash';
class Champions extends Component {
constructor(props) {
super(props);
this.state = {
champions: []
};
}
componentWillMount() {
fetch('http://localhost:3000/champ.json')
.then(response => response.json())
.then(champions => {
this.setState({ champions })
})
}
filter(e){
this.setState({ filter: e.target.value} )
}
render() {
let champions = this.state.champions;
console.log('champions: ', champions);
if(this.state.filter){
champions = champions.filter( champion =>
champion.table.name.toLowerCase()
.includes(this.state.filter.toLowerCase()))
}
if(this.state.handleChange){
tags = champions.handleChange( champion =>
champion.table.tags
.includes(this.state.handleChange()))
}
return (
<div>
<div>
<input type="text"
placeholder="search for a champ"
onChange={this.filter.bind(this)}
/>
</div>
<div className="Sort">
<select>
{champions.map((champion, i) =>
<option key={i}>{champion.table.tags[0]}
</option>)}
</select>
</div>
<ul className="champ-list">
{champions.map(champion => {
return <li key={champion.table.id} className="col-xs-3">
<div>
<Link to={`/${champion.table.id}`} >
<img src={`http://ddragon.leagueoflegends.com/cdn/7.5.2/img/champion/${champion.table.image.full}`} />
</Link>
</div>
<Link to={`/${champion.table.id}`} >
<h3>{champion.table.key}</h3>
<h4>{champion.table.title}</h4>
<h4>{champion.table.tags}</h4>
</Link>
</li>
})}
</ul>
</div>
);
}
}
export default Champions;
as of right now the dropdown goes through each champion object and populates the with the first class tag of every champion. I think there are maybe 7 classes total and I can manually create the if needed, but how can I show All of the champions and then just the ones based on the dropdown selection.
Not sure if this is relevant, but has also given me a bit of a headache in React. The JSON returns an array of 134 objects (champions). Inside each object are several key value pairs as well as more arrays with key value pairs inside of them. The class tags I am trying to sort by are inside of one of these nested arrays.
Any help or even a point in the right direction would be greatly appreciated
figured it out on my own. here's the updated code
import React, { Component } from 'react';
import { Link } from 'react-router';
import _ from 'lodash';
class Champions extends Component {
constructor(props) {
super(props);
this.state = {
champions: [],
};
}
componentWillMount() {
fetch('http://localhost:3000/champ.json')
.then(response => response.json())
.then(champions => {
this.setState({ champions })
// console.log('setState: ', this.state.champions);
})
}
filter(e){
this.setState({filter: e.target.value})
}
filterChamp(e){
this.setState({filterChamp: e.target.value})
}
render() {
let champions = this.state.champions;
// console.log(champions);
// console.log('props: ', this.props);
if(this.state.filter){
champions = champions.filter( champion =>
champion.table.name.toLowerCase()
.includes(this.state.filter.toLowerCase()))
}
if(this.state.filterChamp){
champions = champions.filter( champion =>
champion.table.tags
.includes(this.state.filterChamp))
}
return (
<div>
<select onChange={this.filterChamp.bind(this)} value={this.state.filterChamp}>
<option value="">All Champs</option>
<option value="Assassin">Assassin</option>
<option value="Fighter">Fighter</option>
<option value="Mage">Mage</option>
<option value="Marksman">Marksman</option>
<option value="Support">Support</option>
<option value="Tank">Tank</option>
</select>
<input type="text"
placeholder="search for a champ"
onChange={this.filter.bind(this)}
/>
<ul className="champ-list">
{champions.map(champion => {
return <li key={champion.table.id} className="col-xs-3">
<div>
<Link to={`/champ/${champion.table.name}`} >
<img src={`http://ddragon.leagueoflegends.com/cdn/7.5.2/img/champion/${champion.table.image.full}`} />
</Link>
</div>
<Link to={`/${champion.table.id}`} >
<h3>{champion.table.key}</h3>
<h4>{champion.table.title}</h4>
</Link>
</li>
})}
</ul>
</div>
);
}
}
export default Champions;

How to hijack submit to add values?

I have a form that can have different state based on which button was used for submission; one does a simple submit while the other one adds a flag then submit.
I found a working solution that is, imo, quite ugly, so I'd like to know how else to do it ?
class MyForm extends Component {
// Hijack submit to add published flag
handlePublish = (e) => {
e.preventDefault();
const { handleSubmit, onSubmit } = this.props;
handleSubmit((values) => {
onSubmit({
...values,
isPublished: true,
});
})();
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit}>
<Field
name="foo"
component="input"
/>
<button
type="submit"
onClick={this.handlePublish}
>
Publish
</button>
<button type="submit">
Save
</button>
</form>
);
}
}
✅ This is the idiomatic way. Alternatively, you could provide any number of values as initialValues that don't actually have a Field on the form, but will be submitted.

Angular2 - Create a reusable, validated text input component

I'm creating an Angular2 application with a Node backend. I will have forms that submit data to said backend. I want validation on both the client and server side, and I'd like to avoid duplicating these validation rules.
The above is somewhat irrelevant to the actual question, except to say that this is the reason why I'm not using the conventional Angular2 validation methods.
This leaves me with the following HTML structure:
<div class="form-group" [class.has-error]="hasError(name)">
<label class="control-label" for="name"> Property Name
<input id="name" class="form-control" type="text" name="name" [(ngModel)]="property.name" #name="ngModel" />
<div class="alert alert-danger" *ngIf="hasError(name)">{{errors.name}}</div>
</div>
<div class="form-group" [class.has-error]="hasError(address1)">
<label class="control-label" for="address1"> Address
<input id="address1" class="form-control" type="text" name="address1" [(ngModel)]="property.address.address1" #address1="ngModel" />
<div class="alert alert-danger" *ngIf="hasError(address1)">{{errors['address.address1']}}</div>
</div>
I will have some large forms and would like to reduce the verbosity of the above. I am hoping to achieve something similar to the following:
<my-text-input label="Property Name" [(ngModel)]="property.name" name="name"></my-text-input>
<my-text-input label="Address" [(ngModel)]="property.address.address1" name="address1" key="address.address1"></my-text-input>
I'm stumbling trying to achieve this. Particular parts that give me trouble are:
Setting up two-way binding on the ngModel (changes that I make in the component do not reflect back to the parent).
Generating the template reference variable (#name and #address1 attributes) based on an #Input variable to the component.
It just occurred to me that perhaps I don't need a separate template reference variable name for each instance of the component. Perhaps I can just use #input since it's only referenced from within that component. Thoughts?
I could pass errors or a constraints object to each instance of the component for validation, but I'd like to reduce repetition.
I realize that this is a somewhat broad question, but I believe that a good answer will be widely useful and very valuable to many users, since this is a common scenario. I also realize that I have not actually shown what I've tried (only explained that I have, indeed, put forth effort to solve this on my own), but I'm purposely leaving out code samples of what I've tried because I believe there must be a clean solution to accomplish this, and I don't want the answer to be a small tweak to my ugly, unorthodox code.
I think what you are looking for is custom form control. It can do everything you mentioned and reduce verbosity a lot. It is a large subject and I am not a specialist but here is good place to start: Angular 2: Connect your custom control to ngModel with Control Value Accessor.
Example solution:
propertyEdit.component.ts:
import {Component, DoCheck} from '#angular/core';
import {TextInputComponent} from 'textInput.component';
let validate = require('validate.js');
#Component({
selector: 'my-property-edit',
template: `
<my-text-input [(ngModel)]="property.name" label="Property Name" name="name" [errors]="errors['name']"></my-text-input>
<my-text-input [(ngModel)]="property.address.address1" label="Address" name="address1" [errors]="errors['address.address1']></my-text-input>
`,
directives: [TextInputComponent],
})
export class PropertyEditComponent implements DoCheck {
public property: any = {name: null, address: {address1: null}};
public errors: any;
public constraints: any = {
name: {
presence: true,
length: {minimum: 3},
},
'address.address1': {
presence: {message: "^Address can't be blank"},
length: {minimum: 3, message: '^Address is too short (minimum is 3 characters)'},
}
};
public ngDoCheck(): void {
this.validate();
}
public validate(): void {
this.errors = validate(this.property, this.constraints) || {};
}
}
textInput.component.ts:
import {Component, Input, forwardRef} from '#angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR, NgModel} from '#angular/forms';
const noop = (_?: any) => {};
#Component({
selector: 'my-text-input',
template: `
<div class="form-group" [class.has-error]="hasErrors(input)">
<label class="control-label" [attr.for]="name">{{label}}</label>
<input class="form-control" type="text" [name]="name" [(ngModel)]="value" #input="ngModel" [id]="name" />
<div class="alert alert-danger" *ngIf="hasErrors(input)">{{errors}}</div>
</div>
`,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TextInputComponent), multi: true },
],
})
export class TextInputComponent implements ControlValueAccessor {
protected _value: any;
protected onChange: (_: any) => void = noop;
protected onTouched: () => void = noop;
#Input() public label: string;
#Input() public name: string;
#Input() public errors: any;
get value(): any {
return this._value;
}
set value(value: any) {
if (value !== this._value) {
this._value = value;
this.onChange(value);
}
}
public writeValue(value: any) {
if (value !== this._value) {
this._value = value;
}
}
public registerOnChange(fn: (_: any) => void) {
this.onChange = fn;
}
public registerOnTouched(fn: () => void) {
this.onTouched = fn;
}
public hasErrors(input: NgModel): boolean {
return input.touched && this.errors != null;
}
}

Resources