reactjs - app doesn't change state after getting data from api - javascript

I'm building a React news app that gets its data from News API. On the home page I have a search bar where user enters key words to retreive from the API. When I enter the key word and press enter, the state changes and the results are visible on the page but then immediately it refreshes and displays the default page.
App.js:
class App extends Component {
constructor(props) {
super(props);
this.state = { articles: [], keyword: ''};
this.fetchNewsWithKeywords = this.fetchNewsWithKeywords.bind(this);
}
fetchNewsWithKeywords(keyword){
searchForKeywords(keyword)
.then(articles => this.setState({ articles: articles, keyword: keyword}))
}
render() {
return (
<Router >
<div className="App">
<div className="container" >
<Header/>
<Route exact path="/" render={props => (
<React.Fragment>
<SearchNews fetchNewsWithKeywords = {this.fetchNewsWithKeywords.bind(this)}/>
<NewsList articles = {this.state.articles}/>
</React.Fragment>
)} />
<Route path="/top-headlines" component={TopHeadlines} />
<Route path="/newest" component={Newest} />
</div>
</div>
</Router>
);
}
}
export default App;
SearchNews.js
class SearchNews extends Component {
state = {
value: ""
}
onSubmit = (e) => {
var str = this.state.value;
this.props.fetchNewsWithKeywords(str)
}
handleOnChange = event => {
this.setState({
value: event.target.value
})
};
render() {
const { classes } = this.props;
return (
<form className={classes.container} noValidate autoComplete="off" onSubmit={this.onSubmit}>
<TextField
id="outlined-search"
label="Search"
type="search"
className={classes.textField}
margin="normal"
variant="outlined"
onChange={this.handleOnChange}
/>
</form>
)
}
}
function for retrieving the data from API
export async function searchForKeywords(keyword){
var query = keyword
var url = "https://newsapi.org/v2/everything?q="+
encodeURIComponent(query) +
"&apiKey="+API_KEY;
let result = await fetch(url).then(response => response.json());
return result.articles.slice(0,20);
NewsList.js
export class NewsList extends Component {
render() {
return this.props.articles.map((article) => (
<div className="gridContainer">
<div className="gridItem" >
<Article article = {article}/>
</div>
</div>
));
}
}
export default NewsList
Article.js
class Article extends Component {
render() {
const {
title,
description,
publishedAt,
source,
urlToImage,
url
} = this.props.article;
const { classes } = this.props;
let date = new Date(publishedAt).toLocaleString();
return (
<Card className={classes.card} >
<CardActionArea href={url} target="_blank">
<CardMedia
className={classes.media}
image={urlToImage}
title={title}
/>
<CardContent >
<Typography gutterBottom variant="h5" component="h2">
{title}
</Typography>
<Typography component="p">
{description}
</Typography>
<Typography variant="caption">
{source.name}
</Typography>
<Typography variant="caption">
{date}
</Typography>
</CardContent>
</CardActionArea>
</Card>
);
}
}
export default withStyles(styles)(Article);

I think your problem is here:
let result = await fetch(url).then(response => response.json());
return result.articles.slice(0,20);
You are awaiting the fetch and then .then to get the response as json, however, you are returning the results.articles.slice prior to that response.json() resolving?
Try:
let result = await fetch(url)
result = await result.json()
return result.articles.slice(0,20);

Related

React Class Component is not changing with the change of its props [duplicate]

react-router-dom v5 and React 16
My loading app component contains:
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<BrowserRouter>
<StartApp />
</BrowserRouter>,
</FirebaseContext.Provider>,
document.getElementById("root")
);
I have a route component which contains:
{
path: "/member/:memberId",
component: MemberForm,
layout: "/admin"
},
Admin component:
return (
<>
<div className="main-content" ref="mainContent">
<LoadingComponent loading={this.props.authState.loading}>
<AdminNavbar
{...this.props}
brandText={this.getBrandText(this.props.location.pathname)}
/>
<AuthDetailsProvider>
<Switch>{this.getRoutes(routes)}</Switch>
</AuthDetailsProvider>
<Container fluid>
<AdminFooter />
</Container>
</LoadingComponent>
</div>
</>
)
this.getRoutes in the Switch contains the reference route above.
Now from one of my component pages I can navigate to /member/{memberid} this works fine.
the route loads a component called MemberForm
inside MemberForm I have a row that contains this method:
<Row>
{ this.displayHouseholdMembers() }
</Row>
displayHouseholdMembers = () => {
const householdDetails = this.state.family;
if (householdDetails) {
return householdDetails.map((key, ind) => {
if (key['uid'] != this.state.memberKeyID) {
return (
<Row key={ind} style={{ paddingLeft: '25px', width: '50%'}}>
<Col xs="5">
<Link to={ key['uid'] }>
{ key['first'] + " " + key['last'] }
</Link>
</Col>
<Col xs="4">
{ key['relation'] }
</Col>
<Col xs="3">
<Button
color="primary"
size="sm"
onClick={(e) => this.removeHouseRelation(key)}
>
Remove
</Button>
</Col>
</Row>
);
}
});
}
};
MemberForm:
in componentDidMount I do an firebase call to check for the data pertaining to the user using the uid aka memberId in the URL.
class MemberForm extends React.Component {
constructor(props) {
super(props);
this.state = {
...INITIAL_STATE,
currentOrganization: this.props.orgID,
householdRelation: ['Spouse', 'Child', 'Parent', 'Sibling'],
householdSelected: false,
};
}
componentDidMount() {
let urlPath, personId;
urlPath = "members";
personId = this.props.match.params.memberId;
// if it is a member set to active
this.setState({ statusSelected: "Active" })
this.setState({ memberSaved: true, indiUid: personId });
// this sets visitor date for db
const setVisitorDate = this.readableHumanDate(new Date());
this.setState({ formType: urlPath, visitorDate: setVisitorDate }, () => {
if (personId) {
this.setState({ memberSaved: true, indiUid: personId });
this.getIndividualMemberInDB(
this.state.currentOrganization,
personId,
this.state.formType,
INITIAL_STATE
);
}
});
}
...
return (
<>
<UserHeader first={s.first} last={s.last} />
{/* Page content */}
<Container className="mt--7" fluid>
<Row>
...
<Row>
{ this.displayHouseholdMembers() }
</Row>
</Form>
</CardBody>
) : null}
</Card>
</Col>
</Row>
<Row>
<Col lg="12" style={{ padding: "20px" }}>
<Button
color="primary"
onClick={e => this.submitMember(e)}
size="md"
>
Save Profile
</Button>
{ this.state.indiUid ? (
<Button
color="secondary"
onClick={e => this.disableProfile()}
size="md"
>
Disable Profile
</Button>
) : null }
</Col>
</Row>
</Container>
</>
);
When I click on the Link it shows the url has changed 'members/{new UID appears here}' but the page does not reload. I believe what's going on is that since it's using the same route in essence: path: "/member/:memberId"it doesn't reload the page. How can I get it to go to the same route but with the different memberId?
You are correct that the MemberForm component remains mounted by the router/route when only the path param is updating. Because of this the MailForm component needs to handle prop values changing and re-run any logic depending on the prop value. The componentDidUpdate is the lifecycle method to be used for this.
Abstract the logic into a utility function that can be called from both componentDidMount and componentDidUpdate.
Example:
getData = () => {
const urlPath = "members";
const { memberId } = this.props.match.params;
// this sets visitor date for db
const setVisitorDate = this.readableHumanDate(new Date());
this.setState(
{
// if it is a member set to active
statusSelected: "Active",
memberSaved: true,
indiUid: memberId,
formType: urlPath,
visitorDate: setVisitorDate
},
() => {
if (memberId) {
this.setState({ memberSaved: true, indiUid: memberId });
this.getIndividualMemberInDB(
this.state.currentOrganization,
memberId,
this.state.formType,
INITIAL_STATE
);
}
}
);
}
The lifecycle methods:
componentDidMount() {
this.getData();
}
componentDidUpdate(prevProps) {
if (prevProps.match.params.memberId !== this.props.match.params.memberId) {
this.getData();
}
}
For react-router-dom v6, can you try with simple routing? Create a Test.js with
const Test = ()=> <h1>Test Page</h1>
Then, create a Home.js with
const Home = ()=> <Link to="/test">Test</Link>
Then, add them to route.
<BrowserRouter>
<Routes>
<Route path="/" element={<Home/>} />
<Route path="/test" element={<Test />} />
</Routes>
</BrowserRouter>
Does your component structure look like this? For index route, look more at https://reactrouter.com/docs/en/v6/getting-started/overview.

Context API not displaying array of data

I'm receiving the error 'expected an assignment of function call and instead saw an expression.
*The above error is resolved, it's now giving me an Unhandled Rejection (TypeError): render is not a function. It functions properly until I go to the products page and then gives me the error.
I console logged it and it was pulling the information but then it breaks when I import the ProductBrowse component to display the information.
ProductPage:
class ProductPage extends React.Component {
state = {
shoppingCartOpen: false,
};
drawerToggleClickHandler = () => {
this.setState((prevState) => {
return { shoppingCartOpen: !prevState.shoppingCartOpen };
});
};
render() {
let shoppingCartDrawer;
if (this.state.shoppingCartOpen) {
shoppingCartDrawer = <ShoppingCartDrawer />;
}
return (
<ProductStyling>
<ButtonAppBar drawerClickHandler={this.drawerToggleClickHandler} />
<H1>Products</H1>
{shoppingCartDrawer}
<ProductConsumer>
<Grid container spacing={3}>
{(value) => {
return value.products.map((prod, idx) => {
return (
<Grid item xs={3} key={`${prod.name}-${prod.store}-${idx}`}>
<ProductBrowse
productName={prod.name}
productDesc={prod.desc}
productImg={prod.img}
productPrice={prod.price}
/>
</Grid>
);
});
}}
</Grid>
</ProductConsumer>
;
</ProductStyling>
);
}
}
Structure of value:
const ProductContext = React.createContext();
class ProductProvider extends React.Component {
state = {
products: productData,
};
addToCart = () => {
console.log('Hello from add to cart'); };
render() {
return (
<ProductContext.Provider
value={{ ...this.state, addToCart: this.addToCart }}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
ProductBrowse:
const ProductBrowse = ({
productName,
productDesc,
productImg,
productPrice,
}) => {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const openModal = () => {
setOpen(!open);
};
const closeModal = () => {
setOpen(!open);
};
return (
<Box border={1} borderRadius={3}>
<Card className={classes.root}>
<CardActionArea onClick={() => openModal()}>
<ProductModal
open={open}
onClick={() => openModal()}
onClose={() => closeModal()}
onSave={() => closeModal()}
productName={productName}
productDesc={productDesc}
/>
<CardHeader
title={productName}
subheader={formatCurrency(productPrice)}
/>
<CardMedia
className={classes.media}
image={productImg}
alt={productDesc}
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='p'>
{productDesc}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size='small' /*To Checkout*/>BUY NOW</Button>
<Button size='small'>ADD TO CART</Button>
<Button size='small'>REVIEW</Button>
</CardActions>
</Card>
</Box>
);
};
ProductData:
import desk from '../../../assets/img/desk.webp';
export const productData = [
{
img: desk,
name: 'Desk',
store: 'Local Furniture Shop 1',
price: 9.99,
desc: "This sturdy desk is built to outlast years of coffee and hard work. You get a generous work surface and a clever solution to keep cords in place underneath."
},
Index.js:
ReactDOM.render(
<React.StrictMode>
<Auth0Provider
domain={auth0Domain}
client_id={auth0ClientID}
redirect_uri={window.location.origin}
onRedirectCallback={onRedirectCallback}
audience={auth0Audience}
scope={"read:current_user"}
>
<ProductProvider>
<Provider store={store}>
<App />
</Provider>
</ProductProvider>
</Auth0Provider>
</React.StrictMode>,
document.getElementById('root')
);
You are not returning anything from ProductConsumer You need to do like this:
<ProductConsumer>
<Grid container spacing={3}>
{(value) => {
return value.products.map((prod, idx) => {
return (
<Grid item xs={3} key={`${prod.name}-${prod.store}-${idx}`}>
<ProductBrowse
productName={prod.name}
productDesc={prod.desc}
productImg={prod.img}
productPrice={prod.price}
/>
</Grid>
);
});
}}
</Grid>
</ProductConsumer>;

Pokedex project api react js

I'm trying to learn, react us and try to load the API on my pokedex app. https://pokeapi.co/api/v2/pokedex/1/ I'm trying to load every pokemon on the ( pokemon_entries ) list, but I don't know how to do
I have already created the card of the different Pokemon and I had tried to load the List on my app
ListPokemon
import React from 'react';
import Loader from '../components/Loader';
class ListPokemon extends React.Component {
state = {
isLoading: false,
data: [ ]
};
async componentDidMount() {
this.setState({isLoading:true})
const {name, url} = this.props;
try {
const response = await fetch(`https://pokeapi.co/api/v2/pokedex/1/`);
const json = await response.json();
this.setState({data: json,isLoading:false})
console.log({json})
} catch (err){
console.log(err.msg);
this.setState({isLoading:false})
throw err
}
}
render() {
const {isLoading,data} = this.state;
return (
<>
<h1>Lorem</h1>
{
isLoading ?<Loader/> : <h1>{data.entry_number}</h1>
}
</>
);
}
}
export default ListPokemon
DataPokemon :
import React from 'react';
import { Card,Container,Row,Col } from 'react-bootstrap';
const DataPokemon = props => {const { name } = props;
return(
<Container>
<Row>
<Col xs={6}>
<Card style={{ width: '18rem' }}>
<Card.Img variant="top" src="holder.js/100px180" />
<Card.Body>
<Card.Title>{name}</Card.Title>
<Card.Text>
</Card.Text>
{/* <Button variant="primary">Go somewhere</Button> */}
</Card.Body>
</Card>
</Col>
</Row>
</Container>
)
}
export default DataPokemon;
Thank you !
You can change the x and get more or less pokemons.
const pokeArray = [];
for(let i=1; i<x; i++) {
axios.get(`https://pokeapi.co/api/v2/pokemon/${i}`).then(res => {
pokeArray.push( {
id: i,
name: res.data.name,
photo: res.data['sprites']['front_default'],
hp: res.data['stats'][5]['base_stat'],
attack: res.data['stats'][4]['base_stat'],
defense : res.data['stats'][3]['base_stat'],
} )
})
}

How to call child component method from parent

In my Reactjs app, I need to have a parent component (a wizard) named Wizard.js and a number of child components (steps of the wizard) named PrimaryForm.js, SecondaryForm.js etc. They all are Class based components with some local validation functions.
Previous and Next buttons to advance the steps, reside in the Wizard.js.
To advance the next step of the wizard, I'm trying to call a method from PrimaryForm. I checked similar questions in Stackoverflow; tried using ref or forwardRef, but I could not make it work. I currently receive "TypeError: Cannot read property 'handleCheckServer' of null" error.
Below are my parent and child classes. Any help about what I would be doing wrong is appreciated.
Wizard.js:
import React, { Component } from 'react';
...
const getSteps = () => {
return [
'Info',
'Source Details',
'Target Details',
'Configuration'
];
}
class Wizard extends Component {
constructor(props) {
super(props);
this.firstRef = React.createRef();
this.handleNext = this.handleNext.bind(this);
this.state = {
activeStep: 1,
}
}
componentDidMount() {}
handleNext = () => {
if (this.state.activeStep === 1) {
this.firstRef.current.handleCheckServer(); <<<<<<<<<<<<<<<<< This is where I try to call child method
}
this.setState(state => ({
activeStep: state.activeStep + 1,
}));
};
handleBack = () => {
this.setState(state => ({
activeStep: state.activeStep - 1,
}));
};
handleReset = () => {
this.setState({
activeStep: 0,
});
};
render() {
const steps = getSteps();
const currentPath = this.props.location.pathname;
const { classes } = this.props;
return (
<React.Fragment>
<CssBaseline />
<Topbar currentPath={currentPath} />
<div className={classes.root}>
<Grid container spacing={2} justify="center" direction="row">
<Grid container spacing={2} className={classes.grid} justify="center" direction="row">
<Grid item xs={12}>
<div className={classes.topBar}>
<div className={classes.block}>
<Typography variant="h6" gutterBottom>Wizard</Typography>
<Typography variant="body1">Follow the wizard steps to create a configuration.</Typography>
</div>
</div>
</Grid>
</Grid>
<Grid container spacing={2} alignItems="center" justify="center" className={classes.grid}>
<Grid item xs={12}>
<div className={classes.stepContainer}>
<div className={classes.bigContainer}>
<Stepper classes={{ root: classes.stepper }} activeStep={this.state.activeStep} alternativeLabel>
{steps.map(label => {
return (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
);
})}
</Stepper>
</div>
<PrimaryForm ref={this.firstRef} />
</div>
</Grid>
</Grid>
<Grid container spacing={2} className={classes.grid}>
<Grid item xs={12}>
<div className={classes.flexBar}>
<Tooltip title="Back to previous step">
<div>
<Button variant="contained"
disabled={(this.state.activeStep === 0)}
className={classes.actionButton}
onClick={this.handleBack}
size='large'>
<BackIcon className={classes.rightIcon} />Back
</Button>
</div>
</Tooltip>
<Tooltip title="Proceed the next step">
<div>
<Button
variant="contained" className={classes.actionButton}
color="primary"
size='large'
disabled={!(!this.state.isFormValid || this.state.isTestWaiting)}
onClick={this.handleNext}>
<ForwardIcon className={this.props.classes.rightIcon}/>Next</Button>
</div>
</Tooltip>
<Tooltip title="Cancel creating new configuration">
<Button variant="contained" color="default" className={classes.actionButton}
component={Link} to={'/configs'} style={{ marginLeft: 'auto' }}>
<CancelIcon className={classes.rightIcon} />Cancel
</Button>
</Tooltip>
</div>
</Grid>
</Grid>
</Grid>
</div>
</React.Fragment>
)
}
}
export default withRouter(withStyles(styles)(Wizard));
PrimaryForm.js:
import React, { Component } from 'react';
...
class PrimaryForm extends Component {
constructor(props) {
super(props);
this.handleCheckServer = this.handleCheckServer.bind(this);
this.state = {
hostname: {
value: "localhost",
isError: false,
errorText: "",
},
serverIp: {
value: "127.0.0.1",
isError: false,
errorText: "",
},
isFormValid: true,
isTestValid: true,
testErrorMessage: "",
isTestWaiting: false,
};
}
componentDidMount() { }
handleCheckServer() {
alert('Alert from Child. Server check will be done here');
}
evaluateFormValid = (prevState) => {
return ((prevState.hostname.value !== "" && !prevState.hostname.isError) &&
(prevState.serverIp.value !== "" && !prevState.serverIp.isError));
};
handleChange = event => {
var valResult;
switch (event.target.id) {
case 'hostname':
valResult = PrimaryFormValidator.validateHostname(event.target.value, event.target.labels[0].textContent);
this.setState({
...this.state,
hostname:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
},
});
break;
case 'serverIp':
valResult = PrimaryFormValidator.validateIpAddress(event.target.value, event.target.labels[0].textContent);
this.setState({
...this.state,
serverIp:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
default:
}
this.setState(prevState => ({
...prevState,
isFormValid: this.evaluateFormValid(prevState),
}));
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<div className={classes.bigContainer}>
<Paper className={classes.paper}>
<div>
<div>
<Typography variant="subtitle1" gutterBottom className={classes.subtitle1} color='secondary'>
Primary System
</Typography>
<Typography variant="body1" gutterBottom>
Information related with the primary system.
</Typography>
</div>
<div className={classes.bigContainer}>
<form className={classes.formArea}>
<TextField className={classes.formControl}
id="hostname"
label="FQDN Hostname *"
onChange={this.handleChange}
value={this.state.hostname.value}
error={this.state.hostname.isError}
helperText={this.state.hostname.errorText}
variant="outlined" autoComplete="off" />
<TextField className={classes.formControl}
id="serverIp"
label="Server Ip Address *"
onChange={this.handleChange}
value={this.state.serverIp.value}
error={this.state.serverIp.isError}
helperText={this.state.serverIp.errorText}
variant="outlined" autoComplete="off" />
</form>
</div>
</div>
</Paper>
</div>
</React.Fragment>
)
}
}
export default withRouter(withStyles(styles)(PrimaryForm));
(ps: I would like to solve this without another framework like Redux, etc if possible)
Example in Typescript.
The idea is that the parent passes its callback to the child. The child calls the parent's callback supplying its own e.g. child callback as the argument. The parent stores what it got (child callback) in a class member variable and calls it later.
import * as React from 'react'
interface ICallback {
(num: number): string
}
type ChildProps = {
parent_callback: (f: ICallback) => void;
}
class Child extends React.Component {
constructor(props: ChildProps) {
super(props);
props.parent_callback(this.childCallback);
}
childCallback: ICallback = (num: number) => {
if (num == 5) return "hello";
return "bye";
}
render() {
return (
<>
<div>Child</div>
</>
)
}
}
class Parent extends React.Component {
readonly state = { msg: "<not yet set>" };
letChildRegisterItsCallback = (fun: ICallback) => {
this.m_ChildCallback = fun;
}
callChildCallback() {
const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
console.log("Child callback returned string: " + str);
return str;
}
componentDidMount() {
this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
}
render() {
return (
<>
<Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
<div>{this.state.msg}</div>
</>
)
}
m_ChildCallback: ICallback | undefined = undefined;
}
P.S.
The same code in Javascript. The only difference is that interface, type, readonly and type annotations are taken out. Pasting into here confirms it's a valid ES2015 stage-2 code.
class Child extends React.Component {
constructor(props) {
super(props);
props.parent_callback(this.childCallback);
}
childCallback = (num) => {
if (num == 5) return "hello";
return "bye";
}
render() {
return (
<>
<div>Child</div>
</>
)
}
}
class Parent extends React.Component {
state = { msg: "<not yet set>" };
letChildRegisterItsCallback = (fun) => {
this.m_ChildCallback = fun;
}
callChildCallback() {
const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
console.log("Child callback returned string: " + str);
return str;
}
componentDidMount() {
this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
}
render() {
return (
<>
<Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
<div>{this.state.msg}</div>
</>
)
}
m_ChildCallback = undefined;
}

React.js history.push in a separate function?

Can you help me with React.js history.push function?
I have a icon which can be pressed. The onClick calls handleRemoveFavourite function which filters out the current item from localStrorage and sets the updated string to storage. This works fine.
After the storage update is done the program should reroute the user to the root page /favourites. The reroute works well in the bottom example. But how to do this in the handleRemoveFavourites function?
This is the code I would like to have
handleRemoveFavourite = () => {
const { name } = this.props.workout;
let savedStorage = localStorage.saved.split(",");
let cleanedStorage = savedStorage.filter(function(e) {
return e !== name;
});
localStorage.setItem("saved", cleanedStorage.toString());
history.push("/favourites")
};
renderHeartIcon = () => {
return (
<Route
render={({ history }) => (
<Rating
icon="heart"
defaultRating={1}
maxRating={1}
onClick={this.handleRemoveFavourite}
/>
)}
/>
);
};
The rerouting works fine with just this:
renderHeartIcon = () => {
return (
<Route
render={({ history }) => (
<Rating
key={1}
icon="heart"
defaultRating={1}
maxRating={1}
size="large"
onClick={() => history.push("/favourites")}
/>
)}
/>
);
};
The whole component looks like this:
import React from "react";
import {
Container,
Grid,
Icon,
Label,
Table,
Header,
Rating,
Segment
} from "semantic-ui-react";
import { Link, Route } from "react-router-dom";
export default class WorkoutComponent extends React.PureComponent {
renderChallengeRow = workouts => {
let { reps } = this.props.workout;
reps = reps.split(",");
return workouts.map((item, i) => {
return (
<Table.Row key={item.id}>
<Table.Cell width={9}>{item.name}</Table.Cell>
<Table.Cell width={7}>{reps[i]}</Table.Cell>
</Table.Row>
);
});
};
handleRemoveFavourite = () => {
const { name } = this.props.workout;
let savedStorage = localStorage.saved.split(",");
let cleanedStorage = savedStorage.filter(function(e) {
return e !== name;
});
localStorage.setItem("saved", cleanedStorage.toString());
// history.push("/favourites");
};
renderHeartIcon = () => {
return (
<Route
render={({ history }) => (
<Rating
key={1}
icon="heart"
defaultRating={1}
maxRating={1}
size="large"
onClick={this.handleRemoveFavourite}
/>
)}
/>
);
};
render() {
const { name, workouts } = this.props.workout;
const url = `/savedworkout/${name}`;
return (
<Grid.Column>
<Segment color="teal">
<Link to={url}>
<Header as="h2" to={url} content="The workout" textAlign="center" />
</Link>
<Table color="teal" inverted unstackable compact columns={2}>
<Table.Body>{this.renderChallengeRow(workouts)}</Table.Body>
</Table>
<br />
<Container textAlign="center">
<Label attached="bottom">{this.renderHeartIcon()}</Label>
</Container>
</Segment>
<Link to="/generate">
<Icon name="angle double left" circular inverted size="large" />
</Link>
</Grid.Column>
);
}
}
Since you are using react-router you can use withRouter to achive this.
import { withRouter } from 'react-router-dom'
Wrap the with class name with withRouter.
Like this:
instead of doing like this:
export default class App .....
Separate this like:
class App ...
At the end of the line:
export default withRouter(App)
Now you can use like this:
handleRemoveFavourite = () => {
const { name } = this.props.workout;
let savedStorage = localStorage.saved.split(",");
let cleanedStorage = savedStorage.filter(function(e) {
return e !== name;
});
localStorage.setItem("saved", cleanedStorage.toString());
this.props.history.push("/favourites");
};
You can remove Route from renderHearIcon():
renderHeartIcon = () => {
return (
<Rating
key={1}
icon="heart"
defaultRating={1}
maxRating={1}
size="large"
onClick={this.handleRemoveFavourite}
/>
);
};
onClick={this.handleRemoveFavourite}
=>
onClick={()=>this.handleRemoveFavourite(history)}
handleRemoveFavourite = () => {
=>
handleRemoveFavourite = (history) => {
The problem you are facing is, you are providing <Route> with the history prop, so that it is propagated on the component function call. But you are not propagating it to the handleRemoveFavourite function.
You'll need to wrap this. handleRemoveFavourite in an anonymous function call. Like
onClick={() => this.handleRemoveFavourite(history)}
and then accept it as a valid argument in your function
handleRemoveFavourite = (history) => {...}
that should solve it
Changing to this.props.history.push("/favourites"); should works.
EDITED
Is your component inside a Route? If so, this.props.history will works. I tested changing your class to run on my project.
import React from 'react';
import ReactDOM from 'react-dom';
import {
Container,
Grid,
Icon,
Label,
Table,
Header,
Rating,
Segment
} from "semantic-ui-react";
import { Link, Route, BrowserRouter as Router } from "react-router-dom";
export default class WorkoutComponent extends React.Component {
static defaultProps = {
workout: {
reps: "1,2,3,4",
workouts: []
},
}
renderChallengeRow = workouts => {
let { reps } = this.props.workout;
reps = reps.split(",");
return workouts.map((item, i) => {
return (
<Table.Row key={item.id}>
<Table.Cell width={9}>{item.name}</Table.Cell>
<Table.Cell width={7}>{reps[i]}</Table.Cell>
</Table.Row>
);
});
};
handleRemoveFavourite = () => {
const { name } = this.props.workout;
//let savedStorage = localStorage.saved.split(",");
// let cleanedStorage = savedStorage.filter(function(e) {
// return e !== name;
// });
// localStorage.setItem("saved", cleanedStorage.toString());
this.props.history.push("/favourites");
};
renderHeartIcon = () => {
return (
<Route
render={({ history }) => (
<Rating
key={1}
icon="heart"
defaultRating={1}
maxRating={1}
size="large"
onClick={this.handleRemoveFavourite}
/>
)}
/>
);
};
render() {
console.log(this.props)
const { name, workouts } = this.props.workout;
const url = `/savedworkout/${name}`;
return (
<Grid.Column>
<Segment color="teal">
<Link to={url}>
<Header as="h2" to={url} content="The workout" textAlign="center" />
</Link>
<Table color="teal" inverted unstackable compact columns={2}>
<Table.Body>{this.renderChallengeRow(workouts)}</Table.Body>
</Table>
<br />
<Container textAlign="center">
<Label attached="bottom">{this.renderHeartIcon()}</Label>
</Container>
</Segment>
<Link to="/generate">
<Icon name="angle double left" circular inverted size="large" />
</Link>
</Grid.Column>
);
}
}
ReactDOM.render(
<Router>
<Route path="/" component={ WorkoutComponent }/>
</Router>, document.getElementById('root'));

Categories