I'm trying to modify state and take the new state to render.
When I click and modified(added {isClicked: true} to array), console.log(this.state.listOfQuotes) inside onClicked function returns modified the full array of state(which I want to use)
but after render, console.log(this.state.listOfQuotes) returns only one clicked element and not even modified one...
Any help/hint much appreciated!
Here is my code
import React from "react";
export class Quotes extends React.Component {
constructor(props) {
super(props);
this.state = { listOfQuotes: [] };
this.vote = this.vote.bind(this);
this.onClicked = this.onClicked.bind(this);
}
componentDidMount() {
const url = "https://programming-quotes-api.herokuapp.com/quotes";
fetch(url)
.then(res => res.json())
.then(quote => {
this.setState({
listOfQuotes: quote
});
});
}
onClicked(id) {
const quotes = [...this.state.listOfQuotes];
const clickedQuote = quotes.findIndex(quote => quote.id === id);
console.log("onclicked", clickedQuote);
const newArray = { ...quotes[clickedQuote], isClicked: true };
console.log(newArray);
this.setState(prevState => ({
listOfQuotes: [
...prevState.listOfQuotes.splice(clickedQuote, 1, newArray)
]
}));
console.log(this.state.listOfQuotes); ----------> this one returns what i want
}
render() {
console.log(this.state.listOfQuotes); -----------> i want to have same result as above state
return (
<div className="quotes">
<div>
{this.state.listOfQuotes.map((quote, idx) => (
<div key={idx}>
<div onClick={() => this.onClicked(quote.id)}>
{!quote.isClicked ? (
<div className="before-clicked">{quote.en}</div>
) : (
<div className="after-clicked">{quote.en}</div>
)}
</div>
<div>By {quote.author}</div>
<div>Rating {quote.rating}</div>
<div className="vote">
<span>{quote.numberOfVotes}</span>
</div>
</div>
))}
</div>
</div>
);
}
}
There is a problem with your onClicked method.
It is not modifying the array correctly.
In my opinion, this is how it could have done.
onClicked(id) {
let quotes = [...this.state.listOfQuotes];
const clickedQuoteIndex = quotes.findIndex(quote => quote.id === id);
// Modify the object on the found index and assign true to "isClicked"
quotes[clickedQuoteIndex].isClicked = true;
// And then setState with the modified array
// Since setState is async, so the console shouldn't be called immediately
// but rather in the callback
this.setState({ listOfQuotes: quotes }, () => {
console.log(this.state.listOfQuotes);
});
}
Related
I am looking for a way to hide a div once the button thats in it is clicked and continue to show all other div's.
I've tried using the setState method, however when setting it to false with onClick() all of my objects disappear.
class App extends React.PureComponent {
state: {
notHidden: false,
}
constructor(props: any) {
super(props);
this.state = {search: '', notHidden: true};
this.hideObject = this.hideObject.bind(this)
}
hideThisDiv() {
this.setState({notHidden: false})
}
async componentDidMount() {
this.setState({
objects: await api.getObjects()
});
}
render = (objects: Object[]) => {
return ({Object.map((object) =>
<div key={index} className='class'>
<button className='hide' type='button' onClick={() => hideThisDiv()}>Hide</button>
<p>object.text</p>
</div>}
render() {
const {objects} = this.state;
return (<main>
<h1>Objects List</h1>
<header>
<input type="search" onChange={(e) => this.onSearch(e.target.value)}/>
</header>
{objects ? this.render(objects) : null}
</main>)
}
);
The data is a data.json file filled with many of these objects in the array
{
"uuid": "dsfgkj24-sfg34-1r134ef"
"text": "Some Text"
}
Edit: Sorry for the badly asked question, I am new to react.
Not tested, just a blueprint... is it what you want to do?
And yes I didn't hide, I removed but again, just an idea on how you can hide button separately, by keeping in state which ones are concerned.
function MagicList() {
const [hidden, hiddenSet] = useState([]);
const items = [{ id:1, text:'hello'}, { id:2, text:'from'}, { id:3, text:'the other sided'}]
const hideMe = id => hiddenSet([...hidden, id]);
return {
items.filter( item => {
return hidden.indexOf(item.id) !== -1;
})
.map( item => (
<button key={item.id} onClick={hideMe.bind(this, item.id)}>{item.text}</button>
))
};
}
Edition
const hideMe = id => hiddenSet([...hidden, id]);
It is just a fancy way to write:
function hideMe(id) {
const newArray = hidden.concat(id);
hiddenSet(newArray);
}
I suggest using a Set, Map, or object, to track the element ids you want hidden upon click of button. This provides O(1) lookups for what needs to be hidden. Be sure to render your actual text and not a string literal, i.e. <p>{object.text}</p> versus <p>object.text</p>.
class MyComponent extends React.PureComponent {
state = {
hidden: {}, // <-- store ids to hide
objects: [],
search: "",
};
// Curried function to take id and return click handler function
hideThisDiv = id => () => {
this.setState(prevState => ({
hidden: {
...prevState.hidden, // <-- copy existing hidden state
[id]: id // <-- add new id
}
}));
}
...
render() {
const { hidden, objects } = this.state;
return (
<main>
...
{objects
.filter((el) => !hidden[el.uuid]) // <-- check id if not hidden
.map(({ uuid, text }) => (
<div key={uuid}>
<button
type="button"
onClick={this.hideThisDiv(uuid)} // <-- attach handler
>
Hide
</button>
<p>{text}</p>
</div>
))}
</main>
);
}
}
EDIT - I fixed this and posted the working code.
I'm working on a project and I am having a specific issue I can't figure out how to fix. I am displaying a list of champions images and when the user clicks on one of them (s) then it will change the page to display that champions name. Currently I can console.log any of the names without any issues which means my functional component Newchamp() is working! However I am having trouble passing an argument from NewChamp to the class component SpecificChamp. When I add the last line in Newchamp return and try to display it in SpecificChamp using {s} its undefined!
Is it possible to pass an argument from my functional class to my component class? if not how can I get the page to change to the specific image that is clicked? I am new to react and appreciate any help!
Can anyone please help me out with this
import React, { Component } from 'react';
import './Champions.css';
class AllChamps extends Component {
render() {
let champion = this.props.champion;
return(
<div className='champions'>
<h1> all champions</h1>
{Object.keys(this.props.champions).map((s) => (
<div className='champs' onClick={() => this.props.NewChamp({s, champion})}>
<img
alt='Champion Images'
src={`http://ddragon.leagueoflegends.com/cdn/10.16.1/img/champion/${s}.png`}
onClick={this.props.onClick}
></img>
{s}
</div>
))}
</div>
)}}
class SpecificChamp extends Component {
render() {
let champion = this.props.champion
let Spec = champion[champion.length - 1];
return (
<div className='champions'>
<h1> 1 champions</h1>
<div className='champs'>
<button onClick={this.props.onClick}></button>
{Spec}
</div>
</div>
)}
}
class Champions extends Component {
constructor(props) {
super(props);
this.handleAllChamps = this.handleAllChamps.bind(this);
this.handleSpecificChamp = this.handleSpecificChamp.bind(this);
this.NewChamp = this.NewChamp.bind(this);
this.state = {
champions: [],
champion: [],
clickedChamp: false,
thisChamp: 'ahri'
}}
NewChamp = (props) =>
{
let s = props.s;
props.champion.push(s);
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
Object.keys(response.data).map((a) => (s = a
))})
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
console.log(s)
console.log(response.data)
console.log(props.champion)
})
console.log(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`);
}
handleAllChamps = (props) => {
this.setState({ clickedChamp: true,
})};
handleSpecificChamp = () => {
this.setState({ clickedChamp: false,
})};
componentDidMount(props) {
const apiUrl = `http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion.json`;
fetch(apiUrl)
.then(response => { return response.json() })
.then((response) => {
this.setState({
champions: response.data
}, () => (this.state.champions))
return
})
}
render() {
const clickedChamp = this.state.clickedChamp;
let display;
if (clickedChamp ) {
display = <SpecificChamp champion={this.state.champion} onClick={this.handleSpecificChamp} s={this.state.thisChamp}/>;
} else {
display = <AllChamps champions={this.state.champions} onClick={this.handleAllChamps} NewChamp={this.NewChamp} thisChamp={this.state.thisChamp} champion={this.state.champion} />;
}
return (
<div>
<div className='champions'></div>
{display}
</div>
);
}
}
export default Champions;
The render function in class component does not has any props. You should use props from this like what you have done with handle click.
class SpecificChamp extends Component {
render() {
return (
<div className='champions'>
<h1> 1 champions</h1>
<div className='champs'>
<button onClick={this.props.onClick}></button>
{this.props.s}
</div>
</div>
)}
}
I am making a React App for Ping Pong Tournament. Code below.
import React, { Fragment, Component } from "react";
import Button from "./Button";
const playerStylingTrue = {
backgroundColor: "#26C281"
};
class Matches extends Component {
constructor(props) {
super(props);
this.state = {
numOfRounds: "",
numberOfPlayers: this.props.numberOfPlayers,
player1Clicked: false,
player2Clicked: false,
winners: []
}
this.onClickWinnerP1 = this.onClickWinnerP1.bind(this);
this.onClickWinnerP2 = this.onClickWinnerP2.bind(this);
}
numberOfRounds() {
const { numberOfPlayers } = this.state;
const numOfRounds = Math.ceil((Math.log(numberOfPlayers)) /
(Math.log(2)));
this.setState = ({
numOfRounds: numOfRounds
})
};
onClickWinnerP1(player1) {
let player1String = player1.toString()
let { winners } = this.state;
// let findWinner = winners.find(o => o.player1String === player1);
//winners.includes(findWinner) ? null :
this.setState({
player1Clicked: !this.state.player1Clicked,
player2Clicked: this.state.player1Clicked,
winners: [{...winners, player1String, winner:true}]
})
};
onClickWinnerP2(player2) {
let player2String = player2.toString()
let { winners } = this.state;
this.setState({
player2Clicked: !this.state.player2Clicked,
player1Clicked: this.state.player2Clicked,
winners: winners.includes(player2String) ? [...winners] :
[{...winners, player2String, winner:true}]
})
};
render() {
const { pairs } = this.props;
const { winners } = this.state;
console.log(winners)
return (
<Fragment>
<Button
onClick={this.props.onClick}
className={"btn btn-success"}
buttonText={"Create Random Matches 🏓"}
/>
{pairs.map((pair, i) => {
let player1 = [...pair];
let player2 = player1.splice(0, Math.ceil(player1.length /
2));
return (
<div key={i} className="fixture-div">
<ul className="list-unstyled fixture-list">
<li
style={ this.state.winners.includes(player1) ? playerStylingTrue : null}
onClick={() => this.onClickWinnerP1(player1)}
className="hvr-grow fixture">
{player1}
</li>
<span>vs</span>
<li
style={this.state.winners.includes(player2) ? playerStylingTrue : null}
onClick={() => this.onClickWinnerP2(player2)}
className="hvr-grow fixture">
{player2}
</li>
</ul>
</div>
)
})
}
{/* <TwoRounds pairs={pairs}/> */}
</Fragment>
);
}
}
export default Matches;
Currently the onClickWinnerP1 successfully add an object to the state, containing the correct details. However if I click twice or use onClickWinnerP2, is also adds an object to the state, but nested within the object that is already there. And so on, so on. Just keeps nesting.
Any help would be greatly appreciated!
The way you are doing setState in noOfRounds function isn't correct. Instead you should do like below
this.setState({
numOfRounds: numOfRounds
})
Regarding your issue, do something like below in onClickWinnerP1 function to push objects into an array
this.setState(prevState => ({
player1Clicked: !this.state.player1Clicked,
player2Clicked: this.state.player1Clicked,
winners: [...prevState.winners, {player1String, winner:true}]
}))
AND in onClickWinnerP2 function do something like below to push objects into an array
this.setState(prevState => ({
player2Clicked: !this.state.player2Clicked,
player1Clicked: this.state.player2Clicked,
winners: prevState.winners.includes(player2String) ? [...prevState.winners] :
[...prevState.winners, {player2String, winner:true}]
}))
I have a parent component ProductTest that holds state as the source of truth. The state is an array of variant items. Each item is simplified for this example and has a title.
When a Variant's title is updated, the child will call it's handler callback via the prop passed by the parent. The parent then updates state, and the child will then have a new title property.
What I am seeing is that the child's componentWillReceiveProps function will display the same value for this.props and nextProps. I would think that since the value is supplied by the parent, the two would be different.
I'm not sure what I'm doing incorrectly.
Here is the parent ProductTest component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import axios from 'axios'
import Variant from './variants/Variant'
import { REACT_APP_API_URL, REACT_APP_SITE_KEY } from '../../../shared/vars'
import '../../../css/variants.css'
class ProductTest extends Component {
constructor(props) {
super(props)
this.state = {
variants: []
}
this.handleVariantTitleChange = this.handleVariantTitleChange.bind(this)
this.handleVariantValueChange = this.handleVariantValueChange.bind(this)
}
componentDidMount() {
this.loadData()
}
loadData() {
const apiEndpoint = 'products/some-product'
const AUTH_HEADER = { Authorization: REACT_APP_SITE_KEY }
return axios.get(REACT_APP_API_URL + apiEndpoint, { headers: AUTH_HEADER })
.then((response) => {
this.setState({ variants: response.data.product.variants })
})
.catch((err) => {
console.log("YA GOOFED BUD!", err)
})
}
handleVariantTitleChange(e, i) {
let { variants } = this.state
const { value } = e.target
variants = variants.map((item, index) => {
item.title = i === index ? value : item.title
return item
})
this.setState({ variants })
}
handleVariantValueChange(e, id) {
let { variants } = this.state
const { value } = e.target
variants = variants.map(variant => {
variant.items = variant.items.map(item => {
item.title = id === item.id ? value : item.title
return item
})
return variant
})
this.setState({ variants })
}
render() {
return (
<div>
{this.state.variants.map((variant, i) => {
return <Variant key={i}
variant={variant}
index={i}
changeVariantTitle={this.handleVariantTitleChange}
changeVariantValue={this.handleVariantValueChange}
/>
})}
</div>
)
}
}
export default ProductTest
Here is the child Variant component:
import React, { Component } from 'react'
class Variant extends Component {
componentWillReceiveProps(nextProps){
console.log(this.props, nextProps)
}
render() {
const { variant, index } = this.props
return (
<div className="variant-wrapper" data-new="new_value">
<div className="ajax-error" data-hbs-id="{{id}}"></div>
<div className="varient-item-titles">
<div className="variant-item-title">
Title
</div>
<div className="varient-attribute-titles">
<div className="variant-attribute">Value</div>
</div>
</div>
<div className="variant-item-content">
<div className="variant-label">
<input type="text" name="variant_label" value={variant.title} placeholder="Size, colour, etc."
onChange={e => this.props.changeVariantTitle(e, index)}
/>
</div>
</div>
</div>
)
}
}
export default Variant
Before
After
Console.log()
These two objects are this.props and nextProps. Notice the title property is not different, as I'd expect them to be.
SOLUTION!
The reason for all of those is that I was not correctly altering the parent's state immutably. I think I was just changing the value at the same memory address, and hence by the time it got to the componentWillReceiveProps it was already the new value. I think...
I found that the following will work:
handleVariantTitleChange(e, i){
const { value } = e.target
// Immutably clone specific variant item
let variant = Object.assign({}, this.state.data.variants[i])
// Set new title to item
variant.title = value
// Set the variant to the cloned item
let variants = this.state.data.variants
variants[i] = variant
this.setState({
data: {
...this.state.data,
variants
}
})
}
I can't for the life of me figure out why I'm getting error:
Maximum call stack size exceeded
When this code is run. If I comment out:
const tabs = this.getTabs(breakpoints, panels, selectedTab);
the error goes away. I have even commented out other setState() calls to try and narrow down where the problem was at.
Code (removed the extra functions):
export default class SearchTabs extends Component {
constructor() {
super();
this.state = {
filters: null,
filter: null,
isDropdownOpen: false,
selectedFilter: null,
};
this.getTabs = this.getTabs.bind(this);
this.tabChanged = this.tabChanged.bind(this);
this.setSelectedFilter = this.setSelectedFilter.bind(this);
this.closeDropdown = this.closeDropdown.bind(this);
this.openDropdown = this.openDropdown.bind(this);
}
componentDidMount() {
const { panels } = this.props;
if (!panels || !panels.members || panels.members.length === 0) {
this.props.fetchSearch();
}
}
getTabs(breakpoints, panels, selectedTab) {
const tabs = panels.member.map((panel, idx) => {
const { id: panelId, headline } = panel;
const url = getHeaderLogo(panel, 50);
const item = url ? <img src={url} alt={headline} /> : headline;
const classname = classNames([
searchResultsTheme.tabItem,
(idx === selectedTab) ? searchResultsTheme.active : null,
]);
this.setState({ filter: this.renderFilters(
panel,
breakpoints,
this.setSelectedFilter,
this.state.selectedFilter,
this.state.isDropdownOpen,
) || null });
return (
<TabItem
key={panelId}
classname={`${classname} search-tab`}
headline={headline}
idx={idx}
content={item}
onclick={this.tabChanged(idx, headline)}
/>
);
});
return tabs;
}
render() {
const { panels, selectedTab } = this.props;
if (!panels || panels.length === 0) return null;
const tabs = this.getTabs(breakpoints, panels, selectedTab);
return (
<div className={searchResultsTheme.filters}>
<ul className={`${searchResultsTheme.tabs} ft-search-tabs`}>{tabs}</ul>
<div className={searchResultsTheme.dropdown}>{this.state.filter}</div>
</div>
);
}
}
export const TabItem = ({ classname, content, onclick, key }) => (
<li key={key} className={`${classname} tab-item`} onClick={onclick} >{content}</li>
);
Because of this loop:
render -----> getTabs -----> setState -----
^ |
| |
|____________________________________________v
You are calling getTabs method from render, and doing setState inside that, setState will trigger re-rendering, again getTabs ..... Infinite loop.
Remove setState from getTabs method, it will work.
Another issue is here:
onclick={this.tabChanged(idx, headline)}
We need to assign a function to onClick event, we don't need to call it, but here you are calling that method, use this:
onclick={() => this.tabChanged(idx, headline)}