I'm setting user_id from my Context.Provider and album_id from my data from my database. If these two variables are set and equal each other I'm adding two buttons (Edit and Delete) and setting a loading spinner if the values aren't set yet.
Here I'm grabbing the data from the database and setting it to author_id as well as grabbing the Context.Provider from another component to set to user_id.
this.state = {
loading: false,
};
componentDidMount() {
const { match: { params} } = this.props;
console.log('COMPONENT HAS MOUNTED');
this.setState({ loading : true });
fetch(`http://localhost:8000/albums/${params.albumId}`)
.then((response) =>
response.json())
.then((data) => {
this.setState({ albums : data });
}).then(()=> {
this.setState({ loading : false });
}).catch((error) => {
console.log("Error " + error)
})
}
render() {
const user_id = this.context.user ? this.context.user.sub : null;
const author_id = this.state.albums[0] ? this.state.albums[0].author : null;
const shouldRenderButton = user_id && author_id && user_id === author_id;
Album.contextType = Auth0Context;
If both these variables are both set and equal each other I'm rendering two buttons (Edit and Delete):
<div>
{shouldRenderButton ?
<Container>
<Row xs="2">
<Col><Button color="primary" size="sm">Edit</Button></Col>
<Col><Button color="danger" size="sm">Delete</Button></Col>
</Row>
</Container> : <Spinner size="sm" color="secondary" />
}
</div>
While I'm waiting for the data to get added I have a spinner. What I want to do is if the album_id doesn't equal the user_id I would like to display nothing and stop the spinner. As you can see I have loading set to false at first, set to true before the data is fetched and false once the data is fetched but I'm not sure how to use that in my code.
Ok based on your code you should do something like this in your render:
if (loading) return <Spinner />
return (
shouldRenderButton ? <YourJsxFragment /> : null
)
IMO using nested ternaries makes code unreadable so I like to use if statements to return things instead of wrapping everything inside just one return statement.
You can return null if you don't want to show anything.
You should be able to use .finally() as cited in this here. Basically, you are saying, whether 200 or error (4XX and so on) you need to stop the spinner.
(NEWLY UPDATED)
const shouldRenderButton = user_id && author_id && user_id === author_id;
can be converted to
const shouldRenderButton = !this.state.loading && (user_id && author_id && user_id === author_id);
the .then()s can be changed to
.then((data) => {
this.setState({
albums : data,
loading : false
});
})
.catch((error) => {
this.setState({ loading : false });
console.log("Error " + error)
})
(/NEWLY UPDATED)
let me know if that works.
If I understand correctly, you can use loading along with shouldRenderButton, something like:
<div>
{
loading ?
<Spinner size="sm" color="secondary" />
:
shouldRenderButton ?
<Container>
<Row xs="2">
<Col><Button color="primary" size="sm">Edit</Button></Col>
<Col><Button color="danger" size="sm">Delete</Button></Col>
</Row>
</Container>
:
<div></div>
}
</div>
Update
After a rewrite, this should work with the code above:
componentDidMount() {
const { match: { params} } = this.props;
console.log('COMPONENT HAS MOUNTED');
this.setState({ loading: true });
fetch(`http://localhost:8000/albums/${params.albumId}`)
.then((response) => response.json())
.then((data) => this.setState({ albums: data, loading: false }) )
.catch((error) => {
this.setState({ loading : false });
console.log("Error " + error);
});
}
render() {
let { loading } = this.state
const user_id = this.context.user ? this.context.user.sub : null;
const author_id = this.state.albums[0] ? this.state.albums[0].author : null;
const shouldRenderButton = user_id && author_id && user_id === author_id;
This will make sure loading sets to false after albums gets updated.
componentDidMount() {
const { match: { params} } = this.props;
this.setState({ loading: true });
fetch(`http://localhost:8000/albums/${params.albumId}`)
.then((response) => response.json())
.then((data) => this.setState({
albums: data
}, () => {
this.setState({ loading: false }); // here
}))
.catch((error) => {
this.setState({ loading : false });
});
}
Now, Based on your requirements, hopefully, this will get your job done and display nothing & stop the spinner.
render() {
const { loading } = this.state;
const user_id = this.context.user ? this.context.user.sub : null;
const author_id = this.state.albums[0] ? this.state.albums[0].author : null;
const shouldRenderButton = user_id && author_id && user_id === author_id;
return (
<>
{loading && <Spinner size="sm" color="secondary" />}
{shouldRenderButton && !loading && (
<Container>
<Row xs="2">
<Col><Button color="primary" size="sm">Edit</Button></Col>
<Col><Button color="danger" size="sm">Delete</Button></Col>
</Row>
</Container>
)}
</>
)
}
}
Related
I have an idea that this may be because I am doing some styling things to change my radio button, but I am not sure. I am setting an onClick event that is calling my function twice. I have removed it to make sure it wasn't being triggered somewhere else and the onClick seems to be the culprit.
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
my function is just for now a simple console log of the shipping option:
changeShipping(shipOption){
console.log('clicked') // happening twice
}
If there isn't any reason you see here why this would happen I can post the rest of the code, but there is a lot and I don't think it would pertain to this, but I think this is a good starting place.
Full code:
import React, { Component } from 'react'
import fetch from 'isomorphic-fetch'
import { Subscribe } from 'statable'
import { FoldingCube } from 'better-react-spinkit'
import styles from './styles'
import { cost, cartState, userInfo, itemState, Api } from '../../state'
import { removeCookies, resetCart } from '../../../injectState'
export default class ShippingOptions extends Component {
constructor(props) {
super(props)
this.state = {
shippingOption: {}
}
this.changeShipping = this.changeShipping.bind(this)
}
async changeShipping(shipOption) {
const shipKey = Object.keys(shipOption)[0]
// if (userInfo.state.preOrderInfo.setShip[shipKey] === shipOption[shipKey]) {
// return
// }
let updatedShipOption = {}
Object.keys(shipOption).forEach(k => {
updatedShipOption = userInfo.state.preOrderInfo.setShip
? { ...userInfo.state.preOrderInfo.setShip, [k]: shipOption[k] }
: shipOption
})
userInfo.setState({
preOrderInfo: {
...userInfo.state.preOrderInfo,
setShip: updatedShipOption
}
})
// Make request to change shipping option
const { preOrderInfo } = userInfo.state
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(preOrderInfo),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
}
async componentDidMount() {
if (cartState.state.tab === 2) {
const { shipping } = userInfo.state
const { items, coupon } = itemState.state
let updated = { ...shipping }
const names = updated.shippingFullName.split(' ')
updated.shippingFirst = names[0]
updated.shippingLast = names[1]
delete updated.shippingFullName
updated.site = cartState.state.site
updated.products = items
updated.couponCode = coupon
updated.addressSame = userInfo.state.addressSame
cartState.setState({
loading: true
})
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(updated),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
return
shippingRes.products.forEach(product => {
const regexp = new RegExp(product.id, 'gi')
const updatedItem = items.find(({ id }) => regexp.test(id))
if (!updatedItem) {
console.warn('Item not found and being removed from the array')
const index = itemState.state.items.indexOf(updatedItem)
const updated = [...itemState.state.items]
updated.splice(index, 1)
itemState.setState({
items: updated
})
return
}
updatedItem.price = product.price
itemState.setState({
items: itemState.state.items.map(
item => (item.id === product.id ? updatedItem : item)
)
})
})
updated.shippingOptions = shippingRes.shippingOptions
Object.keys(updated.shippingOptions).forEach(k => {
this.setState({
shippingOption: { ...this.state.shippingOption, [k]: 0 }
})
updated.setShip = updated.setShip
? { ...updated.setShip, [k]: 0 }
: { [k]: 0 }
})
updated.success = shippingRes.success
updated.cartId = shippingRes.cartId
updated.locations = shippingRes.locations
userInfo.setState({
preOrderInfo: updated
})
cost.setState({
tax: shippingRes.tax,
shipping: shippingRes.shipping,
shippingOptions:
Object.keys(updated.shippingOptions).length > 0
? updated.shippingOptions
: null
})
cartState.setState({
loading: false,
apiErrors: shippingRes.errors.length > 0 ? shippingRes.errors : null
})
if (shippingRes.errors.length > 0) {
removeCookies()
shippingRes.errors.forEach(err => {
if (err.includes('CRT-1-00013')) {
itemState.setState({ coupon: '' })
}
})
}
}
}
render() {
return (
<Subscribe to={[cartState, cost, itemState]}>
{(cart, cost, itemState) => {
if (cart.loading) {
return (
<div className="Loading">
<div className="Loader">
<FoldingCube size={50} color="rgb(0, 207, 255)" />
</div>
</div>
)
}
if (cart.apiErrors) {
return (
<div className="ShippingErrors">
<div className="ErrorsTitle">
Please Contact Customer Support
</div>
<div className="ErrorsContact">
(contact information for customer support)
</div>
<div className="Msgs">
{cart.apiErrors.map((error, i) => {
return (
<div key={i} className="Err">
{error}
</div>
)
})}
</div>
<style jsx>{styles}</style>
</div>
)
}
return (
<div className="ShippingOptionsContainer">
<div className="ShippingOptions">
{cost.shippingOptions ? (
<div className="ShipOptionLine">
{Object.keys(cost.shippingOptions).map((k, i) => {
const shipOptions = cost.shippingOptions[k]
const updatedProducts =
shipOptions.products.length === 0
? []
: shipOptions.products.map(product =>
itemState.items.find(
item => item.id === product.id
)
)
return (
<div className="ShippingInputs" key={i}>
{shipOptions.options.map((shipOption, i) => {
return (
<div className="ShippingSection" key={i}>
<div className="SectionTitle">
4. {shipOption.name} Shipping Options
</div>
{updatedProducts.length > 0 ? (
<div className="ShippingProducts">
{updatedProducts.map((product, i) => (
<div key={i}>
for{' '}
{shipOption.name === 'Freight'
? 'Large'
: 'Small'}{' '}
{product.name} from{' '}
{k.charAt(0).toUpperCase() + k.slice(1)}
</div>
))}
</div>
) : null}
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
</div>
)
})}
</div>
)
})}
</div>
) : null}
</div>
<style jsx>{styles}</style>
</div>
)
}}
</Subscribe>
)
}
}
Its because your app component is a wrap in StrictMode.
<React.StrictMode>
<App />
</React.StrictMode>,
If you are using create-react-app then it is found in index.js
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and later restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
https://github.com/facebook/react/issues/12856#issuecomment-390206425
The problem is html related, rather than React related. By default clicking a label will also trigger the onClick event of the input element it is associated with. In your case the onClick event is attached to both the label and the input. So by clicking on the label, the event is fired twice: once for the label and once for the input associated with it.
Edit: Attaching the onClick listener to the input is a possible solution to the problem
Prevent calling twice by using e.preventDefault().
changeShipping(e){
e.preventDefault();
console.log('clicked');
}
e.stopPropagation() is also worth exploring. I was handling an onMouseDown event, and preventDefault was not preventing multiple calls.
https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
In my case, it needs both to avoid redundant calls
<React.StrictMode>
<App />
</React.StrictMode>
from Nisharg Shah
e.preventDefault();
from Amruth
and one more thing for functional component where useEffect() method is called twice for me is bcoz, there were only one useEffect used with holds all methods to bind in FC (functional component) and dependency list. so after the change it looks like as follows:
useEffect(() => {
console.log("AFTER CHANGE : ", data) // move to below method
handleSubmit.bind(this);
handleCancel.bind(this);
testChange.bind(this);
}, [
data // move to below method
]);
useEffect(() => {
console.log("AFTER CHANGE : ", data)
}, [data]);
we should have without dependency list for bindings
To view the changed data after onChange(), then we should have useEffect with dependencyList of the data we are looking for.
Hope this helps. Happy coding . . . .
Already implemented an infinite scroll feature to my recipes app using Edamam API, but I have this issue:
When I reach the bottom of the page and it starts to fetch the next page, the component does a total refresh, it does show all the items of each page after tho.
What I want is that when I reach the bottom, it starts fetching (showing my spinner at the bottom and stuff) and render the next page at the bottom without refreshing the whole page at all.
Here is my code:
const RecipesGrid = ({ query }) => {
const getRecipes = async (pageParam) => {
try {
const path = pageParam
? pageParam
: `https://api.edamam.com/api/recipes/v2?q=${query}&app_id=${process.env.REACT_APP_APP_ID}&app_key=${process.env.REACT_APP_API_KEY}&type=public`;
const response = await axios.get(path);
return response.data;
} catch (error) {
console.log(error);
}
};
useEffect(() => {
const onScroll = (event) => {
const { scrollHeight, scrollTop, clientHeight } =
event.target.scrollingElement;
if (scrollHeight - scrollTop <= clientHeight * 1.2) {
fetchNextPage();
}
};
document.addEventListener("scroll", onScroll);
return () => document.removeEventListener("scroll", onScroll);
// eslint-disable-next-line
}, []);
const {
data,
isFetching,
isFetchingNextPage,
status,
hasNextPage,
fetchNextPage,
} = useInfiniteQuery(
["recipes", query],
({ pageParam = "" }) => getRecipes(pageParam),
{
getNextPageParam: (lastPage) =>
lastPage._links.next ? lastPage._links.next.href : undefined,
}
);
if (isFetching || status === "loading") {
return <Spinner />;
}
if (status === "error") {
return <div>Whoops! something went wrong...Please, try again</div>;
}
return (
<div className={styles.Recipes}>
{data?.pages.map((item, index) => (
<React.Fragment key={index}>
{item.hits.map((recipe) => (
<Recipe recipe={recipe} key={recipe.recipe.uri} />
))}
{item.hits.length === 0 ? (
<Empty message="No results found!" />
) : null}
{!hasNextPage && item.hits.length !== 0 && (
<Empty message="No more recipes!" />
)}
</React.Fragment>
))}
{isFetchingNextPage && <Spinner />}
</div>
);
};
export default RecipesGrid;
I think you're unmounting the whole list because you only render a loading spinner here:
if (isFetching || status === "loading") {
return <Spinner />;
}
isFething is always true whenever a request is in-flight. This is also true for when you are fetching the next page, so you get into this early return and remove your list. Removing isFetching from the if statement should solve it, since you display a background loading indicator anyways via:
{isFetchingNextPage && <Spinner />}
Additionally, your error handling won't work because you transform errors into resolved promises by catching them for logging and not re-throwing them:
} catch (error) {
console.log(error);
}
Please use the onError callback for things like that.
I am having some issues in displaying a separate component based on the type of user.
Detailed Explanation of what I am trying to achieve:
The code snippet where I am having issues is as follows:
const addDataRequest = this.state.addDataRequestVisible ? (
<div className="main-page-section">
<Growl ref={this.growl}/>
{isAdmin(user)? (
<AdminDataRequestEnhancedForm handleSubmit={this.handleSubmit} addProject={this.addProjectOpen} onCancel={this.handleCancel}/>
) : (
<DataRequestEnhancedForm handleSubmit={this.handleSubmit} addProject={this.addProjectOpen} onCancel={this.handleCancel}/>
)
}
</div>
) : null;
Right now, DataRequestFormJS andAdminDataRequestForm are exact same forms as far as form content is concerned. So based on the user role, if it's an Admin, I want to display AdminDataRequestEnhancedForm component and if
it's not an Admin, I want to display DataRequestEnhancedForm component. But even though, I have specific checks for isAdmin in place, as shown in the code snippet above, it's not displaying AdminDataRequestEnhancedForm component
for an Admin user. I am wondering what's wrong I am doing? Does it have something to do with the state variable -this.state.addDataRequestVisible?
Here is the complete code:
import {DataRequestEnhancedForm} from "./DataRequestFormJS";
import {AdminDataRequestEnhancedForm} from "./AdminDataRequestForm";
import {isAdmin} from './auth';
class DataRequest extends React.Component<any, any> {
private growl = React.createRef<Growl>();
constructor(props) {
super(props);
this.state = {
result: '',
addDataRequestVisible: false,
addProjectVisible: false,
//For admin specific
addAdminDataRequestVisible: false,
addAdminProjectVisible: false
};
this.handleSubmit = this.handleSubmit.bind(this)
this.handleCancel = this.handleCancel.bind(this)
this.addProjectOpen = this.addProjectOpen.bind(this)
this.addProjectClose = this.addProjectClose.bind(this)
}
handleSubmit = (values) => {
let responseData = []
axios.post('upms/saveData', {
}).then((response) => {
console.log('response', response)
responseData = response.data
this.setState({
addDataRequestVisible: false,
dataRequestFormVisible: false,
dataRequestGridVisible: true,
selectedDataRequest: []
}, () => {
this.growl.current.show({
severity: 'success',
summary: 'Save Successful',
detail: 'Data Request has been created'
})
})
}).catch((err) => {
this.growl.current.show({
severity: 'error',
summary: 'Save Unsuccessful',
detail: 'Could not add Data Request'
})
})
}
handleCancel = event => {
console.log('In handleCancel....')
if (event) {
this.setState({ addDataRequestVisible: false})
}
}
addProjectOpen = event =>{
if (event) {
this.setState({ addProjectVisible: true})
}
}
addProjectClose = event =>{
console.log('In addProjectClose....' + event)
if (event) {
this.setState({ addProjectVisible: false})
}
}
render() {
const user = JSON.parse(sessionStorage.getItem('loggedInUser'))
let url = isAdmin(user) ? 'url1' : 'api/personnels/' + user.id + '/url2'
const defaultView = this.state.addDataRequestVisible?null: (
<div className="data-request-main-section">
<Growl ref={this.growl}/>
<div className="page-title-section">
<Button label="New Data Request" icon="pi pi-plus" className="new-data-req-btn"
onClick={(e) => this.setState({ addDataRequestVisible: true})}/>
</div>
<DataRequestsDataTable url={url} handleSubmit={this.handleSubmit}/>
</div>
)
const addDataRequest = this.state.addDataRequestVisible ? (
<div className="main-page-section">
<Growl ref={this.growl}/>
{isAdmin(user)? (
<AdminDataRequestEnhancedForm handleSubmit={this.handleSubmit} addProject={this.addProjectOpen} onCancel={this.handleCancel}/>
) : (
<DataRequestEnhancedForm handleSubmit={this.handleSubmit} addProject={this.addProjectOpen} onCancel={this.handleCancel}/>
)
}
</div>
) : null;
const addProjectDialog = this.state.addProjectVisible ? (
<Dialog visible={true} modal={true} className="add-project-popup"
onHide={() => this.setState({addProjectVisible: false})}>
<div style={{textAlign: 'center'}}>
<h3>Add New Project</h3>
<AddNewProjectForm closeProject={this.addProjectClose} />
</div>
</Dialog>
) : null
return (
<div className="datareq-page">
{defaultView}
{addDataRequest}
{addProjectDialog}
</div>
);
}
}
export default DataRequest
My auth.js looks like following:
export const isAdmin = (user) => {
return JSON.parse(sessionStorage.assignees).some(assignee => assignee.id === user.id)
}
It seems like your function isAdmin doesn't return the result directly and by the time it finishes executing DataRequestEnhancedForm will have been rendered instead. Try making that isAdmin function asynchronous like this:
export const isAdmin = async (user) => {
let userType = await JSON.parse(sessionStorage.assignees).some(assignee => assignee.id === user.id)
return userType
}
Please help!!
I created a web app using react and connected it with node js.
There I need to pass status of a dish to DishDetail Component whether it is in Favorite or not.
If it is not favorite. I have to mark it as favorite.
Whenever a person click on any dish to make it favorite, an entry is going to be added in favorite collection with user id and dish id.
But whenever a new user logs in and try to add dish as favorite as very first time.I'm facing error that ×TypeError: Cannot read property 'dishes' of undefined at favorite={this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId)} statement in MainComponent.js and in var favorites = props.favorites.favorites.dishes.map((dish) statement of FavoriteDish.js.
MainComponent.js
const DishWithId = ({match}) => {
if(this.props.favorites.favorites!=null) {
if(Array.isArray(this.props.favorites.favorites)) {
this.props.favorites.favorites=this.props.favorites.favorites[0];
}
}
return(
(this.props.auth.isAuthenticated && !this.props.favorites.isLoading)
?
<DishDetail dish={this.props.dishes.dishes.filter((dish) => dish._id === match.params.dishId)[0]}
isLoading={this.props.dishes.isLoading}
errMess={this.props.dishes.errMess}
comments={this.props.comments.comments.filter((comment) => comment.dish === match.params.dishId)}
commentsErrMess={this.props.comments.errMess}
postComment={this.props.postComment}
favorite={this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId)}
postFavorite={this.props.postFavorite}
/>
:
<DishDetail dish={this.props.dishes.dishes.filter((dish) => dish._id === match.params.dishId)[0]}
isLoading={this.props.dishes.isLoading}
errMess={this.props.dishes.errMess}
comments={this.props.comments.comments.filter((comment) => comment.dish === match.params.dishId)}
commentsErrMess={this.props.comments.errMess}
postComment={this.props.postComment}
favorite={false}
postFavorite={this.props.postFavorite}
/>
);
}
<Route path="/menu/:dishId" component={DishWithId} />
<PrivateRoute exact path="/favorites" component={() => <Favorites favorites {this.props.favorites} deleteFavorite={this.props.deleteFavorite} />} />
DishDetail.js
const DishDetail = (props) => {
return <RenderDish dish={props.dish} favorite={props.favorite} postFavorite={props.postFavorite} />
}
function RenderDish({dish, favorite, postFavorite}) {
return(
<div className="col-12 col-md-5 m-1">
<FadeTransform in
transformProps={{
exitTransform: 'scale(0.5) translateY(-50%)'
}}>
<Card>
<CardImg top src={baseUrl + dish.image} alt={dish.name} />
<CardImgOverlay>
<Button outline color="primary" onClick={() => favorite ? console.log('Already favorite') : postFavorite(dish._id)}>
{favorite ?
<span className="fa fa-heart"></span>
:
<span className="fa fa-heart-o"></span>
}
</Button>
</CardImgOverlay>
<CardBody>
<CardTitle>{dish.name}</CardTitle>
<CardText>{dish.description}</CardText>
</CardBody>
</Card>
</FadeTransform>
</div>
);
}
FavoriteDish.js
if (props.favorites.favorites) {
if(Array.isArray(props.favorites.favorites))
props.favorites.favorites=props.favorites.favorites[0];
var favorites = props.favorites.favorites.dishes.map((dish) => {
return (
<div key={dish._id} className="col-12 mt-5">
<RenderMenuItem dish={dish} deleteFavorite={props.deleteFavorite} />
</div>
);
});
}
favorite reducer
import * as ActionTypes from './ActionTypes';
export const favorites = (state = {
isLoading: true,
errMess: null,
favorites: null
}, action) => {
switch(action.type) {
case ActionTypes.ADD_FAVORITES:
return {...state, isLoading: false, errMess: null, favorites: action.payload};
case ActionTypes.FAVORITES_LOADING:
return {...state, isLoading: true, errMess: null, favorites: null};
case ActionTypes.FAVORITES_FAILED:
return {...state, isLoading: false, errMess: action.payload, favorites: null};
default:
return state;
}
}
ActionCreator.js
export const fetchFavorites = () => (dispatch) => {
dispatch(favoritesLoading(true));
const bearer = 'Bearer ' + localStorage.getItem('token');
return fetch(baseUrl + 'favorites', {
headers: {
'Authorization': bearer
},
})
.then(response => {
if (response.ok) {
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText);
error.response = response;
throw error;
}
},
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(favorites => dispatch(addFavorites(favorites)))
.catch(error => dispatch(favoritesFailed(error.message)));
}
export const favoritesLoading = () => ({
type: ActionTypes.FAVORITES_LOADING
});
export const favoritesFailed = (errmess) => ({
type: ActionTypes.FAVORITES_FAILED,
payload: errmess
});
export const addFavorites = (favorites) => ({
type: ActionTypes.ADD_FAVORITES,
payload: favorites
});
Got idea from Giovanni Esposito.
i just changed this line:- favorite={this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId)}
to this :- favorite={this.props.favorites.favorites ? this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId): false}
Ciao, you could modify this line (in DishWithId):
(this.props.auth.isAuthenticated && !this.props.favorites.isLoading)
with:
(this.props.auth.isAuthenticated && !this.props.favorites.isLoading && this.props.favorites.favorites)
and this line (in FavoriteDish.js):
var favorites = props.favorites.favorites.dishes.map((dish)...
with:
if (props.favorites.favorites) {var favorites = props.favorites.favorites.dishes.map((dish)...}
Check condition in favorites such that in case favourite is undefined. It automatically sends false there...
favorite={this.props.favorites.favorites ? this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId): false}
I have an idea that this may be because I am doing some styling things to change my radio button, but I am not sure. I am setting an onClick event that is calling my function twice. I have removed it to make sure it wasn't being triggered somewhere else and the onClick seems to be the culprit.
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
my function is just for now a simple console log of the shipping option:
changeShipping(shipOption){
console.log('clicked') // happening twice
}
If there isn't any reason you see here why this would happen I can post the rest of the code, but there is a lot and I don't think it would pertain to this, but I think this is a good starting place.
Full code:
import React, { Component } from 'react'
import fetch from 'isomorphic-fetch'
import { Subscribe } from 'statable'
import { FoldingCube } from 'better-react-spinkit'
import styles from './styles'
import { cost, cartState, userInfo, itemState, Api } from '../../state'
import { removeCookies, resetCart } from '../../../injectState'
export default class ShippingOptions extends Component {
constructor(props) {
super(props)
this.state = {
shippingOption: {}
}
this.changeShipping = this.changeShipping.bind(this)
}
async changeShipping(shipOption) {
const shipKey = Object.keys(shipOption)[0]
// if (userInfo.state.preOrderInfo.setShip[shipKey] === shipOption[shipKey]) {
// return
// }
let updatedShipOption = {}
Object.keys(shipOption).forEach(k => {
updatedShipOption = userInfo.state.preOrderInfo.setShip
? { ...userInfo.state.preOrderInfo.setShip, [k]: shipOption[k] }
: shipOption
})
userInfo.setState({
preOrderInfo: {
...userInfo.state.preOrderInfo,
setShip: updatedShipOption
}
})
// Make request to change shipping option
const { preOrderInfo } = userInfo.state
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(preOrderInfo),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
}
async componentDidMount() {
if (cartState.state.tab === 2) {
const { shipping } = userInfo.state
const { items, coupon } = itemState.state
let updated = { ...shipping }
const names = updated.shippingFullName.split(' ')
updated.shippingFirst = names[0]
updated.shippingLast = names[1]
delete updated.shippingFullName
updated.site = cartState.state.site
updated.products = items
updated.couponCode = coupon
updated.addressSame = userInfo.state.addressSame
cartState.setState({
loading: true
})
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(updated),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
return
shippingRes.products.forEach(product => {
const regexp = new RegExp(product.id, 'gi')
const updatedItem = items.find(({ id }) => regexp.test(id))
if (!updatedItem) {
console.warn('Item not found and being removed from the array')
const index = itemState.state.items.indexOf(updatedItem)
const updated = [...itemState.state.items]
updated.splice(index, 1)
itemState.setState({
items: updated
})
return
}
updatedItem.price = product.price
itemState.setState({
items: itemState.state.items.map(
item => (item.id === product.id ? updatedItem : item)
)
})
})
updated.shippingOptions = shippingRes.shippingOptions
Object.keys(updated.shippingOptions).forEach(k => {
this.setState({
shippingOption: { ...this.state.shippingOption, [k]: 0 }
})
updated.setShip = updated.setShip
? { ...updated.setShip, [k]: 0 }
: { [k]: 0 }
})
updated.success = shippingRes.success
updated.cartId = shippingRes.cartId
updated.locations = shippingRes.locations
userInfo.setState({
preOrderInfo: updated
})
cost.setState({
tax: shippingRes.tax,
shipping: shippingRes.shipping,
shippingOptions:
Object.keys(updated.shippingOptions).length > 0
? updated.shippingOptions
: null
})
cartState.setState({
loading: false,
apiErrors: shippingRes.errors.length > 0 ? shippingRes.errors : null
})
if (shippingRes.errors.length > 0) {
removeCookies()
shippingRes.errors.forEach(err => {
if (err.includes('CRT-1-00013')) {
itemState.setState({ coupon: '' })
}
})
}
}
}
render() {
return (
<Subscribe to={[cartState, cost, itemState]}>
{(cart, cost, itemState) => {
if (cart.loading) {
return (
<div className="Loading">
<div className="Loader">
<FoldingCube size={50} color="rgb(0, 207, 255)" />
</div>
</div>
)
}
if (cart.apiErrors) {
return (
<div className="ShippingErrors">
<div className="ErrorsTitle">
Please Contact Customer Support
</div>
<div className="ErrorsContact">
(contact information for customer support)
</div>
<div className="Msgs">
{cart.apiErrors.map((error, i) => {
return (
<div key={i} className="Err">
{error}
</div>
)
})}
</div>
<style jsx>{styles}</style>
</div>
)
}
return (
<div className="ShippingOptionsContainer">
<div className="ShippingOptions">
{cost.shippingOptions ? (
<div className="ShipOptionLine">
{Object.keys(cost.shippingOptions).map((k, i) => {
const shipOptions = cost.shippingOptions[k]
const updatedProducts =
shipOptions.products.length === 0
? []
: shipOptions.products.map(product =>
itemState.items.find(
item => item.id === product.id
)
)
return (
<div className="ShippingInputs" key={i}>
{shipOptions.options.map((shipOption, i) => {
return (
<div className="ShippingSection" key={i}>
<div className="SectionTitle">
4. {shipOption.name} Shipping Options
</div>
{updatedProducts.length > 0 ? (
<div className="ShippingProducts">
{updatedProducts.map((product, i) => (
<div key={i}>
for{' '}
{shipOption.name === 'Freight'
? 'Large'
: 'Small'}{' '}
{product.name} from{' '}
{k.charAt(0).toUpperCase() + k.slice(1)}
</div>
))}
</div>
) : null}
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
</div>
)
})}
</div>
)
})}
</div>
) : null}
</div>
<style jsx>{styles}</style>
</div>
)
}}
</Subscribe>
)
}
}
Its because your app component is a wrap in StrictMode.
<React.StrictMode>
<App />
</React.StrictMode>,
If you are using create-react-app then it is found in index.js
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and later restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
https://github.com/facebook/react/issues/12856#issuecomment-390206425
The problem is html related, rather than React related. By default clicking a label will also trigger the onClick event of the input element it is associated with. In your case the onClick event is attached to both the label and the input. So by clicking on the label, the event is fired twice: once for the label and once for the input associated with it.
Edit: Attaching the onClick listener to the input is a possible solution to the problem
Prevent calling twice by using e.preventDefault().
changeShipping(e){
e.preventDefault();
console.log('clicked');
}
e.stopPropagation() is also worth exploring. I was handling an onMouseDown event, and preventDefault was not preventing multiple calls.
https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
In my case, it needs both to avoid redundant calls
<React.StrictMode>
<App />
</React.StrictMode>
from Nisharg Shah
e.preventDefault();
from Amruth
and one more thing for functional component where useEffect() method is called twice for me is bcoz, there were only one useEffect used with holds all methods to bind in FC (functional component) and dependency list. so after the change it looks like as follows:
useEffect(() => {
console.log("AFTER CHANGE : ", data) // move to below method
handleSubmit.bind(this);
handleCancel.bind(this);
testChange.bind(this);
}, [
data // move to below method
]);
useEffect(() => {
console.log("AFTER CHANGE : ", data)
}, [data]);
we should have without dependency list for bindings
To view the changed data after onChange(), then we should have useEffect with dependencyList of the data we are looking for.
Hope this helps. Happy coding . . . .