React application design: parent component that controls access to backend and needs to keep its state...
up vote
0
down vote
favorite
I'm not satisfied with a solution I came up for a React application, since I'm new with the technology but I'm seeing some code that feels like anti-patterns. I have a parent component that communicates to the backend through React Context (using the Context for service layer) and needs to update its state when its child components change something about it. I'll simplify it below:
ParentComponent.jsx
import React, { Component } from 'react';
import Search from './search';
import { ActionTypes } from 'context/searchProvider';
import { PropTypes } from 'prop-types';
export default class ParentComponent extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
history: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
item: {
fieldOne: '',
fieldTwo: '',
...
fieldN: ''
},
fieldN1: '',
selectField: [{ text: 'My Text', value: '1' }, { text: 'Another Text', value: '2' }],
selectList: selectList,
errors: props.errors,
searchParams: () => ({
fieldOne: this.state.fieldOne,
fieldTwo: this.state.fieldTwo,
fieldThree: this.state.fieldThree
}),
};
}
updateFieldOne(fieldOneUpdated) {
this.setState({ fieldOne: fieldOneUpdated });
}
updateFieldTwo(fieldTwoUpdated) {
this.setState({ fieldTwo: fieldTwoUpdated });
}
// One update method for each field
...
validateForm() {
if (this.state.fieldOne === 'Some value') {
this.setState({
errors: {
fieldTwo: this.validateFieldOne(this.state.fieldOne),
fieldThree: this.validateFieldOne(this.state.fieldThree)
}
});
} else {
this.setState({
errors: {
fieldFour: this.validateFieldFour(this.state.fieldFour),
fieldFive this.validateFieldFive(this.state.fieldFive)
}
});
}
}
// One validate method for fields that need to be validated
...
sendSearch() {
if (this.state.fieldOne === 'USA') {
if (this.state.errors.fieldTwo.length === 0 && this.state.errors.fieldThree.length === 0 &&) {
this.dispatchDetails();
}
} else if (this.state.errors.fieldFour.length === 0 && this.state.errors.fieldFive.length === 0) {
this.dispatchDetails();
}
}
dispatchDetails() {
const params = this.state.searchParams();
this.props.dispatch({ type: ActionTypes.POSTSEARCH }, params, () => {
if (Object.keys(this.props.errors).length) {
this.setState({ errors: this.props.errors });
} else {
this.props.history.push('/nextPage?' + utilities.convertObjectToQueryString(params));
}
});
}
render() {
return (
<div
className={!this.state.showRecentSearches ? 'container-fluid no-recent-searches' : 'container-fluid'}
id="content"
>
{
this.state.showRecentSearches ?
<RecentSearches data={this.props.recentItems} dispatch={this.props.dispatch} history={this.props.history} />
:
null
}
<Search
item={this.state}
fieldOne={this.updateFieldOne}
fieldTwo={this.updateFieldTwo}
fieldThree={this.updatefieldThree}
...
selectList={this.state.selectList}
secondSelectList={this.state.selectField}
errors={this.state.errors}
doSearch={this.validateForm}
history={this.props.history}
/>
</div>
);
}
};
Search.jsx
import React, { Component } from 'react';
import Search from './search';
import { ActionTypes } from 'context/searchProvider';
import { PropTypes } from 'prop-types';
export default class Search extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
fieldOne: PropTypes.func.isRequired,
fieldTwo: PropTypes.func.isRequired,
...
}
render() {
const fieldContent =
this.props.fieldOne === 'Some value' ? (
<FieldsSection
item={this.props.item}
updateFieldOne={this.props.fieldOne}
updateFieldTwo={this.props.fieldTwo}
...
errors={this.props.errors}
/>
) : (
<DifferentFieldsSection
selectList={this.props.selectList}
selection={this.props.selection}
onChange={this.props.updateFieldFour}
errors={this.props.errors}
/>
);
return fieldContent;
}
}
FieldsSection.jsx (DifferentFieldsSection is similar)
import React, { Component } from 'react';
import {
NumberInputControl,
DatePickerInputControl,
SelectControl,
TextInputControl,
} from 'components/common/reactBootstrapWrappers';
export default class FieldsSection extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
updateFieldOne: PropTypes.func.isRequired,
updateFieldTwo: PropTypes.func.isRequired,
updatecity: PropTypes.func.isRequired,
...
errors: PropTypes.array.isRequired
};
render() {
const {
item,
updateFieldOne,
updateaddress2,
updateFieldTwo,
...
errors,
} = this.props;
const stateInfoDetail = this.getstateInfoDetail();
return (
<div>
<TextInputControl
ref={(input) => {
value={item.fieldOne}
onChange={updateFieldOne}
...
errors={errors.fieldOne}
/>
<TextInputControl
value={item.fieldTwo}
onChange={updateFieldTwo}
...
errors={errors.fieldTwo}
/>
// One control for each field passed to the component
...
);
}
}
Explanation
ParentComponent is the component that renders the page and communicates with the backend. It receives a dispatch property from the Routes.jsx components (which uses react-router) by being wrapped around a Context Provider and consumer like so:
@withRouter
export default class Routes extends Component {
render() {
return (
<div>
<SearchProvider>
<SearchConsumer>
{
{ dispatch, errors } =>
(
<Switch>
<Route path="/search" component={ParentComponent} dispatch={dispatch} ... />
</Switch>
)
}
</SearchConsumer>
</SearchProvider>
</div>
);
}
}
It validates the fields that are completed in either FieldsSection component and posts them to the back-end in the dispatchDetails() method. In order to do the validation, it needs to keep its state updated. In order to do that, it injects update functions (e.g updateFieldOne()) to its child component Search, which in turn injects it to either FieldsSection component. Those update functions are then called by one of the components that wrap react-bootstrap controls when the user modifies their value.
The logic that renders the FieldsSection components is complicated enough that it gets wrapped in the Search component, which is also used elsewhere in other components, while the ParentComponent also renders other elements that I excluded for simplicity's sake. But in those components, you also need to inject those update functions to keep the respective parent component's state updated. Also, the parent components are the only ones that get to consume the dispatch function to POST or GET information to the backend.
The SearchConsumer code isn't pretty neither:
import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import api from 'apis/api';
const Context = React.createContext();
export const ActionTypes = {
GETDATA: 'GETDATA',
POSTSEARCH: 'POSTSEARCH',
... // Other methods
};
export class SearchProvider extends Component {
static propTypes = {
children: PropTypes.any,
};
static defaultProps = {
children: ,
};
constructor() {
super();
this.reducer = this.reducer.bind(this);
}
state = {
items: ,
dispatch: (action, params, callback) => {
this.reducer(action, params, callback);
},
result: {},
errors: {},
};
reducer = (action, params, callback) => {
switch (action.type) {
case ActionTypes.GETDATA:
this.getData();
break;
case ActionTypes.POSTSEARCH:
this.postSearch(params, callback);
break;
... // Other methods
}
};
getData = () => {
api
.getItems()
.then((result) => {
if (result) {
this.setState({ items: result.Results });
}
})
.catch(() => {})
}
postSearch = () => {
api
.postSearch()
.then((result) => {
if (result) {
if (result.Results.length === 1) {
this.setState({ result: result.Results[0], errors: {} }, callback);
}
}
})
.catch((error) => {
if (error && error.FieldErrors) {
this.setState({ errors: error.FieldErrors, result: {} }, callback);
}
})
}
... // Other methods
render() {
return <Context.Provider value={this.state}>{this.props.children}</Context.Provider>;
}
}
export const SearchConsumer = Context.Consumer;
I'm sure the successive injections of the update functions and the way the SearchConsumer and SearchProvider are built are wrong or disencouraged, at least. But I don't know what alternatives I have.
Thank you.
javascript react.js
add a comment |
up vote
0
down vote
favorite
I'm not satisfied with a solution I came up for a React application, since I'm new with the technology but I'm seeing some code that feels like anti-patterns. I have a parent component that communicates to the backend through React Context (using the Context for service layer) and needs to update its state when its child components change something about it. I'll simplify it below:
ParentComponent.jsx
import React, { Component } from 'react';
import Search from './search';
import { ActionTypes } from 'context/searchProvider';
import { PropTypes } from 'prop-types';
export default class ParentComponent extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
history: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
item: {
fieldOne: '',
fieldTwo: '',
...
fieldN: ''
},
fieldN1: '',
selectField: [{ text: 'My Text', value: '1' }, { text: 'Another Text', value: '2' }],
selectList: selectList,
errors: props.errors,
searchParams: () => ({
fieldOne: this.state.fieldOne,
fieldTwo: this.state.fieldTwo,
fieldThree: this.state.fieldThree
}),
};
}
updateFieldOne(fieldOneUpdated) {
this.setState({ fieldOne: fieldOneUpdated });
}
updateFieldTwo(fieldTwoUpdated) {
this.setState({ fieldTwo: fieldTwoUpdated });
}
// One update method for each field
...
validateForm() {
if (this.state.fieldOne === 'Some value') {
this.setState({
errors: {
fieldTwo: this.validateFieldOne(this.state.fieldOne),
fieldThree: this.validateFieldOne(this.state.fieldThree)
}
});
} else {
this.setState({
errors: {
fieldFour: this.validateFieldFour(this.state.fieldFour),
fieldFive this.validateFieldFive(this.state.fieldFive)
}
});
}
}
// One validate method for fields that need to be validated
...
sendSearch() {
if (this.state.fieldOne === 'USA') {
if (this.state.errors.fieldTwo.length === 0 && this.state.errors.fieldThree.length === 0 &&) {
this.dispatchDetails();
}
} else if (this.state.errors.fieldFour.length === 0 && this.state.errors.fieldFive.length === 0) {
this.dispatchDetails();
}
}
dispatchDetails() {
const params = this.state.searchParams();
this.props.dispatch({ type: ActionTypes.POSTSEARCH }, params, () => {
if (Object.keys(this.props.errors).length) {
this.setState({ errors: this.props.errors });
} else {
this.props.history.push('/nextPage?' + utilities.convertObjectToQueryString(params));
}
});
}
render() {
return (
<div
className={!this.state.showRecentSearches ? 'container-fluid no-recent-searches' : 'container-fluid'}
id="content"
>
{
this.state.showRecentSearches ?
<RecentSearches data={this.props.recentItems} dispatch={this.props.dispatch} history={this.props.history} />
:
null
}
<Search
item={this.state}
fieldOne={this.updateFieldOne}
fieldTwo={this.updateFieldTwo}
fieldThree={this.updatefieldThree}
...
selectList={this.state.selectList}
secondSelectList={this.state.selectField}
errors={this.state.errors}
doSearch={this.validateForm}
history={this.props.history}
/>
</div>
);
}
};
Search.jsx
import React, { Component } from 'react';
import Search from './search';
import { ActionTypes } from 'context/searchProvider';
import { PropTypes } from 'prop-types';
export default class Search extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
fieldOne: PropTypes.func.isRequired,
fieldTwo: PropTypes.func.isRequired,
...
}
render() {
const fieldContent =
this.props.fieldOne === 'Some value' ? (
<FieldsSection
item={this.props.item}
updateFieldOne={this.props.fieldOne}
updateFieldTwo={this.props.fieldTwo}
...
errors={this.props.errors}
/>
) : (
<DifferentFieldsSection
selectList={this.props.selectList}
selection={this.props.selection}
onChange={this.props.updateFieldFour}
errors={this.props.errors}
/>
);
return fieldContent;
}
}
FieldsSection.jsx (DifferentFieldsSection is similar)
import React, { Component } from 'react';
import {
NumberInputControl,
DatePickerInputControl,
SelectControl,
TextInputControl,
} from 'components/common/reactBootstrapWrappers';
export default class FieldsSection extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
updateFieldOne: PropTypes.func.isRequired,
updateFieldTwo: PropTypes.func.isRequired,
updatecity: PropTypes.func.isRequired,
...
errors: PropTypes.array.isRequired
};
render() {
const {
item,
updateFieldOne,
updateaddress2,
updateFieldTwo,
...
errors,
} = this.props;
const stateInfoDetail = this.getstateInfoDetail();
return (
<div>
<TextInputControl
ref={(input) => {
value={item.fieldOne}
onChange={updateFieldOne}
...
errors={errors.fieldOne}
/>
<TextInputControl
value={item.fieldTwo}
onChange={updateFieldTwo}
...
errors={errors.fieldTwo}
/>
// One control for each field passed to the component
...
);
}
}
Explanation
ParentComponent is the component that renders the page and communicates with the backend. It receives a dispatch property from the Routes.jsx components (which uses react-router) by being wrapped around a Context Provider and consumer like so:
@withRouter
export default class Routes extends Component {
render() {
return (
<div>
<SearchProvider>
<SearchConsumer>
{
{ dispatch, errors } =>
(
<Switch>
<Route path="/search" component={ParentComponent} dispatch={dispatch} ... />
</Switch>
)
}
</SearchConsumer>
</SearchProvider>
</div>
);
}
}
It validates the fields that are completed in either FieldsSection component and posts them to the back-end in the dispatchDetails() method. In order to do the validation, it needs to keep its state updated. In order to do that, it injects update functions (e.g updateFieldOne()) to its child component Search, which in turn injects it to either FieldsSection component. Those update functions are then called by one of the components that wrap react-bootstrap controls when the user modifies their value.
The logic that renders the FieldsSection components is complicated enough that it gets wrapped in the Search component, which is also used elsewhere in other components, while the ParentComponent also renders other elements that I excluded for simplicity's sake. But in those components, you also need to inject those update functions to keep the respective parent component's state updated. Also, the parent components are the only ones that get to consume the dispatch function to POST or GET information to the backend.
The SearchConsumer code isn't pretty neither:
import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import api from 'apis/api';
const Context = React.createContext();
export const ActionTypes = {
GETDATA: 'GETDATA',
POSTSEARCH: 'POSTSEARCH',
... // Other methods
};
export class SearchProvider extends Component {
static propTypes = {
children: PropTypes.any,
};
static defaultProps = {
children: ,
};
constructor() {
super();
this.reducer = this.reducer.bind(this);
}
state = {
items: ,
dispatch: (action, params, callback) => {
this.reducer(action, params, callback);
},
result: {},
errors: {},
};
reducer = (action, params, callback) => {
switch (action.type) {
case ActionTypes.GETDATA:
this.getData();
break;
case ActionTypes.POSTSEARCH:
this.postSearch(params, callback);
break;
... // Other methods
}
};
getData = () => {
api
.getItems()
.then((result) => {
if (result) {
this.setState({ items: result.Results });
}
})
.catch(() => {})
}
postSearch = () => {
api
.postSearch()
.then((result) => {
if (result) {
if (result.Results.length === 1) {
this.setState({ result: result.Results[0], errors: {} }, callback);
}
}
})
.catch((error) => {
if (error && error.FieldErrors) {
this.setState({ errors: error.FieldErrors, result: {} }, callback);
}
})
}
... // Other methods
render() {
return <Context.Provider value={this.state}>{this.props.children}</Context.Provider>;
}
}
export const SearchConsumer = Context.Consumer;
I'm sure the successive injections of the update functions and the way the SearchConsumer and SearchProvider are built are wrong or disencouraged, at least. But I don't know what alternatives I have.
Thank you.
javascript react.js
add a comment |
up vote
0
down vote
favorite
up vote
0
down vote
favorite
I'm not satisfied with a solution I came up for a React application, since I'm new with the technology but I'm seeing some code that feels like anti-patterns. I have a parent component that communicates to the backend through React Context (using the Context for service layer) and needs to update its state when its child components change something about it. I'll simplify it below:
ParentComponent.jsx
import React, { Component } from 'react';
import Search from './search';
import { ActionTypes } from 'context/searchProvider';
import { PropTypes } from 'prop-types';
export default class ParentComponent extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
history: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
item: {
fieldOne: '',
fieldTwo: '',
...
fieldN: ''
},
fieldN1: '',
selectField: [{ text: 'My Text', value: '1' }, { text: 'Another Text', value: '2' }],
selectList: selectList,
errors: props.errors,
searchParams: () => ({
fieldOne: this.state.fieldOne,
fieldTwo: this.state.fieldTwo,
fieldThree: this.state.fieldThree
}),
};
}
updateFieldOne(fieldOneUpdated) {
this.setState({ fieldOne: fieldOneUpdated });
}
updateFieldTwo(fieldTwoUpdated) {
this.setState({ fieldTwo: fieldTwoUpdated });
}
// One update method for each field
...
validateForm() {
if (this.state.fieldOne === 'Some value') {
this.setState({
errors: {
fieldTwo: this.validateFieldOne(this.state.fieldOne),
fieldThree: this.validateFieldOne(this.state.fieldThree)
}
});
} else {
this.setState({
errors: {
fieldFour: this.validateFieldFour(this.state.fieldFour),
fieldFive this.validateFieldFive(this.state.fieldFive)
}
});
}
}
// One validate method for fields that need to be validated
...
sendSearch() {
if (this.state.fieldOne === 'USA') {
if (this.state.errors.fieldTwo.length === 0 && this.state.errors.fieldThree.length === 0 &&) {
this.dispatchDetails();
}
} else if (this.state.errors.fieldFour.length === 0 && this.state.errors.fieldFive.length === 0) {
this.dispatchDetails();
}
}
dispatchDetails() {
const params = this.state.searchParams();
this.props.dispatch({ type: ActionTypes.POSTSEARCH }, params, () => {
if (Object.keys(this.props.errors).length) {
this.setState({ errors: this.props.errors });
} else {
this.props.history.push('/nextPage?' + utilities.convertObjectToQueryString(params));
}
});
}
render() {
return (
<div
className={!this.state.showRecentSearches ? 'container-fluid no-recent-searches' : 'container-fluid'}
id="content"
>
{
this.state.showRecentSearches ?
<RecentSearches data={this.props.recentItems} dispatch={this.props.dispatch} history={this.props.history} />
:
null
}
<Search
item={this.state}
fieldOne={this.updateFieldOne}
fieldTwo={this.updateFieldTwo}
fieldThree={this.updatefieldThree}
...
selectList={this.state.selectList}
secondSelectList={this.state.selectField}
errors={this.state.errors}
doSearch={this.validateForm}
history={this.props.history}
/>
</div>
);
}
};
Search.jsx
import React, { Component } from 'react';
import Search from './search';
import { ActionTypes } from 'context/searchProvider';
import { PropTypes } from 'prop-types';
export default class Search extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
fieldOne: PropTypes.func.isRequired,
fieldTwo: PropTypes.func.isRequired,
...
}
render() {
const fieldContent =
this.props.fieldOne === 'Some value' ? (
<FieldsSection
item={this.props.item}
updateFieldOne={this.props.fieldOne}
updateFieldTwo={this.props.fieldTwo}
...
errors={this.props.errors}
/>
) : (
<DifferentFieldsSection
selectList={this.props.selectList}
selection={this.props.selection}
onChange={this.props.updateFieldFour}
errors={this.props.errors}
/>
);
return fieldContent;
}
}
FieldsSection.jsx (DifferentFieldsSection is similar)
import React, { Component } from 'react';
import {
NumberInputControl,
DatePickerInputControl,
SelectControl,
TextInputControl,
} from 'components/common/reactBootstrapWrappers';
export default class FieldsSection extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
updateFieldOne: PropTypes.func.isRequired,
updateFieldTwo: PropTypes.func.isRequired,
updatecity: PropTypes.func.isRequired,
...
errors: PropTypes.array.isRequired
};
render() {
const {
item,
updateFieldOne,
updateaddress2,
updateFieldTwo,
...
errors,
} = this.props;
const stateInfoDetail = this.getstateInfoDetail();
return (
<div>
<TextInputControl
ref={(input) => {
value={item.fieldOne}
onChange={updateFieldOne}
...
errors={errors.fieldOne}
/>
<TextInputControl
value={item.fieldTwo}
onChange={updateFieldTwo}
...
errors={errors.fieldTwo}
/>
// One control for each field passed to the component
...
);
}
}
Explanation
ParentComponent is the component that renders the page and communicates with the backend. It receives a dispatch property from the Routes.jsx components (which uses react-router) by being wrapped around a Context Provider and consumer like so:
@withRouter
export default class Routes extends Component {
render() {
return (
<div>
<SearchProvider>
<SearchConsumer>
{
{ dispatch, errors } =>
(
<Switch>
<Route path="/search" component={ParentComponent} dispatch={dispatch} ... />
</Switch>
)
}
</SearchConsumer>
</SearchProvider>
</div>
);
}
}
It validates the fields that are completed in either FieldsSection component and posts them to the back-end in the dispatchDetails() method. In order to do the validation, it needs to keep its state updated. In order to do that, it injects update functions (e.g updateFieldOne()) to its child component Search, which in turn injects it to either FieldsSection component. Those update functions are then called by one of the components that wrap react-bootstrap controls when the user modifies their value.
The logic that renders the FieldsSection components is complicated enough that it gets wrapped in the Search component, which is also used elsewhere in other components, while the ParentComponent also renders other elements that I excluded for simplicity's sake. But in those components, you also need to inject those update functions to keep the respective parent component's state updated. Also, the parent components are the only ones that get to consume the dispatch function to POST or GET information to the backend.
The SearchConsumer code isn't pretty neither:
import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import api from 'apis/api';
const Context = React.createContext();
export const ActionTypes = {
GETDATA: 'GETDATA',
POSTSEARCH: 'POSTSEARCH',
... // Other methods
};
export class SearchProvider extends Component {
static propTypes = {
children: PropTypes.any,
};
static defaultProps = {
children: ,
};
constructor() {
super();
this.reducer = this.reducer.bind(this);
}
state = {
items: ,
dispatch: (action, params, callback) => {
this.reducer(action, params, callback);
},
result: {},
errors: {},
};
reducer = (action, params, callback) => {
switch (action.type) {
case ActionTypes.GETDATA:
this.getData();
break;
case ActionTypes.POSTSEARCH:
this.postSearch(params, callback);
break;
... // Other methods
}
};
getData = () => {
api
.getItems()
.then((result) => {
if (result) {
this.setState({ items: result.Results });
}
})
.catch(() => {})
}
postSearch = () => {
api
.postSearch()
.then((result) => {
if (result) {
if (result.Results.length === 1) {
this.setState({ result: result.Results[0], errors: {} }, callback);
}
}
})
.catch((error) => {
if (error && error.FieldErrors) {
this.setState({ errors: error.FieldErrors, result: {} }, callback);
}
})
}
... // Other methods
render() {
return <Context.Provider value={this.state}>{this.props.children}</Context.Provider>;
}
}
export const SearchConsumer = Context.Consumer;
I'm sure the successive injections of the update functions and the way the SearchConsumer and SearchProvider are built are wrong or disencouraged, at least. But I don't know what alternatives I have.
Thank you.
javascript react.js
I'm not satisfied with a solution I came up for a React application, since I'm new with the technology but I'm seeing some code that feels like anti-patterns. I have a parent component that communicates to the backend through React Context (using the Context for service layer) and needs to update its state when its child components change something about it. I'll simplify it below:
ParentComponent.jsx
import React, { Component } from 'react';
import Search from './search';
import { ActionTypes } from 'context/searchProvider';
import { PropTypes } from 'prop-types';
export default class ParentComponent extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
history: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
item: {
fieldOne: '',
fieldTwo: '',
...
fieldN: ''
},
fieldN1: '',
selectField: [{ text: 'My Text', value: '1' }, { text: 'Another Text', value: '2' }],
selectList: selectList,
errors: props.errors,
searchParams: () => ({
fieldOne: this.state.fieldOne,
fieldTwo: this.state.fieldTwo,
fieldThree: this.state.fieldThree
}),
};
}
updateFieldOne(fieldOneUpdated) {
this.setState({ fieldOne: fieldOneUpdated });
}
updateFieldTwo(fieldTwoUpdated) {
this.setState({ fieldTwo: fieldTwoUpdated });
}
// One update method for each field
...
validateForm() {
if (this.state.fieldOne === 'Some value') {
this.setState({
errors: {
fieldTwo: this.validateFieldOne(this.state.fieldOne),
fieldThree: this.validateFieldOne(this.state.fieldThree)
}
});
} else {
this.setState({
errors: {
fieldFour: this.validateFieldFour(this.state.fieldFour),
fieldFive this.validateFieldFive(this.state.fieldFive)
}
});
}
}
// One validate method for fields that need to be validated
...
sendSearch() {
if (this.state.fieldOne === 'USA') {
if (this.state.errors.fieldTwo.length === 0 && this.state.errors.fieldThree.length === 0 &&) {
this.dispatchDetails();
}
} else if (this.state.errors.fieldFour.length === 0 && this.state.errors.fieldFive.length === 0) {
this.dispatchDetails();
}
}
dispatchDetails() {
const params = this.state.searchParams();
this.props.dispatch({ type: ActionTypes.POSTSEARCH }, params, () => {
if (Object.keys(this.props.errors).length) {
this.setState({ errors: this.props.errors });
} else {
this.props.history.push('/nextPage?' + utilities.convertObjectToQueryString(params));
}
});
}
render() {
return (
<div
className={!this.state.showRecentSearches ? 'container-fluid no-recent-searches' : 'container-fluid'}
id="content"
>
{
this.state.showRecentSearches ?
<RecentSearches data={this.props.recentItems} dispatch={this.props.dispatch} history={this.props.history} />
:
null
}
<Search
item={this.state}
fieldOne={this.updateFieldOne}
fieldTwo={this.updateFieldTwo}
fieldThree={this.updatefieldThree}
...
selectList={this.state.selectList}
secondSelectList={this.state.selectField}
errors={this.state.errors}
doSearch={this.validateForm}
history={this.props.history}
/>
</div>
);
}
};
Search.jsx
import React, { Component } from 'react';
import Search from './search';
import { ActionTypes } from 'context/searchProvider';
import { PropTypes } from 'prop-types';
export default class Search extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
fieldOne: PropTypes.func.isRequired,
fieldTwo: PropTypes.func.isRequired,
...
}
render() {
const fieldContent =
this.props.fieldOne === 'Some value' ? (
<FieldsSection
item={this.props.item}
updateFieldOne={this.props.fieldOne}
updateFieldTwo={this.props.fieldTwo}
...
errors={this.props.errors}
/>
) : (
<DifferentFieldsSection
selectList={this.props.selectList}
selection={this.props.selection}
onChange={this.props.updateFieldFour}
errors={this.props.errors}
/>
);
return fieldContent;
}
}
FieldsSection.jsx (DifferentFieldsSection is similar)
import React, { Component } from 'react';
import {
NumberInputControl,
DatePickerInputControl,
SelectControl,
TextInputControl,
} from 'components/common/reactBootstrapWrappers';
export default class FieldsSection extends Component {
static propTypes = {
item: PropTypes.object.isRequired,
updateFieldOne: PropTypes.func.isRequired,
updateFieldTwo: PropTypes.func.isRequired,
updatecity: PropTypes.func.isRequired,
...
errors: PropTypes.array.isRequired
};
render() {
const {
item,
updateFieldOne,
updateaddress2,
updateFieldTwo,
...
errors,
} = this.props;
const stateInfoDetail = this.getstateInfoDetail();
return (
<div>
<TextInputControl
ref={(input) => {
value={item.fieldOne}
onChange={updateFieldOne}
...
errors={errors.fieldOne}
/>
<TextInputControl
value={item.fieldTwo}
onChange={updateFieldTwo}
...
errors={errors.fieldTwo}
/>
// One control for each field passed to the component
...
);
}
}
Explanation
ParentComponent is the component that renders the page and communicates with the backend. It receives a dispatch property from the Routes.jsx components (which uses react-router) by being wrapped around a Context Provider and consumer like so:
@withRouter
export default class Routes extends Component {
render() {
return (
<div>
<SearchProvider>
<SearchConsumer>
{
{ dispatch, errors } =>
(
<Switch>
<Route path="/search" component={ParentComponent} dispatch={dispatch} ... />
</Switch>
)
}
</SearchConsumer>
</SearchProvider>
</div>
);
}
}
It validates the fields that are completed in either FieldsSection component and posts them to the back-end in the dispatchDetails() method. In order to do the validation, it needs to keep its state updated. In order to do that, it injects update functions (e.g updateFieldOne()) to its child component Search, which in turn injects it to either FieldsSection component. Those update functions are then called by one of the components that wrap react-bootstrap controls when the user modifies their value.
The logic that renders the FieldsSection components is complicated enough that it gets wrapped in the Search component, which is also used elsewhere in other components, while the ParentComponent also renders other elements that I excluded for simplicity's sake. But in those components, you also need to inject those update functions to keep the respective parent component's state updated. Also, the parent components are the only ones that get to consume the dispatch function to POST or GET information to the backend.
The SearchConsumer code isn't pretty neither:
import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import api from 'apis/api';
const Context = React.createContext();
export const ActionTypes = {
GETDATA: 'GETDATA',
POSTSEARCH: 'POSTSEARCH',
... // Other methods
};
export class SearchProvider extends Component {
static propTypes = {
children: PropTypes.any,
};
static defaultProps = {
children: ,
};
constructor() {
super();
this.reducer = this.reducer.bind(this);
}
state = {
items: ,
dispatch: (action, params, callback) => {
this.reducer(action, params, callback);
},
result: {},
errors: {},
};
reducer = (action, params, callback) => {
switch (action.type) {
case ActionTypes.GETDATA:
this.getData();
break;
case ActionTypes.POSTSEARCH:
this.postSearch(params, callback);
break;
... // Other methods
}
};
getData = () => {
api
.getItems()
.then((result) => {
if (result) {
this.setState({ items: result.Results });
}
})
.catch(() => {})
}
postSearch = () => {
api
.postSearch()
.then((result) => {
if (result) {
if (result.Results.length === 1) {
this.setState({ result: result.Results[0], errors: {} }, callback);
}
}
})
.catch((error) => {
if (error && error.FieldErrors) {
this.setState({ errors: error.FieldErrors, result: {} }, callback);
}
})
}
... // Other methods
render() {
return <Context.Provider value={this.state}>{this.props.children}</Context.Provider>;
}
}
export const SearchConsumer = Context.Consumer;
I'm sure the successive injections of the update functions and the way the SearchConsumer and SearchProvider are built are wrong or disencouraged, at least. But I don't know what alternatives I have.
Thank you.
javascript react.js
javascript react.js
asked Nov 13 at 13:51
Heathcliff
1184
1184
add a comment |
add a comment |
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207557%2freact-application-design-parent-component-that-controls-access-to-backend-and-n%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown