Lot of repetition in React component - reactjs

I have a rather large React component that manages the display of a detail for a job on my site.
There are a few things that I would like to do smarter
The component has a few options for opening Dialogs. For each dialog I have a separate Open and Close function. For example handleImageGridShow and handleImageGridClose. Is there any way to be more concise around this?
I have many presentational components (e.g. ViewJobDetails) that shows details about the job. My issue is that I have to pass them down into each Component as a prop and I'm passing the same props over and over again
As I'm loading my data from firebase I often have to do similar checks to see if the data exists before I render the component (e.g.this.state.selectedImageGrid && <ImageGridDialog />). Is there any more clever way of going about this?
import React, { Component } from 'react';
import { withStyles } from 'material-ui/styles';
import ViewJobAttachment from "../../components/jobs/viewJobAttachment";
import ViewJobDetails from "../../components/jobs/viewJob/viewJobDetails";
import ViewJobActions from "../../components/jobs/viewJob/viewJobActions";
import ViewCompanyDetails from "../../components/jobs/viewJob/viewCompanyDetails";
import ViewClientsDetails from "../../components/jobs/viewJob/viewClientsDetails";
import ViewProductsDetails from "../../components/jobs/viewJob/viewProductsDetails";
import ViewAttachmentDetails from "../../components/jobs/viewJob/viewAttachmentDetails";
import ViewEventLogDetails from "../../components/jobs/viewJob/viewEventLogDetails";
import ViewSummaryDetails from "../../components/jobs/viewJob/viewSummary";
import {FirebaseList} from "../../utils/firebase/firebaseList";
import SimpleSnackbar from "../../components/shared/snackbar";
import {calculateTotalPerProduct} from "../../utils/jobsService";
import BasicDialog from "../../components/shared/dialog";
import ImageGrid from "../../components/shared/imageGrid";
import Spinner from "../../components/shared/spinner";
import ViewPinnedImageDialog from "../../components/jobs/viewEntry/viewPinnedImage";
import {
Redirect
} from 'react-router-dom';
const styles = theme => ({
wrapper: {
marginBottom: theme.spacing.unit*2
},
rightElement: {
float: 'right'
}
});
const ImageGridDialog = (props) => {
return (
<BasicDialog open={!!props.selectedImageGrid}
handleRequestClose={props.handleRequestClose}
fullScreen={props.fullScreen}
title={props.title}
>
<ImageGrid selectedUploads={props.selectedImageGrid}
handleClickOpen={props.handleClickOpen}/>
</BasicDialog>
)
};
class ViewJob extends Component {
constructor() {
super();
this.state = {
currentJob: null,
entries: [],
promiseResolved: false,
attachmentDialogOpen: false,
openAttachment: null,
selectedImageGrid: false,
selectedPinnedImage: false,
showSnackbar: false,
snackbarMsg: '',
markedImageLoaded: false,
loading: true,
redirect: false
};
this.firebase = new FirebaseList('jobs');
this.handleJobStatusChange = this.handleJobStatusChange.bind(this);
this.handleImageGridShow = this.handleImageGridShow.bind(this);
this.handleImageGridClose = this.handleImageGridClose.bind(this);
this.handlePinnedImageClose = this.handlePinnedImageClose.bind(this);
this.handlePinnedImageShow = this.handlePinnedImageShow.bind(this);
this.handleMarkedImageLoaded = this.handleMarkedImageLoaded.bind(this);
this.handleRemove = this.handleRemove.bind(this);
this.pushLiveToClient = this.pushLiveToClient.bind(this);
}
componentDidMount() {
this.firebase.db().ref(`jobs/${this.props.id}`).on('value', (snap) => {
const job = {
id: snap.key,
...snap.val()
};
this.setState({
currentJob: job,
loading: false
})
});
const previousEntries = this.state.entries;
this.firebase.db().ref(`entries/${this.props.id}`).on('child_added', snap => {
previousEntries.push({
id: snap.key,
...snap.val()
});
this.setState({
entries: previousEntries
})
});
}
handleRemove() {
this.firebase.remove(this.props.id)
.then(() => {
this.setState({redirect: true})
})
};
pushLiveToClient() {
const updatedJob = {
...this.state.currentJob,
'lastPushedToClient': Date.now()
};
this.firebase.update(this.state.currentJob.id, updatedJob)
.then(() => this.handleSnackbarShow("Job pushed live to client"))
}
handleJobStatusChange() {
const newState = !this.state.currentJob.completed;
const updatedJob = {
...this.state.currentJob,
'completed': newState
};
this.firebase.update(this.state.currentJob.id, updatedJob)
}
handleSnackbarShow = (msg) => {
this.setState({
showSnackbar: true,
snackbarMsg: msg
});
};
handleSnackbarClose= (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ showSnackbar: false });
};
handleAttachmentDialogClose =() => {
this.setState({attachmentDialogOpen: false})
};
handleClickOpen = (file) => {
this.setState({
attachmentDialogOpen: true,
openAttachment: file
});
};
handleImageGridShow(imageGrid) {
this.setState({selectedImageGrid: imageGrid})
}
handleImageGridClose() {
this.setState({selectedImageGrid: false})
}
handlePinnedImageShow(pinnedImage) {
this.setState({selectedPinnedImage: pinnedImage})
}
handlePinnedImageClose() {
this.setState({selectedPinnedImage: false})
}
handleMarkedImageLoaded() {
this.setState({markedImageLoaded: true})
}
render() {
const {classes} = this.props;
let {_, costPerItem} = calculateTotalPerProduct(this.state.entries);
if (this.state.redirect) {
return <Redirect to='/jobs' push/>
} else {
if (this.state.loading) {
return <Spinner/>
} else {
return (
<div className={styles.wrapper}>
{this.state.currentJob &&
<div>
<ViewJobActions currentJob={this.state.currentJob}
handleJobStatusChange={this.handleJobStatusChange}
pushLiveToClient={this.pushLiveToClient}
/>
<ViewJobDetails currentJob={this.state.currentJob}/>
<ViewCompanyDetails currentJob={this.state.currentJob}/>
<ViewClientsDetails currentJob={this.state.currentJob}/>
<ViewProductsDetails currentJob={this.state.currentJob}/>
{this.state.currentJob.selectedUploads && this.state.currentJob.selectedUploads.length > 0
? <ViewAttachmentDetails currentJob={this.state.currentJob} handleClickOpen={this.handleClickOpen}/>
: null}
<ViewEventLogDetails jobId={this.state.currentJob.jobId}
jobKey={this.state.currentJob.id}
entries={this.state.entries}
handlePinnedImageShow={this.handlePinnedImageShow}
handleImageGridShow={this.handleImageGridShow}/>
<ViewSummaryDetails stats={costPerItem}/>
<ViewJobAttachment open={this.state.attachmentDialogOpen}
handleRequestClose={this.handleAttachmentDialogClose}
attachment={this.state.openAttachment}
/>
{this.state.selectedImageGrid &&
<ImageGridDialog selectedImageGrid={this.state.selectedImageGrid}
handleRequestClose={this.handleImageGridClose}
handleClickOpen={this.handleClickOpen}
title="Pictures for job"
fullScreen={false}/>}
{this.state.selectedPinnedImage &&
<ViewPinnedImageDialog attachment={this.state.selectedPinnedImage}
open={!!this.state.selectedPinnedImage}
markedImageLoaded={this.state.markedImageLoaded}
handleMarkedImageLoaded={this.handleMarkedImageLoaded}
handleRequestClose={this.handlePinnedImageClose}
otherMarkedEntries={this.state.entries}
/>
}
<SimpleSnackbar showSnackbar={this.state.showSnackbar}
handleSnackbarClose={this.handleSnackbarClose}
snackbarMsg={this.state.snackbarMsg}/>
</div>}
</div>
);
}
}
}
}
export default withStyles(styles)(ViewJob);

You can define a regular component method and bind it in handler like this onSomething={this.handler.bind(this, index)} assuming you have some distinguishable thing in the index var
function should look like this
handler(index) {
...
}

Related

React Lifecycle- The plot is drawn using the initial state value, and not using the fetched value

I want to fetch the value of the variable 'r2score' from flask. The value is fetched successfully. I even wote a console.log(r2score) statement to see if the fetching works. Here's the problem. Initially it logged a value of 0.1, which is its initial state. then in the next line of the console it logged a value of 0.26, which the value that was fetched from flask. So atleast the fetching was successful. However, the plot that is being drawn, is drawn with a value of 0.1(it's initial state) and not 0.26(it's fetched value).
My Code:
import ReactDOM from "react-dom";
import React from "react";
import { Liquid } from "#antv/g2plot";
import ReactG2Plot from "react-g2plot";
class R2ScorePlot extends React.Component {
constructor(props) {
super(props);
this.state = { r2score: 0.1 };
}
componentDidMount() {
fetch(`/fetch_regressionmodel`)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error("Something went wrong ...");
}
})
.then(info =>
this.setState({
r2score: info.R2score
})
).then( this.forceUpdate() )
.catch(error => this.setState({ error }));
}
shouldComponentUpdate() {
return true;
}
render() {
const { r2score } = this.state;
console.log(r2score);
const config = {
title: {
visible: false,
text: ""
},
description: {
visible: false,
text: ""
},
min: 0,
max: 1,
value: r2score,
statistic: {
formatter: value => ((1 * value) / 1).toFixed(1)
}
};
return (
<div>
<ReactG2Plot Ctor={Liquid} config={config} />
</div>
);
}
}
export default R2ScorePlot;
Console Image
React Dev Tools
Have solved the issue. The solution was to wrap the graph component in a
<div key={r2score}>...</div>
So that the graph will rebuild whenever, the key changes.
My code:
import ReactDOM from "react-dom";
import React from "react";
import { Liquid } from "#antv/g2plot";
import ReactG2Plot from "react-g2plot";
class R2ScorePlot extends React.Component {
constructor(props) {
super(props);
this.state = { r2score: 0.1 };
}
componentDidMount() {
fetch(`/fetch_regressionmodel`)
.then(response => {
if (response.ok) {
this.setState({ spinloading: false });
return response.json();
} else {
throw new Error("Something went wrong ...");
}
})
.then(info =>
this.setState({
r2score: info.Explained_Variance_score
})
).catch(error => this.setState({ error }));
}
shouldComponentUpdate() {
return true;
}
render() {
const { r2score } = this.state;
console.log(r2score);
const config = {
title: {
visible: false,
text: ""
},
description: {
visible: false,
text: ""
},
min: 0,
max: 1,
value: r2score,
statistic: {
formatter: value => ((1 * value) / 1).toFixed(1)
}
};
return (
<div key={r2score}>
<ReactG2Plot Ctor={Liquid} config={config} />
</div>
);
}
}
export default R2ScorePlot;

How to add props to state

I have a problem when trying to add my redux array to a component’s state:
componentDidMount() {
this.props.cardAction()
this.setState({ showCards: this.props.cardAction() })
}
hhhhhh undefined console log undefined
Here’s my dashboard code:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import Header from '../../common/Header/'
import Masonry from '../../common/Masonry/'
import { cardAction } from '../../store/actions/Cards'
import Arrow_Down from '../../assets/img/arrow-down.svg'
class Dashboard extends Component {
componentDidMount() {
this.props.cardAction()
this.setState({ showCards: this.props.cardAction() })
}
constructor(props) {
super(props)
this.state = {
collapsed: true,
class: 'collapsed',
showCards: {},
}
this.toggleCollapse = this.toggleCollapse.bind(this);
}
toggleCollapse(i, info) {
console.log('i', info, 'iiiii', i)
this.setState({
collapsed: !this.state.collapsed,
class: this.state.collapsed ? '' : 'collapsed',
showCards: info
}, () => {
// my state is updated here !
console.log('cardsss', this.state.showCards)
})
if (this.state.showCards === 'active') {
let carddd = this.state.showCards
this.setState({
showCards: {
...this.state.showCards,
open: 'inactive'
}
});
}
else {
this.setState({
showCards: {
...this.state.showCards,
open: 'active'
}
});
}
}
render() {
console.log('hhhhhh', this.state.showCards)
const cardList = this.props.Cards.map((info, i) => {
return (
<div className={(info.open === 'active') ? 'collapsed' : ''} key={i}>
<div className={(info.open === 'active') ? 'header flex space-between active' : 'header flex space-between'}>
<h2>{info.title}</h2>
<span onClick={() => { this.toggleCollapse(i, info) }}><img src={Arrow_Down} alt='Arrow' /></span>
</div>
<div className='content'>
<p>{info.description}</p>
</div>
</div>
)
})
return (
<div>
<Header />
<Masonry columns={3} gap={20}>
{cardList}
</Masonry>
</div>
)
}
}
Dashboard.defaultProps = {
columns: 2,
gap: 20,
Cards: []
}
Dashboard.propTypes = {
Cards: PropTypes.array.isRequired,
}
const mapStateToProps = state => {
return { Cards: state.cards.result }
}
const mapDispatchToProps = dispatch => ({
cardAction: () => dispatch(cardAction())
})
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
this.props.cardAction() is a redux action, it's not meant for you to directly assign to state, reason is redux action will return to reducer, not component. You should remove the setState in componentDidMount
componentDidMount() {
this.props.cardAction();
}
When you invoked this.props.cardAction(), it will call the function that defined in redux action file, and the result will be available at this.props.Cards as you mentioned above
const mapStateToProps = state => {
return { Cards: state.cards.result }
}

