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 . . . .
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 . . . .
When a restart button is clicked on my quiz, I want the input selections to be removed. I tried testing disabled={restart === true} in my input selection on the question component, but it's not exactly working correctly. Any clue how to accomplish this? https://replit.com/#arshia93/Quizzical#sections/Question.jsx
Question component
export default function Question({question, answers, correctAnswer, updateAnswers, restart}) {
return (
<>
<h3 className="questions">
{`${decode(question)}`}
</h3>
{answers.map(( answerOption, index ) => (
<div key={index}>
<input
disabled={restart === true}
type="radio"
name={`answer option-${question}`}
id={`answer-options-${answerOption}`}
value={answerOption}
onChange={updateAnswers}
/>
<label
className={`answer-options ${correctAnswer ? 'answer-correct' : ``}`}
htmlFor={`answer-options-${question}`}>
{`${decode(answerOption)}`}</label>
</div>
))}
</>
)
}
Quiz data component
export default function QuizData() {
const [finished, setFinished] = React.useState(false)
const [newQuiz, setNewQuiz] = React.useState(false)
const [quizData, setQuizData] = React.useState([{
question: "",
answers: "",
correctAnswer: ""
}]);
const [selections, setSelections] = React.useState([]);
const [score, setScore] = React.useState(0)
React.useEffect(() => {
fetch("https://opentdb.com/api.php?amount=5&category=12&difficulty=medium&type=multiple")
.then(res => res.json())
.then(data => setQuizData(data.results.map(item => ({
question: item.question,
answers: arrayShuffle(item.incorrect_answers.concat(item.correct_answer)),
correctAnswer: item.correct_answer
}))))
},[newQuiz])
function handleSelectedAnswer(event) {
const {value} = event.target
setSelections(prevData => prevData.length > 0 ? [...prevData, value] : [value])
}
const allCorrectAnswers = quizData.map(item => item.correctAnswer)
const totalScore = selections.reduce((score, selection) =>
score + (allCorrectAnswers.includes(selection) ? 1 : 0) , 0);
function complete() {
setScore(totalScore)
setFinished(true)
}
function restart() {
setFinished(false)
setNewQuiz(prevState => !prevState)
}
return (
<div>
{ quizData.length > 1 ?
(quizData.map((item, index) => (
<div>
<Question
key={index}
question={item.question}
answers={item.answers}
restart={newQuiz}
correctAnswer={finished === true ? item.correctAnswer : ""}
chosenAnswer={selections.selectedAnswer}
updateAnswers={handleSelectedAnswer}
/>
</div>
)))
: <p>Loading...</p>
}
{finished === false ?
<button
className="quiz-button"
onClick={complete}>
Check answers
</button>
:
<>
{`You scored ${score}/5 correct`}
<button
className="quiz-button"
onClick={restart}>
Play again
</button>
</>
}
</div>
)
}`
Closed the earlier thread, because might be missleading.
In fact (wtihtout the simplifications and clearing) all you need to do is just remove the
value={answerOption}
disabled={restart === true}
line from Question.jsx (line 18 and 15).
You dont need to change the value dynamically here, since you always get new questions (thus no answers to display at the start) + no reason to disable it
add the key attribute for proper updates for the Question.jsx (Line 14)
key={answerOption}
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
}
I have the following:
const OrderItems = (props) => {
const { Order } = props.res
return (
<div>
{Order.map(order => {
if (order.OrderLines.OrderLine instanceof Array) {
order.OrderLines.OrderLine.map(line => (
<OrderLineItem data={line} />
))
}
})}
</div>
)
}
export default OrderItems
And the component:
const OrderLineItem = ({data}) => {
console.log(data)
return (
<div>Order Line Item</div>
)
}
Nothing is rendered and nothing is logged to the console.
However if I do:
const OrderItems = (props) => {
const { Order } = props.res
return (
<div>
{Order.map(order => {
if (order.OrderLines.OrderLine instanceof Array) {
order.OrderLines.OrderLine.map(line => console.log(line))
}
})}
</div>
)
}
The data I want is logged to the console. How can I render or pass the data from line please?
You don't return from .map() that's the reason. You can use && instead of if.
Try as the following instead:
{Order.map(order =>
order.OrderLines.OrderLine instanceof Array &&
order.OrderLines.OrderLine.map(line => <OrderLineItem data={line} />)
)}
While getting the props in the child component
const OrderLineItem = ({ data }) => {
console.log(data)
// DO WHAT YOU WANT TO DO WITH THE DATA!
return (
<div>Order Line Item</div>
)
}
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>
)}
</>
)
}
}