I have a list of results, and each time I click on a result, it calls my addFunc function to add that particular result's details as an object in the selectedData list. Currently, this works when I console.log my selectedData list.
What I want to do is display my selectedData list, and each time a result is clicked, the object will be added to the list, and I want that to be reflected on my screen.
I believe I have to use state to do this, but I am not very sure how to. Thank you for your help.
A summary of my code is below:
let selectedData = [];
const Wrapper = cb => {
return (res, triggerClickAnalytics) => (
<RenderItem
res={res}
triggerClickAnalytics={triggerClickAnalytics}
addFunc={cb}
/>
);
};
class Search extends Component {
constructor(props) {
super(props);
this.addFunc = this.addFunc.bind(this);
}
addFunc(resultdata) {
selectedData = [...selectedData, resultdata]
console.log(selectedData)
}
render() {
return (
<ReactiveList
componentId="results"
dataField="_score"
pagination={true}
react={{
and: ["system", "grouping", "unit", "search"]
}}
size={10}
noResults="No results were found..."
renderItem={Wrapper(this.addFunc)}
/>
</ReactiveBase>
);
}
}
const RenderItem = ({ res, triggerClickAnalytics, addFunc }) => {
let { unit, title, system, score, proposed, id } = {
title: "maker_tag_name",
proposed: "proposed_standard_format",
unit: "units",
system: "system",
score: "_score",
id: "_id"
};
const resultdata = { id, title, system, unit, score, proposed };
return (
<Button
shape="circle"
icon={<CheckOutlined />}
style={{ marginRight: "5px" }}
onClick={() => addFunc(resultdata)}
/>
);
};
The whole code is in this code sandbox: https://codesandbox.io/s/little-framework-spg1w
EDIT:
I've managed to change my function to a state by editing my code as such:
constructor(props) {
super(props);
this.addFunc = this.addFunc.bind(this);
this.state = { selectedData:[] }
}
addFunc(resultdata) {
var joined = this.state.selectedData.concat(resultdata);
this.setState({ selectedData: joined })
console.log(joined)
}
I now need to display the results in my joined array on the screen. How do I do this?
Replace Search.js with
import React, { useState, Component } from "react";
import {
ReactiveBase,
DataSearch,
MultiList,
SelectedFilters,
ReactiveList
} from "#appbaseio/reactivesearch";
import { Row, Button, Col } from "antd";
import { CheckOutlined } from "#ant-design/icons";
const Wrapper = cb => {
return (res, triggerClickAnalytics) => (
<RenderItem
res={res}
triggerClickAnalytics={triggerClickAnalytics}
addFunc={cb}
/>
);
};
class Search extends Component {
constructor(props) {
super(props);
this.state = {
selectedData : []
}
this.addFunc = this.addFunc.bind(this);
}
addFunc(resultdata) {
this.setState(prevState => ({selectedData: [...prevState.selectedData, resultdata]}))
}
render() {
return (
<div>
<ReactiveBase
app="datataglist"
credentials="mRgWyoKGQ:f47be2a6-65d0-43b6-8aba-95dbd49eb882"
url="https://scalr.api.appbase.io"
>
<DataSearch
componentId="search"
dataField={[
"maker_tag_name",
"maker_tag_name.autosuggest",
"maker_tag_name.keyword"
]}
fieldWeights={[6, 2, 6]}
fuzziness={1}
highlightField={["maker_tag_name"]}
placeholder="Search Tag Name"
style={{
marginBottom: 20
}}
title="Maker Tag Name"
/>
<Row gutter={16}>
<Col span={8}>
<MultiList
componentId="system"
dataField="system.keyword"
queryFormat="or"
size={100}
sortBy="asc"
style={{
marginBottom: 20
}}
title="System"
/>
</Col>
<Col span={8}>
<MultiList
componentId="grouping"
dataField="grouping.keyword"
size={100}
style={{
marginBottom: 20
}}
title="Grouping"
/>
</Col>
<Col span={8}>
<MultiList
componentId="unit"
dataField="units.keyword"
size={100}
style={{
marginBottom: 20
}}
title="Unit"
/>
</Col>
</Row>
<SelectedFilters />
<ReactiveList
componentId="results"
dataField="_score"
pagination={true}
react={{
and: ["system", "grouping", "unit", "search"]
}}
size={10}
noResults="No results were found..."
renderItem={Wrapper(this.addFunc)}
/>
</ReactiveBase>
<div />
</div>
);
}
}
function getNestedValue(obj, path) {
const keys = path.split(".");
const currentObject = obj;
const nestedValue = keys.reduce((value, key) => {
if (value) {
return value[key];
}
return "";
}, currentObject);
if (typeof nestedValue === "object") {
return JSON.stringify(nestedValue);
}
return nestedValue;
}
const RenderItem = ({ res, triggerClickAnalytics, addFunc }) => {
// console.log(name);
let { unit, title, system, score, proposed, id } = {
title: "maker_tag_name",
proposed: "proposed_standard_format",
unit: "units",
system: "system",
score: "_score",
id: "_id"
};
title = getNestedValue(res, title);
system = getNestedValue(res, system);
unit = getNestedValue(res, unit);
score = getNestedValue(res, score);
proposed = getNestedValue(res, proposed);
id = getNestedValue(res, id);
const resultdata = { id, title, system, unit, score, proposed };
return (
<Row
onClick={triggerClickAnalytics}
type="flex"
gutter={16}
key={res._id}
style={{ margin: "20px auto", borderBottom: "1px solid #ededed" }}
>
<Col style={{ width: "360px" }}>
<h3
style={{ fontWeight: "600" }}
dangerouslySetInnerHTML={{
__html: title || "Choose a valid Title Field"
}}
/>
</Col>
<div style={{ padding: "20px" }} />
<Col>
<p
style={{ fontSize: "1em", width: "300px" }}
dangerouslySetInnerHTML={{
__html: system || "Choose a valid Description Field"
}}
/>
</Col>
<div style={{ padding: "10px" }} />
<Col>
<p
style={{ fontSize: "1em" }}
dangerouslySetInnerHTML={{
__html: unit || "-"
}}
/>
</Col>
<div style={{ padding: "10px" }} />
<Col style={{ minWidth: "120px" }}>
<p
style={{ fontSize: "1em", width: "300px" }}
dangerouslySetInnerHTML={{
__html: proposed || "Choose a valid Description Field"
}}
/>
</Col>
<div style={{ padding: "10px" }} />
<Col>
<p
style={{ fontSize: "1em" }}
dangerouslySetInnerHTML={{
__html: Math.round(score) || "Choose a valid Description Field"
}}
/>
</Col>
<Col>
<Button
shape="circle"
icon={<CheckOutlined />}
style={{ marginRight: "5px" }}
onClick={() => addFunc(resultdata)}
/>
</Col>
</Row>
);
};
export default Search;
Related
I am trying to change a parent's component state from a child component's state.
This is the parent component:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null,
loggedin: false
}
this.updateLanguage = this.updateLanguage.bind(this)
this.logIn = this.logIn.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then(
(repos) => this.setState({
repos,
error: null,
})
)
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
logIn() {
this.setState({
loggedin: true
})
}
render() {
const { selectedLanguage, repos, error, loggedin } = this.state
console.log(loggedin)
return (
<Router>
<div className='container'>
<LanguagesNav
selected={selectedLanguage}
onUpdateLanguage={this.updateLanguage}
/>
<Route
exact path='/'
render={(props) => (
<Login
repos={repos}
selectedLanguage={selectedLanguage}
logIn={this.logIn}
/>
)}
/>
<Route
path='/dashboard'
render={(props) => (
<Dashboard
repos={repos}
selectedLanguage={selectedLanguage}
/>
)}
/>
<Route
path='/profile'
render={(props) => (
<Profile
repos={repos}
selectedLanguage={selectedLanguage}
/>
)}
/>
<Route path='/newdashboard'>
<DrawerPage />
</Route>
</div>
</Router>
)
}
}
As I saw on this answer to a similar question, I am passing a function setting the state from parent to child, and then, I call the function via props from the child component:
function LoginForm ({ repos, selected }) {
const languages = ['EU', 'ES', 'EN']
var language = {}
switch (selected) {
case "EU":
selected = "EU";
language = repos[0].terms;
break;
case "ES":
selected = "ES";
language = repos[1].terms;
break;
case "EN":
selected = "EN";
language = repos[2].terms;
break;
}
return (
<ThemeProvider theme={theme}>
<Grid container component="main" sx={{ height: '100vh' }}>
<CssBaseline />
<Grid
item
xs={false}
sm={4}
md={7}
sx={{
backgroundImage: 'url(https://loginsso.ehu.es/login/images/forest.jpg)',
backgroundRepeat: 'no-repeat',
backgroundColor: (t) =>
t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900],
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
<Box
sx={{
my: 8,
mx: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
src="https://loginsso.ehu.es/login/images/logo_UPV_peq.png"
/>
<br/>
<Box component="form" noValidate sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label={language.username}
name="email"
autoComplete="email"
autoFocus
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label={language.password}
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label={language.remember}
/>
<Link
to={{
pathname: '/dashboard',
search: `?lang=${selected}`
}}
>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
// onClick={() => {
// alert('clicked');
// }}
>
{language.login}
</Button>
</Link>
<Grid container>
<Grid item xs>
<Link
to={{
pathname: '/newdashboard',
search: `?lang=${selected}`
}} variant="body2">
{language.forgot}
</Link>
</Grid>
<Grid item>
</Grid>
</Grid>
</Box>
</Box>
</Grid>
</Grid>
</ThemeProvider>
)
}
LoginForm.propTypes = {
repos: PropTypes.array.isRequired
}
export default class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null,
}
this.updateLanguage = this.updateLanguage.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then(
(repos) => this.setState({
repos,
error: null,
})
)
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
render() {
const { selectedLanguage, repos, error } = this.props
return (
<React.Fragment>
{this.props.logIn} //calling the parent's function
{error && <p>{error}</p>}
{repos && <LoginForm repos={repos} selected={selectedLanguage}/>}
</React.Fragment>
)
}
}
However, the state won't change.
Okay, I found a solution to my question. I tried this on the child component:
function LoginForm ({ repos, selected, onLogIn }) {
const languages = ['EU', 'ES', 'EN']
var language = {}
switch (selected) {
case "EU":
selected = "EU";
language = repos[0].terms;
break;
case "ES":
selected = "ES";
language = repos[1].terms;
break;
case "EN":
selected = "EN";
language = repos[2].terms;
break;
}
return (
<ThemeProvider theme={theme}>
<Grid container component="main" sx={{ height: '100vh' }}>
<CssBaseline />
<Grid
item
xs={false}
sm={4}
md={7}
sx={{
backgroundImage: 'url(https://loginsso.ehu.es/login/images/forest.jpg)',
backgroundRepeat: 'no-repeat',
backgroundColor: (t) =>
t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900],
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
<Box
sx={{
my: 8,
mx: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
src="https://loginsso.ehu.es/login/images/logo_UPV_peq.png"
/>
<br/>
<Box component="form" noValidate sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label={language.username}
name="email"
autoComplete="email"
autoFocus
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label={language.password}
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label={language.remember}
/>
<Link
to={{
pathname: '/dashboard',
search: `?lang=${selected}`
}}
>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
// I call the child component's method which at the same time calls the parent's component's method.
onClick={() => {onLogIn()}}
>
{language.login}
</Button>
</Link>
<Grid container>
<Grid item xs>
<Link
to={{
pathname: '/newdashboard',
search: `?lang=${selected}`
}} variant="body2">
{language.forgot}
</Link>
</Grid>
<Grid item>
</Grid>
</Grid>
</Box>
</Box>
</Grid>
</Grid>
</ThemeProvider>
)
}
LoginForm.propTypes = {
repos: PropTypes.array.isRequired
}
export default class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedLanguage: 'EU',
repos: null,
error: null
}
this.updateLanguage = this.updateLanguage.bind(this)
this.logIn = this.logIn.bind(this)
}
componentDidMount () {
this.updateLanguage(this.state.selectedLanguage)
}
updateLanguage (selectedLanguage) {
this.setState({
selectedLanguage,
error: null
})
fetchLanguageRepos(selectedLanguage)
.then(
(repos) => this.setState({
repos,
error: null,
})
)
.catch(() => {
console.warn('Error fetching repos: ', error)
this.setState({
error: 'There was an error fetching the repositories.'
})
})
}
// Created this function that calls the parent's logIn function.
logIn() {
this.props.logIn();
}
render() {
const { selectedLanguage, repos, error, logIn } = this.props
return (
<React.Fragment>
{error && <p>{error}</p>}
{/*I passed child component's method to the function component with the onLogIn attribute.*/}
{repos && <LoginForm repos={repos} selected={selectedLanguage} onLogIn={this.logIn}/>}
</React.Fragment>
)
}
}
Basically, I created a method on the child component that calls the method of the parent component.
I am not sure if I am using the right words to describe the solution, but I have used this solution in other parts of the project and it always works.
Feel free to comment with suggestions to explain my solution more correctly! :)
I have working code in App.jsx. Everything is working when this written together in one file.
const App = props => {
const [cartProducts, setCartProducts] = useState([]);
const [products, setProducts] = useState(getProducts());
//const [searchValue, setSearchValue] = useState();
const handleAddProductToCart = productID => {
setCartProducts([...cartProducts, productID]);
};
const handleRemoveFromCart = productID => {
const newCartProducts = cartProducts.filter(id => id !== productID);
setCartProducts(newCartProducts);
};
/*const filterItems = ({ description, title }) => {
return title.toLocaleLowerCase().indexOf(searchValue.toLocaleLowerCase())
|| description.toLocaleLowerCase().indexOf(searchValue.toLocaleLowerCase())
}*/
return (
<>
<Header/>
<Search/>
<Sidebar/>
<div>
{products.map(product => {
const { id, title, description, image } = product;
let haveInCart = false;
cartProducts.forEach(productID => {
if (productID === id) {
haveInCart = true;
}
});
return (
<Card key={id} className="item-card">
<CardImg src={image} alt={title} className="item-img" style={{ height: '260px' }} />
<CardBody style={{ border: 'none' }} className="custom-card-body">
<CardTitle style={{ border: 'none' }} className="custom-card-title">{title}</CardTitle>
<CardText style={{ border: 'none' }} className="custom-card-text">{description}</CardText>
<FontAwesomeIcon icon={faCartPlus} className="add-icon" onClick={() => handleAddProductToCart(id)} />
</CardBody>
</Card>
);
})}
<h2>Your Cart</h2>
{cartProducts.length > 0
? cartProducts.map(productID => {
const productIndex = products.findIndex(product => {
return product.id === productID;
});
let { id, title, image } = products[productIndex];
return (
<Card key={id} className="item-card">
<CardImg src={image} alt={title} className="item-img" style={{ height: '260px' }} />
<CardBody style={{ border: 'none' }} className="custom-card-body">
<CardTitle style={{ border: 'none' }} className="custom-card-title">{title}</CardTitle>
<FontAwesomeIcon icon={faTrash} className="remove-icon" onClick={() => handleRemoveFromCart(id)} />
</CardBody>
</Card>
);
})
: "Yor Cart is Empty :("}
</div>
</>
)
}
export default App;
I want to put product Card and cart Card into it's own component. And when I'm doing it, like this
const Product = ({product}) => {
const { id, title, description, image } = product;
return (
<Card key={id} className="item-card">
<CardImg src={image} alt={title} className="item-img" style={{ height: '260px' }} />
<CardBody style={{ border: 'none' }} className="custom-card-body">
<CardTitle style={{ border: 'none' }} className="custom-card-title">{title}</CardTitle>
<CardText style={{ border: 'none' }} className="custom-card-text">{description}</CardText>
<FontAwesomeIcon icon={faCartPlus} className="add-icon" /*onClick={() => handleAddProductToCart(id)}*/ />
</CardBody>
</Card>
)
}
I'm not getting properties of product in my Card. I want to make my code better organized and save it's functionality. How to access those states from Product and Cart component?
EDIT
Here's the link to codesandbox
https://codesandbox.io/s/late-cookies-r2inh?file=/src/App.jsx&fbclid=IwAR38tcE39tVL51YpG4_6A1HRz-kth1GSIocQWMPrU3QXepc5CHUNn-ZqiG8
EDIT 2 How I can make items in cart be displaying beside Product component, on the left side? Should I create entire Cart component for it?
Your Product.jsx file should look like this:
import React from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCartPlus } from "#fortawesome/free-solid-svg-icons";
import Card from "react-bootstrap/Card";
import CardBody from "react-bootstrap/Card";
import CardImg from "react-bootstrap/CardImg";
import CardTitle from "react-bootstrap/Card";
import CardText from "react-bootstrap/Card";
const Product = ({ product, handleAddProductToCart }) => {
const { id, title, description, image } = product;
return (
<Card key={id} className="item-card">
<CardImg
src={image}
alt={title}
className="item-img"
style={{ height: "260px" }}
/>
<CardBody style={{ border: "none" }} className="custom-card-body">
<CardTitle style={{ border: "none" }} className="custom-card-title">
{title}
</CardTitle>
<CardText style={{ border: "none" }} className="custom-card-text">
{description}
</CardText>
<FontAwesomeIcon
icon={faCartPlus}
className="add-icon"
onClick={() => handleAddProductToCart(id)}
/>
</CardBody>
</Card>
);
};
export default Product;
And in your App.jsx you can map the products array like this:
{products.map((product) => (
<Product
product={product}
key={product.id}
handleAddProductToCart={handleAddProductToCart}
/>
))}
EDIT:
https://codesandbox.io/s/determined-swartz-5q2fo
I edited the codesandbox, i made the Product component accept both add and remove product callback and the "haveInCart" prop. It will decide which icon and which callback to use base on that prop. I'm not sure about this approch tho
I'm building an application, where there is a form presented with different steps. In all the steps but one, I manage to provide the necessary functions as props to make some operations such as 'handleNext', 'handleBack' or 'handleChange'.
Nevertheless, in the last step, represented in the class SuccessForm, when I try to execute the 'handleDownload' function, I get the following error:
TypeError: this.props.handleDownload is not a function
Here it is the SuccessForm.js class:
export class SuccessForm extends Component {
constructor() {
super();
}
download = e => {
e.preventDefault();
this.props.handleDownload();
}
render() {
return (
<React.Fragment>
<Grid container>
<Grid item xs={12} sm={2}>
<DirectionsWalkIcon fontSize="large" style={{
fill: "orange", width: 65,
height: 65
}} />
</Grid>
<Grid>
<Grid item xs={12} sm={6}>
<Typography variant="h5" gutterBottom>
Route created
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle1">
Your new track was succesfully created and saved
</Typography>
</Grid>
</Grid>
<Tooltip title="Download" arrow>
<IconButton
variant="contained"
color="primary"
style={{
marginLeft: 'auto',
// marginRight: '2vh'
}}
onClick={this.download}
>
<GetAppIcon fontSize="large" style={{ fill: "orange" }} />
</IconButton>
</Tooltip>
</Grid>
</React.Fragment>
)
}
}
The entire NewRouteForm.js:
import React, { Component } from 'react'
import { makeStyles, MuiThemeProvider } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import DataForm from '../stepper/dataform/DataForm';
import ReviewForm from '../stepper/reviewform/ReviewForm';
import MapForm from '../stepper/mapform/MapForm';
import NavBar from '../../graphic interface/NavBar';
import DirectionsWalkIcon from '#material-ui/icons/DirectionsWalk';
import Avatar from '#material-ui/core/Avatar';
import CheckCircleOutlineOutlinedIcon from '#material- ui/icons/CheckCircleOutlineOutlined';
import FilterHdrIcon from '#material-ui/icons/FilterHdr';
import Grid from '#material-ui/core/Grid';
import SuccessForm from '../stepper/success/SuccessForm';
import { withStyles } from '#material-ui/styles';
export class NewRouteForm extends Component {
state = {
activeStep: 0,
name: '',
description: '',
date: new Date(),
photos: [],
videos: [],
points: []
};
handleNext = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep + 1 });
};
handleBack = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep - 1 });
};
handleChange = input => e => {
this.setState({ [input]: e.target.value });
}
handleDateChange = date => {
this.setState({ date: date });
}
handleMediaChange = (selectorFiles: FileList, code) => { // this is not an error, is TypeScript
switch (code) {
case 0: // photos
this.setState({ photos: selectorFiles });
break;
case 1: // videos
this.setState({ videos: selectorFiles });
break;
default:
alert('Invalid media code!!');
console.log(code)
break;
}
}
handleMapPoints = points => {
this.setState({ points: points })
}
// ###########################
// Download and Upload methods
// ###########################
handleDownload = () => {
// download route
console.log("DOWNLOAD")
alert("DOWNLOAD");
}
upload = () => {
// upload route
}
render() {
const { activeStep } = this.state;
const { name, description, date, photos, videos, points } = this.state;
const values = { activeStep, name, description, date, photos, videos, points };
const { classes } = this.props;
return (
<MuiThemeProvider>
<React.Fragment>
<NavBar />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<FilterHdrIcon fontSize="large" />
</Avatar>
<Typography component="h1" variant="h4" align="center">
Create your own route
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{activeStep === steps.length ? (
<SuccessForm />
) : (
<React.Fragment>
{getStepContent(activeStep,
values,
this.handleNext,
this.handleBack,
this.handleChange,
this.handleDateChange,
this.handleMediaChange,
this.handleMapPoints,
this.handleDownload
)}
</React.Fragment>
)}
</React.Fragment>
</Paper>
</main>
</React.Fragment>
</MuiThemeProvider>
)
}
}
const steps = ['Basic data', 'Map', 'Review your route'];
function getStepContent(step,
values,
handleNext,
handleBack,
handleChange,
handleDateChange,
handleMediaChange,
handleMapPoints,
handleDownload) {
switch (step) {
case 0:
return <DataForm
handleNext={handleNext}
handleChange={handleChange}
handleDateChange={handleDateChange}
handleMediaChange={handleMediaChange}
values={values}
/>;
case 1:
return <MapForm
handleNext={handleNext}
handleBack={handleBack}
handleMapPoints={handleMapPoints}
values={values}
/>;
case 2:
return <ReviewForm
handleNext={handleNext}
handleBack={handleBack}
values={values}
/>;
case 3:
return <SuccessForm
handleDownload={handleDownload}
/>;
default:
throw new Error('Unknown step');
}
}
const useStyles = theme => ({
layout: {
width: 'auto',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
width: 600,
marginLeft: 'auto',
marginRight: 'auto',
},
},
paper: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
padding: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
padding: theme.spacing(3),
},
},
stepper: {
padding: theme.spacing(3, 0, 5),
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
},
button: {
marginTop: theme.spacing(3),
marginLeft: theme.spacing(1),
},
avatar: {
marginLeft: 'auto',
marginRight: 'auto',
backgroundColor: theme.palette.warning.main,
},
icon: {
width: 65,
height: 65,
},
grid: {
marginLeft: theme.spacing(2),
}
});
export default withStyles(useStyles)(NewRouteForm);
Try calling super(props) in the constructor:
constructor(props) {
super(props);
}
and passing function with this instance (this.handleDownload) as it is a class property:
<SuccessForm handleDownload={this.handleDownload} />
Update:
You have a bug on the last step when you not passing a property:
activeStep === steps.length ? <SuccessForm handleDownload={this.handleDownload}/>
Assuming that you have a class in your parent Component, what you're missing is the this keyword in the function reference...
case 3:
return <SuccessForm
handleDownload={this.handleDownload}
/>;
I am getting data from the backend using postman. But when i am using frontend for the same, it is not working. I am getting errors like
1.TypeError: Cannot read property 'map' of null
2.Unhandled Rejection (TypeError): Cannot read property 'map' of null.
I think, I am getting this error because cards are not able to render when I am searching.The backend data is coming as a array.
const styles = theme => ({
appBar: {
position: 'relative',
},
icon: {
marginRight: theme.spacing.unit * 2,
},
layout: {
width: 'auto',
marginLeft: theme.spacing.unit * 3,
marginRight: theme.spacing.unit * 3,
[theme.breakpoints.up(1100 + theme.spacing.unit * 3 * 2)]: {
width: 1100,
marginLeft: 'auto',
marginRight: 'auto',
},
},
cardGrid: {
padding: `${theme.spacing.unit * 8}px 0`,
},
card: {
height: '100%',
display: 'flex',
flexDirection: 'column',
},
cardContent: {
flexGrow: 1,
},
});
class Products extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
searchString: ''
};
this.onSearchInputChange = this.onSearchInputChange.bind(this);
this.getProducts = this.getProducts.bind(this);
}
componentDidMount() {
this.getProducts();
}
// delete = id => {
// axios.post('http://localhost:9022/products/delete/' + id)
// .then(res => {
// let updatedProducts = [...this.state.products].filter(i => i.id !== id);
// this.setState({ products: updatedProducts });
// });
// }
delete = id => {
axios.post('http://localhost:9022/products/delete/' + id)
.then(res => {
this.setState((prevState, prevProps) => {
let updatedProducts = [...prevState.products].filter(i => i.id !== id);
return ({
products: updatedProducts
});
});
});
}
getProducts() {
axios.get('http://localhost:9022/products/getAll')
.then(res => {
this.setState({ products: res.data }, () => {
console.log(this.state.products);
});
});
}
onSearchInputChange = (event) => {
let newSearchString = '';
if (event.target.value) {
newSearchString = event.target.value;
}
axios.get('http://localhost:9022/products/getproducts' + newSearchString)
.then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
this.getProducts();
}
// onSearchInputChange(event) {
// let newSearchString = '';
// if (event.target.value) {
// newSearchString = event.target.value;
// }
// // call getProducts once React has finished updating the state using the callback (second argument)
// this.setState({ searchString: newSearchString }, () => {
// this.getProducts();
// });
// }
render() {
const { classes } = this.props;
return (
<React.Fragment>
<TextField style={{ padding: 24 }}
id="searchInput"
placeholder="Search for products"
margin="normal"
onChange={this.onSearchInputChange} />
<CssBaseline />
<main>
<div className={classNames(classes.layout, classes.cardGrid)}>
<Grid container spacing={40}>
{this.state.products.map(currentProduct => (
<Grid item key={currentProduct.id} sm={6} md={4} lg={3}>
<Card className={classes.card}>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
{currentProduct.title}
</Typography>
<Typography>
{currentProduct.price}
</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary" component={Link} to={"/products/" + currentProduct.id}>
Edit
</Button>
<Button size="small" color="primary" onClick={() => this.delete(currentProduct.id)}>
Delete
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</div>
</main>
</React.Fragment>
)
}
}
Products.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Products);
It has been observed that, you have added wrong URL of getproducts, slash is missing in the URL. Please find details below:
If search-string is r, then you are using this URL of getproducts: http://localhost:9022/products/getproductsr
which is wrong and it should be http://localhost:9022/products/getproducts/r
Hence you have to change your code of retrieving products as follows:
axios.get('http://localhost:9022/products/getproducts/' + newSearchString)
.then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
Also it will be good to provide a check for undefined/null for this.state.products and then render the components because it is possible that products might be null if one provide wrong URL and undefined as axios request is async. Hence by adding 'this.state.products && ' in existing render code will be good to avoid such issues. I have updated your render function, please find it below:
render() {
const { classes } = this.props;
return (
<React.Fragment>
<TextField style={{ padding: 24 }}
id="searchInput"
placeholder="Search for products"
margin="normal"
onChange={this.onSearchInputChange} />
<CssBaseline />
<main>
<div className={classNames(classes.layout, classes.cardGrid)}>
<Grid container spacing={40}>
{this.state.products && this.state.products.map(currentProduct => (
<Grid item key={currentProduct.id} sm={6} md={4} lg={3}>
<Card className={classes.card}>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
{currentProduct.title}
</Typography>
<Typography>
{currentProduct.price}
</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary" component={Link} to={"/products/" + currentProduct.id}>
Edit
</Button>
<Button size="small" color="primary" onClick={() => this.delete(currentProduct.id)}>
Delete
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</div>
</main>
</React.Fragment>
)
}
Hope it will help..
You may try this one:
You just missed the slash in the api end Point as below : Use this
axios.get('http://localhost:9022/products/getproducts/' + newSearchString)
instead of :
axios.get('http://localhost:9022/products/getproducts' + newSearchString)
I am using 'react-form-validator-core' package and trying to create a custom form validator that implements 'mui-downshift', a Material UI implementation of PayPal's downshift. This question is mostly about 'react-form-validator-core' package itself. The problem is that the form itself does not register the validator component I've created. Here is my full code of the custom component and the form itself. I've exhausted my debugging skills, but what I noticed is that there's something wrong with the this.context in the form...
Validator component:
import React from 'react';
import PropTypes from 'prop-types';
import MuiDownshift from 'mui-downshift';
import { ValidatorComponent } from 'react-form-validator-core';
class AutocompleteValidator extends ValidatorComponent {
constructor(props) {
debugger;
super(props);
this.originalItems = props.items.map(({key, name}) => ({ text: name, value: key }));
this.handleStateChange = this.handleStateChange.bind(this);
this.errorText = this.errorText.bind(this);
}
componentWillMount() {
if (!this.filteredItems) {
this.setState({filteredItems: this.originalItems});
}
if (!!this.props.value) {
const selectedItem = this.originalItems.filter(
item => item.value.toLowerCase().includes(this.props.value.toLowerCase())
)[0];
this.setState({ selectedItem })
} else {
this.setState({ selectedItem: null})
}
}
componentWillReceiveProps(nextProps) {
// If no filteredItems in sate, get the whole list:
if (!nextProps.value) {
this.setState({ isValid: false })
}
}
handleStateChange(changes) {
// If searching
if (changes.hasOwnProperty('inputValue')) {
const filteredItems = this.originalItems.filter(
item => item.text.toLowerCase().includes(changes.inputValue.toLowerCase())
);
this.setState({ filteredItems })
}
// If something is selected
if (changes.hasOwnProperty('selectedItem')) {
!!changes.selectedItem ? this.setState({isValid: true}) : this.setState({isValid: false})
// If we get undefined, change to '' as a fallback to default state
changes.selectedItem = changes.selectedItem ? changes.selectedItem : '';
this.props.onStateChange(changes);
}
}
errorText() {
const { isValid } = this.state;
if (isValid) {
return null;
}
return (
<div style={{ color: 'red' }}>
{this.getErrorMessage()}
</div>
);
}
render() {
return (
<div>
<MuiDownshift
{...this.props}
items={this.state.filteredItems}
onStateChange={this.handleStateChange}
ref={(r) => { this.input = r; }}
defaultSelectedItem={this.state.selectedItem}
/>
{this.errorText()}
</div>
);
}
}
AutocompleteValidator.childContextTypes = {
form: PropTypes.object
};
export default AutocompleteValidator;
A component where it's used:
render() {
return (
<ValidatorForm
ref='form'
onSubmit={() => {
this.context.router.history.push(this.props.config.urls['step5']);
}}
onError={errors => console.log(errors)}
>
<Row>
<Col md={12}>
<AutocompleteValidator
validators={['required']}
errorMessages={['Cette information doit ĂȘtre renseignĂ©e']}
isRequired={true}
name='bankId'
items={this.props.config.list.bank}
onStateChange={(changes) => {
this.props.loansUpdate('bankId', changes.selectedItem.value);
}}
value={!!this.props.loans.bankId ? this.props.loans.bankId : false}
/>
</Col>
</Row>
<Row>
<Col md={12} style={{ marginTop: '15px' }}>
<Checkbox
label={<Translate value='step4.insuranceProvidedByBank' />}
labelStyle={{ 'top': '0px' }}
name='insuranceProvidedByBank'
value={this.props.loans.insuranceProvidedByBank}
checked={this.props.loans.insuranceProvidedByBank}
onCheck={(event, value) => {
this.props.loansUpdate('insuranceProvidedByBank', value);
}}
/>
</Col>
</Row>
<Row>
<Col sm={6} md={5}>
<Button
fullWidth
style={{ marginTop: '50px', marginBottom: '20px' }}
type='submit'
icon={<i className='fa fa-angle-right' style={{ marginLeft: '10px', display: 'inline-block' }} />}
><Translate value='iContinue' /></Button>
</Col>
</Row>
</ValidatorForm >
)
};
};
you get this problem because you override default componentWillMount and componentWillReceiveProps methods from ValidatorComponent. So, to solve this case you should do something like this
componentWillMount() {
// should call parent method before
super.componentWillMount();
// your code
}