I have set up a conditional element on click on a button I've made within react. but default prop runs onload without clicking the button how can I fix this issue?
the button looks like this:
<p onClick={Butter + Milk + Bread + Soup + Cheese > 0 ? props.next_ClickHandler : alert('Please Input some food!')}>Buy Now!</p>
I would like it so that if the values add to greater than 0 the props are passed but if not an alert is played why it this not working as intended?
Edit full code:
import React, { useState, useEffect, useContext } from "react";
import Data from '../shoppingData/Ingredients';
import { quantitiesContext } from '../shoppingData/Quantities';
const ShoppingPageOne = (props) => {
//element displays
const [pageone_show, setPageone_show] = useState("pageOne");
//stores quantities of items as JSON objects
const [Quantities, setQuantities] = useContext(quantitiesContext);
const quantities = useContext(quantitiesContext);
const Bread = quantities[0].Bread.quantities;
const Milk = quantities[0].Milk.quantities;
const Cheese = quantities[0].Cheese.quantities;
const Soup = quantities[0].Soup.quantities;
const Butter = quantities[0].Butter.quantities;
useEffect(() => {
//sets info text using Json
if (props.showOne) {
setPageone_show("pageOne");
} else {
setPageone_show("pageOne hide");
}
}, [props.showOne]);
return (
<div className={"Shopping_Content " + pageone_show}>
<div className="Shopping_input_aligner">
<div className='Shopping_input_container'>
{Data.map((Ingredients) => {
//updates Quanties Hook
const handleChange = (event) => {
setQuantities({
...Quantities,
[Ingredients.Name]: {
...(Quantities[Ingredients.Name] ?? {}),
quantities: event.target.value
}
});
};
return (<div className={"Shopping_input " + Ingredients.Name} key={Ingredients.Name}>
<p>{Ingredients.Name} £{Ingredients.Price}</p>
<input onChange={handleChange.bind(this)} min="0" placeholder="Input food quantity" type="number"></input>
</div>)
})}
</div>
<div className='Discount_list'>
<h3>Discounts:</h3>
<li>Buy one cheese get one free!</li>
<li>Buy a Soup get a half price bread!</li>
<li>A third off butter!</li>
</div>
</div>
<div className="Shopping_Buttons">
<p onClick={() => {Butter + Milk + Bread + Soup + Cheese > 0 ? props.next_ClickHandler : alert('Please Input some food!')}} >Buy Now!</p>
</div>
</div>
);
};
export default ShoppingPageOne;
You can have a cleaner code with something like this if you're using React Hooks
const [ingredientsGreaterThanZero, setIngredientsGreaterThanZero] = useState(false);
useEffect(() => {
if (butter + milk + bread + soup + cheese > 0) {
setIngredientsGreaterThanZero(true)
} else {
setIngredientsGreaterThanZero(false)
}
}, [butter, milk, bread, soup, cheese]);
...
{ingredientsGreaterThanZero ?
<p onClick={props.next_ClickHandler}>Buy Now!</p> :
<p onClick={() => alert('Please Input some food!')}>Buy Now!</p>
}
<p onClick={() => { Butter + Milk + Bread + Soup + Cheese > 0 ? props.next_ClickHandler : alert('Please Input some food!')}}>Buy Now!</p>
Can you try using this?
Reason
If you attach any event in element with onClick() or any other event handler,
You shouldn't add any function invocation like in your example alert().
Because, of parentheses () the function runs when component mounted in dom.
Example:
<p onClick={alert('runs on load')}>wrong way</p>
Solution
You have to add an empty function and and writes your codes in it. If your codes contain function invocation ( with parentheses () ).
Example:
<p onClick={() => alert ('only runs on click')}>right way</p>
Happy Coding :)
Related
I have the following code:
export default function App() {
const defaultText = "Your partner for software innovations";
const colorText = "software";
const result = defaultText.split(" ").map((txt) => txt);
return <div className="App">{result}</div>;
}
defaultText is my data from the backend.
colorText is a word that should be colored.
So I am trying to do the following. I want to map through the defaultText and if txt === colorText, then display that word in red, so the final result would read "Your partner for software innovations" with the word "software" in red.
I easily can map defaultText as you see but I can't understand how to implement that filtering logic.
You can split the text into parts and then add a class to the text to highlight.
Pseudo code:
const defaultText = "Your partner for software innovations";
const colorText = "software";
const segments = defaultText.split(colorText);
return (
<div className="App">
{segments.map((segment, index) => (
<Fragment key={index}>
<span>{segment}</span>
{(index !== segments.length - 1) && (<span>{colorText}</span>)}
</Fragment>
)}
</div>
);
And CSS:
span.highlight {
color: red;
}
If you want the keyword to match regardless of the letter's case, you can use RegEx when splitting.
Just replace the string, with the same string but with a style="color:(some color);"
defaultText.replace("software", "<span style='color:red;'>software</span>");
import "./styles.css";
export default function App() {
const defaultText = "Your partner for software innovations";
const colorText = "software";
const result = defaultText
.split(" ")
.map((txt) =>
txt === colorText ? (
<span className="colorText">{colorText} </span>
) : (
<span>{txt} </span>
)
);
return <div className="App">{result}</div>;
}
styles.css
.colorText{
color red
}
Try out with this solution. It worked for me
So I have this function here:
const printCardList = (arr) => {
const uo_list = document.getElementById("verify_list");
arr.forEach((card) => {
let list_item = document.createElement("LI");
let str = card.name + " " + card.mana_cost + " " + card.set_name;
list_item.appendChild(document.createTextNode(str));
uo_list.appendChild(list_item);
});
};
and its suppose to insert list items into and unorder list from an array of card objects.
return(
<div className="list-confirm">
<h3> Please confirm card list </h3>
<ul id="verify_list"></ul>
<br />
<button onClick={getCardList}>Confirm</button>
</div>
);
If I do a console.log on arr I can verify that it is an array of cards, but if I console.log card from inside the for each it does not even trigger. It's like the for each does not run. Does anyone have an idea why this is happening?
I'm not sure what you are trying to do, the first part of your code is plain javascript that manipulates the DOM, while the second part is react js object.
You normally don't want to mix these two, either you code your javascript as part of the html, like the first part, or - if you want to create an array of cards in react you can do something like:
let cardList = arr.map(card => {
listItem = <li>{card.name + " " + card.mana_cost + " " + card.set_name }</li>
return listItem;
})
return(
<div className="list-confirm">
<h3> Please confirm card list </h3>
<ul id="verify_list">{cardList}</ul>
<br />
<button onClick={getCardList}>Confirm</button>
</div>
);
what I did is assigned the list itself to a variable named 'cardList', JSX object are just javascript objects, so you can assign them to a variable or return then from a function.
to place the card list inside the page (or component), you can just use the {} notation, which will embed the cardList object as part of the returned JSX object.
Thanks for all the advice. In hindsight, I should have stuck to what I was learning and not try to freestyle. React is about using states. So rather than having a function that will generate HTML from an array of data and I had to do use "the state". Then code the render to loop through the list of cards when the button is pressed.
const [state, setState] = useState([]);
const card_list= ()=> {...}
const changeState = ()=> {setState(card_list)}
return(
<div className="list-confirm">
<h3> Please confirm card list </h3>
<ul>
{state.map((card) => (
<li>{card.name}</li>
))}
</ul>
<br />
<button onClick={changeSate}>Confirm</button>
</div>
);
You should change the onClick. More precisely call the method after getting items from getCardList() method.
This is an example:
const printCardList = (arr) => {
const uo_list = document.getElementById("verify_list");
arr.forEach((card) => {
let list_item = document.createElement("li");
let str = card.name + " " + card.mana_cost + " " + card.set_name;
list_item.appendChild(document.createTextNode(str));
uo_list.appendChild(list_item);
});
};
// this is a test method. Not the real one
const getCardList = () => {
return [ { name: "Card", mana_cost: 0, set_name: "Set: Card" } ];
};
<div className="list-confirm">
<h3> Please confirm card list </h3>
<ul id="verify_list"></ul>
<br />
<button onClick="printCardList(getCardList())">Confirm</button>
</div>
What is the problem?
I have a functional component that should render a list of player stats based on what team the user selects. The functionality of getting the data works and when I console log the state array using a useEffect I get an array with data inside it, but when I try to render the objects in html so you can see them on the screen sometimes they flicker on and then disappear, sometimes nothing happens at all.
What I've tried
I've tried using both a state array and just an ordinary variable array to see if that makes any difference. I've tried using .forEach and just a for loop to see if that would work. I've messed around with how I store the data and just trying to use a simple array instead of an object nothing so far has managed to get it rendered. As a note it is clear that the component does render as the div (className = Player-Stats) that contains the .map function is visible when inspected.
Thanks for any help and suggestions, I've spent days on this one functionality because the NHL api stores it's data super weirdly and you need to do all kinds of things to get the data you want. I didn't want to spam this question with tons of my code so if you need anything else like the parent components please ask and I can provide them.
Code Snippets
Landing Page
import { useState } from 'react';
import '../CSS/LandingPage.css';
import Instruction from './Instruction';
import LeagueLeaders from './LeagueLeaders';
import NavBar from './NavBar';
import TeamSelector from './TeamSelector';
import TeamStandings from './TeamStandings';
function LandingPage() {
const [teamSelected, setTeamSelected] = useState(false);
const [listOfTeams, setListOfTeams] = useState([]);
return (
<div className = 'Landing-Page-Container'>
<NavBar/>
<div className = 'Stats-Standings-Container'>
<div className = 'Team-Select-Container'>
<TeamSelector toggleStats = {setTeamSelected} setListTeams = {setListOfTeams}/>
</div>
<div className = 'Stats-Container'>
<LeagueLeaders showStats = {teamSelected} getListTeams = {listOfTeams} />
</div>
<div className = 'Standings-Container'>
<TeamStandings/>
</div>
</div>
</div>
);
}
export default LandingPage;
LeagueLeaders code
import { useState } from 'react';
import {FaChevronLeft, FaChevronRight} from 'react-icons/fa';
import '../CSS/LeagueLeaders.css';
import Instruction from './Instruction';
import LeaderStats from './LeaderStats.js';
function LeagueLeaders({showStats, getListTeams}){
var title = ['Skaters', 'Goalies', 'Defencemen'];
var [titleNo, setTitleNo] = useState(0);
var goalieOptions = ['GAA', 'SV%', 'SHUTOUTS'];
var nonGoalieOptions = ['POINTS', 'GOALS', 'ASSISTS'];
function selectPosition(task){
if(task === '+' && titleNo <2){
setTitleNo(titleNo+1);
}else if (task === '+' && titleNo == 2){
setTitleNo(0);
}else if(task === '-' && titleNo >0){
setTitleNo(titleNo-1);
}else{
setTitleNo(2);
}
}
return(
<div className = 'Leaders-Container'>
<div className = 'Leaders-Title'>
<FaChevronLeft className = 'toggleArrow' size = {24} color = 'white' onClick={() => selectPosition('-')}/>
<h1>{title[titleNo]}</h1>
<FaChevronRight className = 'toggleArrow' size = {24} color = 'white' onClick={() => selectPosition('+')}/>
</div>
<div className = 'Leaders-Selection-Container'>
<div className = 'Stat-Select-1'>
<p>{titleNo == 1 ? goalieOptions[0]: nonGoalieOptions[0]}</p>
</div>
<div className = 'Stat-Select-2'>
<p>{titleNo == 1 ? goalieOptions[1]: nonGoalieOptions[1]}</p>
</div>
<div className = 'Stat-Select-3'>
<p>{titleNo == 1 ? goalieOptions[2]: nonGoalieOptions[2]}</p>
</div>
</div>
<div className = 'Leaders-Stats-Container'>
{showStats ? <LeaderStats playerPos = {titleNo} teams = {getListTeams}/> : <Instruction/>}
</div>
</div>
);
}
export default LeagueLeaders;
TeamSelector component code
import '../CSS/TeamSelector.css';
import { useEffect, useState } from "react";
import teamDetail from "../Assets/teamDetail";
function TeamSelector( {toggleStats, setListTeams}) {
const [listOfTeams, setListOfTeams] = useState([]);
const [listOfURL, setListOfURL] = useState([]);
const [selectedTeams, setSelectedTeams] = useState([]);
useEffect(()=>{
console.log(selectedTeams);
setListTeams(selectedTeams);
}, [selectedTeams])
function handleClick(e){
const selectedTeamsCopy = [...selectedTeams];
if(selectedTeams.includes(e.currentTarget.id)){
if(selectedTeams.length <= 1){
toggleStats(false);
selectedTeamsCopy.splice(selectedTeamsCopy.indexOf(e.currentTarget.id, 1), 1);
setSelectedTeams(selectedTeamsCopy);
}else{
selectedTeamsCopy.splice(selectedTeamsCopy.indexOf(e.currentTarget.id, 1), 1);
setSelectedTeams(selectedTeamsCopy);
}
}else {
if(selectedTeams.length === 0){
toggleStats(true);
selectedTeamsCopy.push(e.currentTarget.id);
setSelectedTeams(selectedTeamsCopy);
}else{
selectedTeamsCopy.push(e.currentTarget.id);
setSelectedTeams(selectedTeamsCopy);
}
}
if(e.target.style.opacity === '1'){
e.target.style.opacity = '25%';
}else {
e.target.style.opacity = '100%';
}
}
return (
<div className = 'Team-Logo-Container'>
{teamDetail.map((Teams)=>(
<div>
<img onClick={(e) => handleClick(e)} key = {Teams.ID} id = {Teams.ID} alt = {Teams.Name +' Logo'} src = {Teams.URL} className = 'logo'/>
</div>
))}
</div>
);
}
export default TeamSelector;
Array of objects layout
[{ ID: "8480003", Name: "Jesper Boqvist", Points: "1", … }, { ID: "8475193", Name: "Tomas Tatar", Points: "10", … }, etc. etc.]
Rendering the array
return(
<div className = 'Player-Stats'>
{triggerStats ? listOfStats.map((d)=>{
return <p className = 'Stats' key={d.ID}>{d.ID}</p>}
) : <p className = 'Stats'> Sorry theres no available data</p>}
</div>
)
LeaderStats component script (Where the rendering issue is)
import { useEffect, useState, useRef } from "react";
import '../CSS/LeaderStats.css';
function LeaderStats({playerPos, teams}){
const isInitialMount = useRef(true);
const [listOfStats, setListOfStats] = useState([]);
const [triggerStats, setTriggerStats] = useState(false);
//If it's not the first render and the listOfStats state has changed it will render the stats of the players
useEffect(()=>{
if(!isInitialMount.current){
console.log(listOfStats);
setTriggerStats(true);
}
}, [listOfStats])
//When teams prop changes, run the function to get player data from API
useEffect(()=>{
if (isInitialMount.current) {
isInitialMount.current = false;
}else{
if(teams.length !== 0){
getPlayerIDs(teams);
}else{
setTriggerStats(false);
}
}
},[teams])
//This function runs all the axios calls and gathers data from multiple endpoints and saves it to an array
function getPlayerIDs(teamID){
const axios = require('axios');
var playerList=[];
var tempObj;
teamID.forEach(d =>
axios.get(`https://statsapi.web.nhl.com/api/v1/teams/${parseInt(d)}/roster`).then(res => {
//If user has selected the forward position filter roster by that player position and get the stats and save to temp array.
if(playerPos === 0){
res.data.roster.filter(obj => obj.position.type === 'Forward').map(e=>
axios.get(`https://statsapi.web.nhl.com/api/v1/people/${e.person.id}/stats?stats=statsSingleSeason&season=20212022`).then(res =>{
if(typeof res.data.stats[0].splits[0] !== 'undefined'){
if(playerPos !== 1 ){
tempObj = { ID: `${e.person.id}`, Name: `${e.person.fullName}` ,Points: `${res.data.stats[0].splits[0].stat.points}`, Goals: `${res.data.stats[0].splits[0].stat.goals}`, Assists: `${res.data.stats[0].splits[0].stat.assists}`};
playerList.push(tempObj);
}
}
})
);
}
//If user has selected the goalie position filter roster by that player position and get the stats and save to temp array.
else if(playerPos === 1){
res.data.roster.filter(obj => obj.position.type === 'Goalie').map(e=>
axios.get(`https://statsapi.web.nhl.com/api/v1/people/${e.person.id}/stats?stats=statsSingleSeason&season=20212022`).then(res =>{
if(typeof res.data.stats[0].splits[0] !== 'undefined'){
if(playerPos !== 1 ){
tempObj = { ID: `${e.person.id}`, Name: `${e.person.fullName}`, GAA: `${res.data.stats[0].splits[0].stat.goalAgainstAverage}`, SavePercentage: `${res.data.stats[0].splits[0].stat.savePercentage}`, Shutouts: `${res.data.stats[0].splits[0].stat.shutouts}`};
playerList.push(tempObj);
}
}
})
);
}
//If user has selected the defense position filter roster by that player position and get the stats and save to temp array.
else if(playerPos === 2){
res.data.roster.filter(obj => obj.position.type === 'Defenseman').map(e=>
axios.get(`https://statsapi.web.nhl.com/api/v1/people/${e.person.id}/stats?stats=statsSingleSeason&season=20212022`).then(res =>{
if(typeof res.data.stats[0].splits[0] !== 'undefined'){
if(playerPos !== 1 ){
tempObj = { ID: `${e.person.id}`, Name: `${e.person.fullName}` ,Points: `${res.data.stats[0].splits[0].stat.points}`, Goals: `${res.data.stats[0].splits[0].stat.goals}`, Assists: `${res.data.stats[0].splits[0].stat.assists}`};
playerList.push(tempObj);
}
}
})
);
}
})
);
//Set the state to the temp array that will be used to render the stats
setListOfStats(playerList);
}
return(
<div className = 'Player-Stats'>
{triggerStats ? listOfStats.map((d)=>{
return <p className = 'Stats' key={d.ID}>{d.ID}</p>}
) : <p className = 'Stats'> Sorry theres no available data</p>}
</div>
)
}
export default LeaderStats;
I have managed to figure out the issue so I'll post it here for those of you who in the future might find yourself in the same position as me without being able to find an answer. Turns out the way I was setting setListOfStats(playerList); made the state change without me realising it and by the time it got to rendering the .map there was nothing actually there as console.log takes a snapshot of the state at the time of the console.log. The solution (which I had experimented with before a few times but couldn't get working) was to remove the temprorary array of playerList completely and change the following code playerList.push(tempObj); to setListOfStats(listOfStats => [...listOfStats, tempObj]) thise line of code essentially sets the list of stats as I did before but it captures the previous state as well. This comes up with another issue of when you want to remove some data from the list but that's not related to this question. Hopefully someone can make use of this answer in the future.
I have an issue with my Hangman app im creating.I'm stuck at the stage when im trying to program the letters reveal while clicking on them.
Hangman.js
import al from '../alphabet'
import {HangmanContext} from '../context/HangmanContext'
const Hangman = (props) =>{
const {password,displayGame,displayForm} = useContext(HangmanContext)
const [subject,setSubject] = password
const [gameDisplay,setGameDisplay] = displayGame
const [createSubjectDisplay,setCreateSubjectDisplay] = displayForm
let passwordArr = Array.from(subject)
const [hiddenPassword,changeHiddenPassword] = useState([''])
useEffect(() => {
let passwordArr = Array.from(subject)
console.log(passwordArr)
changeHiddenPassword(passwordArr.map(item => '_'))
},[subject]);
const chooseLetter = (e) => {
let letter = e.target.innerHTML
passwordArr.forEach((s,i) => {
if (s == letter){
console.log(hiddenPassword)
changeHiddenPassword(hiddenPassword.map((item,j) => {
if(j == i){
item = subject[i]
}
return item
}))
}
console.log(hiddenPassword)
})
}
const letters = al.map((letter) =>
<span onClick={chooseLetter}>{letter}</span>
)
return(
<div style={{display:gameDisplay}}>
<div className='password'>
{
passwordArr.map((item,i) => <span>{hiddenPassword[i]}</span>)
}
</div>
<div className='wrapper'>
<div className='pass_input'>Enter password:<input type='text' maxLength='30'/></div>
<div className='alphabet'> {letters} </div>
</div>
</div>
)
}
export default Hangman
In my chooseLetter function im trying to change hiddenPassword state that contains initially as many "_" as is the password length(which i have fixed in another state ) to the point where it has revealed clicked letter.It works fine when the password contain letters that doesn't repeat. When letters in the password are repeating, revealing them only works for the last of them and i want them to reveal all.
Hello I am new to React and building a quote generator. I want to pull out one quote at a time from my array and show it on the screen, however I can only seem to output each quote to the console.
I have:
1.Created an on click handler and function so that when the user clicks my quote array is targeted.
2. In this function I have created a variable to hold my random array index
3. I have console.logged the array index to see if every time the user clicks it the quote appears.
Component and function and click handler, as you can see the Quote Component should return the quote from the array in my opinion but nothing happens:
class Card extends Component {
state = {
quotes: ['"A dream doesn\'t become reality through magic; it takes sweat, determination and hard work."','"You GOT this!"','"To be or not to be that is the question"'];
changeQuoteHandler = (event) => {
const quotes = [...this.state.quotes];
const arrayIndex = quotes[Math.floor(Math.random() * quotes.length)]
console.log(arrayIndex);
this.setState({
quotes: quotes
})
};
render(){
return (
<div className="Card">
<div>
<h2>Random Quote Generator</h2>
<Quote className="QuoteStyle" quote={this.state.quotes.arrayIndex}/>
</div>
<div className="Flex">
<div>
<NewQuoteButton onClick={this.changeQuoteHandler}/>
</div>
</div>
</div>
)
}
};
export default Card;
Quote Componenet :
import React from 'react';
const Quote = (props) => {
return(
<p>{props.quote}</p>
)
};
export default Quot
I would like to print one quote at a time to the screen on click.
You are so close. You can store the arrayIndex that you generate in the state and use it to display the quote. The code would look like something below
class Card extends Component {
state = {
quotes: ['"A dream doesn\'t become reality through magic; it takes sweat, determination and hard work."','"You GOT this!"','"To be or not to be that is the question"'],
selectedIndex: 0,
}
changeQuoteHandler = (event) => {
const quotes = [...this.state.quotes];
const arrayIndex = Math.floor((Math.random() * 10) % quotes.length);
this.setState({
quotes: quotes,
selectedIndex: arrayIndex,
});
};
render(){
return (
<div className="Card">
<div>
<h2>Random Quote Generator</h2>
<Quote className="QuoteStyle" quote={this.state.quotes[this.state.selectedIndex]}/>
</div>
<div className="Flex">
<div>
<NewQuoteButton onClick={this.changeQuoteHandler}/>
</div>
</div>
</div>
)
}
};
export default Card;