How to rerender one sibling component due to change of second sibling component

I have this structure:
<Filter>
<Departure setDeparture={this.setDeparture} />
<Destination setDestination={this.setDestination} iataDeparture={this.state.departure} />
<DatePicker setDates={this.setDates} />
<SearchButton />
</Filter>
Now, I try to rerender Destination component when I update Departure component. Unfortunatelly my code doesn't work.
I don't use redux because I don't know it yet, so I try solutions without redux.
Please, help me with this problem.
Here goes code for each component:
Filter:
import React, { Component } from 'react';
import axios from 'axios';
import Departure from './Departure';
import Destination from './Destination';
import DatePicker from './DatePicker';
import SearchButton from './SearchButton';
class Filter extends Component {
constructor(props) {
super(props);
this.state = {
departure: '',
destination: '',
startDate: '',
endDate: '',
flights: []
}
}
handleSubmit = event => {
const getFlights = `https://murmuring-ocean-10826.herokuapp.com/en/api/2/flights/from/${this.state.departure}/to/${this.state.destination}/${this.state.startDate}/${this.state.endDate}/250/unique/?limit=15&offset-0`;
event.preventDefault();
console.log(this.state.departure);
console.log(this.state.destination);
console.log(this.state.startDate);
console.log(this.state.endDate);
axios.get(getFlights)
.then(response => {
this.setState({ flights: response.data.flights });
console.log(getFlights);
console.log(this.state.flights);
this.props.passFlights(this.state.flights);
});
}
setDeparture = departure => {
this.setState({ departure: departure });
}
setDestination = destination => {
this.setState({ destination: destination });
}
setDates = (range) => {
this.setState({
startDate: range[0],
endDate: range[1]
});
}
render() {
return (
<section className='filter'>
<form className='filter__form' onSubmit={this.handleSubmit}>
<Departure setDeparture={this.setDeparture} />
<Destination setDestination={this.setDestination} iataDeparture={this.state.departure} />
<DatePicker setDates={this.setDates} />
<SearchButton />
</form>
</section>
);
}
}
export default Filter;
Departure:
import React, { Component } from 'react';
import axios from 'axios';
const url = 'https://murmuring-ocean-10826.herokuapp.com/en/api/2/forms/flight-booking-selector/';
class Departure extends Component {
constructor(props) {
super(props);
this.state = {
airports: [],
value: '',
iataCode: ''
}
}
componentDidMount() {
axios.get(url)
.then(data => {
const airports = data.data.airports;
const updatedAirports = [];
airports.map(airport => {
const singleAirport = [];
singleAirport.push(airport.name);
singleAirport.push(airport.iataCode);
updatedAirports.push(singleAirport);
return singleAirport;
});
this.setState({
airports: updatedAirports,
value: airports[0].name,
iataCode: airports[0].iataCode
});
this.props.setDeparture(this.state.iataCode);
});
}
handleChange = event => {
const nameValue = event.target.value;
const iataCode = this.state.airports.find(airport => {
return airport[0] === nameValue;
});
this.setState({
value: event.target.value,
iataCode: iataCode[1]
});
this.props.setDeparture(iataCode[1]);
}
render() {
const departureNames = this.state.airports;
let departureOptions = departureNames.map((item, index) => {
return (
<option value={item[0]} key={index}>{item[0]}</option>
);
});
return (
<div className='filter__form__select'>
<select value={this.state.value} onChange={this.handleChange}>
{departureOptions}
</select>
</div>
);
}
}
export default Departure;
Destination:
import React, { Component } from 'react';
import axios from 'axios';
const url = 'https://murmuring-ocean-10826.herokuapp.com/en/api/2/forms/flight-booking-selector/';
class Destination extends Component {
constructor(props) {
super(props);
this.state = {
routes: {},
airports: [],
value: '',
iataCode: '',
iataDestinationAirports: '',
options: []
}
}
componentDidMount() {
axios.get(url)
.then(data => {
const routes = data.data.routes;
const airports = data.data.airports;
const updatedAirports = [];
airports.map(airport => {
const singleAirport = [];
singleAirport.push(airport.name);
singleAirport.push(airport.iataCode);
updatedAirports.push(singleAirport);
return singleAirport;
});
this.setState({
routes: routes,
airports: updatedAirports,
});
})
.then(() => {
this.getNamesFromIataCode();
this.props.setDestination(this.state.iataDestinationAirports);
});
}
componentDidUpdate(prevProps) {
if (this.props.iataDeparture !== prevProps.iataDeparture) {
this.setState({ iataCode: this.props.iataDeparture });
() => this.getNamesFromIataCode();
};
}
handleChange = (event) => {
const nameValue = event.target.value;
const iataCode = this.state.airports.find(airport => {
return airport[0] === nameValue;
});
this.setState({
value: event.target.value,
iataDestinationAirports: iataCode[1]
});
this.props.setDestination(iataCode[1]);
}
getNamesFromIataCode = () => {
const iataCode = this.state.iataCode;
console.log(iataCode);
const destinationNames = this.state.routes[iataCode];
let destionationAirports = destinationNames.map(item => {
return this.state.airports.filter(el => {
return el[1] === item;
});
});
let arrayOfOptions = [];
let firstOptionIataCode = '';
let firstOptionName = '';
let destinationOptions = destionationAirports.map((item, index) => {
console.log(item);
arrayOfOptions.push(item[0]);
return (
<option value={item[0][0]} key={index}>{item[0][0]}</option>
);
});
firstOptionIataCode = arrayOfOptions[0][1];
firstOptionName = arrayOfOptions[0][0];
console.log(firstOptionIataCode);
this.setState({
options: destinationOptions,
iataDestinationAirports: firstOptionIataCode,
value: firstOptionName
});
console.log(this.state.iataDestinationAirports);
console.log(this.state.options);
return destinationOptions;
}
render() {
const selectionOptions = this.state.options;
return (
<div className='filter__form__select'>
<select value={this.state.value} onChange={this.handleChange}>
{selectionOptions}
</select>
</div>
);
}
}
export default Destination;
As Tholle mentioned, you need to lift the state up. Here's an example:
import React from "react";
import ReactDOM from "react-dom";
const A = ({ users, selectUser }) => {
return (
<React.Fragment>
<h1>I am A.</h1>
{users.map((u, i) => {
return <button onClick={() => selectUser(i)}>{u}</button>;
})}
</React.Fragment>
);
};
const B = ({ user }) => {
return <h1>I am B. Current user: {user}</h1>;
};
const C = ({ user }) => {
return <h1>I am C. Current user: {user}</h1>;
};
class App extends React.Component {
state = {
users: ["bob", "anne", "mary"],
currentUserIndex: 0
};
selectUser = n => {
this.setState({
currentUserIndex: n
});
};
render() {
const { users, currentUserIndex } = this.state;
const currentUser = users[currentUserIndex];
return (
<React.Fragment>
<A selectUser={this.selectUser} users={users} />
<B user={currentUser} />
<C user={currentUser} />
</React.Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working example here.

Adding events to ReactBigCalendar using redux

I'm currently working on a booking app, and in that regard I need to fetch some data from an API. I'm using react + redux to do this, but I can't seem to get this element BigCalendar to update when the state is updated. BigCalendar has to have some kind of object for it's init process.
BookingReducer.js
import { fetchBookingStart, fetchBookingsSuccess, fetchBookingsError } from '../types.js';
const initialState = {
fetching: false,
fetched: false,
events: [],
error: null
}
export default function (state = initialState, action) {
switch (action.type) {
case fetchBookingStart:
return {
...state,
fetching: true,
fetched: false
}
case fetchBookingsSuccess:
return {
...state,
fetching: false,
fetched: true,
events: action.payload.bookings.length < 0 ? [] : action.payload.bookings.map(B => {
return {
id:22,
title: "testTitle",
description: B.description,
start: B.dateFrom,
end: B.dateTo,
room: B.room,
user: B.user
}
})
}
case fetchBookingsError:
return {
...state,
fetching: false,
error: action.payload.error
}
default:
return state;
}
}
bookingAction.js
import axios from "axios";
import { fetchBookingStart, fetchBookingsSuccess, fetchBookingsError } from '../types.js';
const apiUrl = "http://localhost/api/booking"; //CHANGE FOR PROD!
export const getBookings = () => dispatch => {
let allUrl = apiUrl + "/find/";
dispatch({type: fetchBookingStart});
axios.get(allUrl).then(response => {
console.log(response.data);
dispatch({
type: fetchBookingsSuccess,
payload: response.data
})
}).catch(error => {
dispatch({
type: fetchBookingsError,
payload: error
});
});
}
App.js
import React, { Component } from "react";
import { connect } from 'react-redux';
import { getBookings } from './components/redux/actions/bookingActions';
import Link from "react-router-dom/Link";
import BigCalendar from 'react-big-calendar';
import moment from 'moment';
import 'react-big-calendar/lib/css/react-big-calendar.css';
import "./style.css";
import 'moment/locale/da';
import BookingDialog from './components/booking_create_dialog/BookingDialog';
import Dialog from "material-ui/Dialog/Dialog";
import FlatButton from "material-ui/FlatButton/FlatButton";
let isDialogOpen = false;
moment.locale('da');
BigCalendar.momentLocalizer(moment);
function eventStyleGetter(event, start, end, isSelected) {
let style = { backgroundColor: "" }
switch (event.room) {
case "sal":
style.borderColor = "#781B7F";
style.backgroundColor = "#781B7F";
break;
case "cafe":
style.borderColor = "#067F3D";
style.backgroundColor = "#067F3D";
break;
default:
break;
}
return { style: style };
}
class App extends Component {
componentWillMount() {
this.props.getBookings();
}
componentDidUpdate() {
if (this.props.fetched === true && this.props.fetching === false) {
this.refs.BigCalendar.forceUpdate();
}
}
render() {
return (
<div className="MainContainer">
{/*<Link to="/login"><FlatButton>Login</FlatButton></Link>*/}
<div className="CalendarContainer">
<BigCalendar
ref="BigCalendar"
selectable
className="Calendar"
events={this.props.events}
defaultView="week"
defaultDate={new Date()}
step={60}
eventPropGetter={eventStyleGetter}
/>
</div>
<div className="TestContainer">
<button onClick={() => {
this.props.events.push({
id: 55,
title: "test event",
allDay: true,
start: new Date(),
end: new Date()
}); console.log(this.props.events)
}}> bookigns </button>
{this.props.events.map(E => <h1> {E.room} </h1>)}
</div>
</div>
)
}
}
const mapStateToProps = state => ({
events: state.bookings.events,
fetched: state.bookings.fetched,
fetching: state.bookings.fetching
})
export default connect(mapStateToProps, { getBookings })(App);
Please point out any mistakes that can help me along the right way of doing this.
I was having a similar problem with my react-redux setup and realized my events array of objects wasn't formatted correctly. Other than checking this, I'd also check to see if BigCalendar is getting rendered with anything from this.props.events in the first place. Every time the events prop is changed for BigCalendar it updates itself.

Assigning state to props from redux does not work

import React, { Component } from 'react';
import DisplayTable from './Table.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
menuItems: this.props.menu_items,
searchString: '',
displayItems: this.props.menu_items
}
this.search = this.search.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentWillMount() {
this.props.get_menu_items_api(false);
}
componentWillReceiveProps(nextProps) {
this.setState({ menuItems: nextProps.menu_items })
}
handleChange(e, isEnter) {
const searchData = () => {
let tempMenuProductDetails = this.props.menu_items;
const filterArray = tempMenuProductDetails.reduce((result, category) => {
if (category.categoryName.toLowerCase()
.indexOf(this.state.searchString.toLowerCase()) > -1) {
result.push(category);
}
if (category.productList && category.productList.length > 0) {
category.productList = category.productList.reduce((productListResult,
productList) => {
if (!!productList.productName &&
productList.productName.toLowerCase()
.indexOf(this.state.searchString.toLowerCase()) > -1)
{
productListResult.push(productList);
}
return productListResult;
}, []);
}
return result;
}, []);
this.setState({
displayItems: filterArray
}, function () {
console.log(this.state.displayItems);
})
console.log(filterArray);
}
if (!isEnter) {
this.setState({
searchString: e.target.value
});
} else {
searchData();
}
}
search(e) {
if (e.keyCode == 13) {
this.handleChange(e, true);
}
this.handleChange(e, false);
}
render() {
console.log(this.state.displayItems);
console.log(this.props.menu_items);
console.log(this.state.menuItems);
return (
<DisplayTable dataProp={this.state.displayItems} editFuncProp=
{this.props.edit_menu_items_api} /> )
}
}
export default App;
I have this search function in this file that does not update the value of props coming from the container of redux. Now when I pass {this.state.displayItems} in menu ,it does not display the data.
But when I pass {this.props.menu_items} it displays the data and I am not able to modify this.props.menu_items on the basis of search.
I have tried this code . what should i do?
The problem seems to be that, initially this.props.menu_items is an empty array and only after some API call the value is updated and you get the returned array on the second render, thus if you use it like
<DisplayTable dataProp={this.props.menu_items} editFuncProp=
{this.props.edit_menu_items_api} />
it works. Now that you use
<DisplayTable dataProp={this.state.displayItems} editFuncProp=
{this.props.edit_menu_items_api} />
and displayItems is only initialized in the constructor which is only executed once at the time, component is mounted and hence nothing is getting displayed.
The solution seems to be that you update the displayItems state in componentWillReceiveProps and call the search function again with the current search string so that you search results are getting updated.
Code:
import React, { Component } from 'react';
import DisplayTable from './Table.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
menuItems: this.props.menu_items,
searchString: '',
displayItems: this.props.menu_items
}
this.search = this.search.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentWillMount() {
this.props.get_menu_items_api(false);
}
componentWillReceiveProps(nextProps) {
this.setState({ menuItems: nextProps.menu_items, displayItems: nextProps.menu_items })
this.handleChange(null, true);
}
handleChange(e, isEnter) {
const searchData = () => {
let tempMenuProductDetails = this.props.menu_items;
const filterArray = tempMenuProductDetails.reduce((result, category) => {
if (category.categoryName.toLowerCase()
.indexOf(this.state.searchString.toLowerCase()) > -1) {
result.push(category);
}
if (category.productList && category.productList.length > 0) {
category.productList = category.productList.reduce((productListResult,
productList) => {
if (!!productList.productName &&
productList.productName.toLowerCase()
.indexOf(this.state.searchString.toLowerCase()) > -1)
{
productListResult.push(productList);
}
return productListResult;
}, []);
}
return result;
}, []);
this.setState({
displayItems: filterArray
}, function () {
console.log(this.state.displayItems);
})
console.log(filterArray);
}
if (!isEnter) {
this.setState({
searchString: e.target.value
});
} else {
searchData();
}
}
search(e) {
if (e.keyCode == 13) {
this.handleChange(e, true);
}
this.handleChange(e, false);
}
render() {
console.log(this.state.displayItems);
console.log(this.props.menu_items);
console.log(this.state.menuItems);
return (
<DisplayTable dataProp={this.state.displayItems} editFuncProp=
{this.props.edit_menu_items_api} /> )
}
}
export default App;

Resources