my code:
import React from "react"
export default class MenuItem extends React.Component {
constructor(props) {
super(props)
this.state = { value: this.props.value }
}
updateValue(newValue) {
let propertyName =
this.state.selectedItem.objType === "number" ? "value" : "strValue"
console.log(propertyName)
this.setState((prevState) => {
let objectToUpdate = prevState.currentObjects.find(
)
objectToUpdate[propertyName] = newValue
return { currentObjects: this.state.currentObjects }
})
}
data = [
{ itemId: 1 },
]
addTen() {
this.setState((prevState) => {
let newValue = prevState.value + 1
return {
value: newValue,
}
})
}
subTen() {
this.setState((prevState) => {
let newValue = prevState.value - 1
this.setState({ newValue })
return {
value: newValue,
}
})
}
render() {
let array = this.data.map((itemPrice, itemName) => {
return (<div>
<p>${this.props.itemPrice}</p>
<h2>{this.props.itemName}</h2>
<p>{this.props.itemDescription}</p>
<button onClick={() => this.addTen()}>Add to Cart</button>
<p> Item Count: {this.state.value}</p>
<button onClick={() => this.addTen()}>Add One</button>
<button onClick={() => this.subTen()}>Subtract One</button>
</div>
)
})
return (array)
}
}
The problem is that when I go to use the add or subtract button I get this
picture of item count: NaN
Basically, I am trying to get the button to add or subtract one count when the button is pushed but it is not doing that, instead, it is just showing up as NaN. Also, there is no count of 0 even in the beginning. I have to press the button to make even NaN pop up. I don't know why. any suggestions? I don't know how to fix it.
So a couple of changes needed to happen here. First off in your constructor you never gave value a default value therefore setState returned NaN when trying to add 1 to an undefined value. Constructor for reference:
constructor(props) {
super(props);
this.state = { value: 0 };
}
One other change is to your subTen() method. You called this.setState twice inside of hit here is the updated version:
subTen() {
this.setState((prevState) => {
let newValue = prevState.value - 1;
return {
value: newValue
};
});
}
Related
I have several React components getting parameters from other components. I am at the last level child, but for some reason, no matter how I declare the setState parameters, the account value state in the component doesn't seem to be updating, which is a basic feature of React. Also, if I try to initialize the state by passing in props from the parent component, the value becomes undefined, yet if I do the same thing in my render function, everything works normally. Here is the code
export class AccountValue extends React.Component<ILiquidiManagerProps, AccountState> {
public constructor(props:ILiquidiManagerProps){
super(props);
this.state = {
Value: this.props.value,
Subtotal: this.props.subtotal,
items: [],
Date: this.props.date,
Dates: this.props.dates,
}
}
private handleChange = e => {
let value = 0;
let input = e.target.textContent.replace(/[,.]/g, x => { return x == "," ? "." : ""; });
if (input && !isNaN(input)) {
value = parseFloat(input);
} else {
value = 0;
}
let newTotal = Math.round(value);
this.setState({
Value: newTotal
});
}
private keyPress = e => {
if(e.charCode == 13) {
e.preventDefault();
e.target.blur();
}
}
public async componentDidMount(){
this.setState({
Subtotal: this.props.subtotal,
Value: this.props.value,
});
}
public async componentDidUpdate(_prevProps, prevState){
if (this.state.Subtotal !== prevState.Subtotal){
this.props.getValue(this.state.Subtotal);
}
if (this.state.Value !== prevState.Value){
// this.setState({Value: this.props.value,})
let newSubtotal = this.state.Subtotal - prevState.Value + this.state.Value;
this.props.getValue(newSubtotal);
}
}
public render(): React.ReactElement<ILiquidiManagerProps> {
return(
<div contentEditable
className="bg-white text-right pr-2"
data-date={this.props.date}
data-label={this.props.label}
onBlur={this.handleChange}
onKeyPress={this.keyPress}>{this.props.value.toString().replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, ".")}</div>
);
}
}
Any idea what I am missing?
you need to use the spread operator ... to call the previous state value. For the example in:
public async componentDidMount(){
this.setState({
Subtotal: this.props.subtotal,
Value: this.props.value,
});
}
You need to call the previous state value every time you want to set a state like this:
this.setState({
...this.state,
Subtotal: this.props.subtotal,
Value: this.props.value,
});
I want to filter the data from my multiple states at one time. But I am getting the data of only second state.
I have two states and both states are getting seprate data from seprate apis. Now I want to filter the data from it. thank youI don't know what i m doing wrong so pls help me and look at my code.
searchFeatured = value => {
const filterFeatured = (
this.state.latestuploads || this.state.featuredspeakers
).filter(item => {
let featureLowercase = (item.name + " " + item.title).toLowerCase();
let searchTermLowercase = value.toLowerCase();
return featureLowercase.indexOf(searchTermLowercase) > -1;
});
this.setState({
featuredspeakers: filterFeatured,
latestuploads: filterFeatured
});
};
class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
featuredspeakers: [],
latestuploads: [],
};
}
componentDidMount() {
axios
.all([
axios.get(
'https://staging.islamicmedia.com.au/wp-json/islamic-media/v1/featured/speakers',
),
axios.get(
'https://staging.islamicmedia.com.au/wp-json/islamic-media/v1/featured/latest-uploads',
),
])
.then(responseArr => {
//this will be executed only when all requests are complete
this.setState({
featuredspeakers: responseArr[0].data,
latestuploads: responseArr[1].data,
loading: !this.state.loading,
});
});
}
Using the || (OR) statement will take the first value if not null/false or the second. What you should do is combine the arrays
You should try something like
[...this.state.latestuploads, ... this.state.featuredspeakers].filter(item=>{});
Ahmed, I couldn't get your code to work at all - searchFeatured is not called anywhere. But I have some thoughts, which I hope will help.
I see that you're setting featuredspeakers and latestuploads in componentDidMount. Those are large arrays with lots of data.
But then, in searchFeatured, you are completely overwriting the data that you downloaded and replacing it with search/filter results. Do you really intend to do that?
Also, as other people mentioned, your use of the || operator is just returning the first array, this.state.latestuploads, so only that array is filtered.
One suggestion that might help is to set up a very simple demo class which only does the filtering that you want. Don't use axios at all. Instead, set up the initial state with some mocked data - an array of just a few elements. Use that to fix the filter and search functionality the way that you want. Here's some demo code:
import React from 'react';
import { Button, View, Text } from 'react-native';
class App extends React.Component {
constructor(props) {
super(props);
this.searchFeatured = this.searchFeatured.bind(this);
this.customSearch = this.customSearch.bind(this);
this.state = {
loading: false,
featuredspeakers: [],
latestuploads: [],
};
}
searchFeatured = value => {
// overwrite featuredspeakers and latestuploads! Downloaded data is lost
this.setState({
featuredspeakers: this.customSearch(this.state.featuredspeakers, value),
latestuploads: this.customSearch(this.state.latestuploads, value),
});
};
customSearch = (items, value) => {
let searchTermLowercase = value.toLowerCase();
let result = items.filter(item => {
let featureLowercase = (item.name + " " + item.title).toLowerCase();
return featureLowercase.indexOf(searchTermLowercase) > -1;
});
return result;
}
handlePress(obj) {
let name = obj.name;
this.searchFeatured(name);
}
handleReset() {
this.setState({
featuredspeakers: [{ name: 'Buffy', title: 'Slayer' }, { name: 'Spike', title: 'Vampire' }, { name: 'Angel', title: 'Vampire' }],
latestuploads: [{ name: 'Sarah Michelle Gellar', 'title': 'Actress' }, { name: 'David Boreanaz', title: 'Actor' }],
loading: !this.state.loading,
});
}
componentDidMount() {
this.handleReset();
}
getList(arr) {
let output = [];
if (arr) {
arr.forEach((el, i) => {
output.push(<Text>{el.name}</Text>);
});
}
return output;
}
render() {
let slayerList = this.getList(this.state.featuredspeakers);
let actorList = this.getList(this.state.latestuploads);
return (
<View>
<Button title="Search results for Slayer"
onPress={this.handlePress.bind(this, {name: 'Slayer'})}></Button>
<Button title="Search results for Actor"
onPress={this.handlePress.bind(this, {name: 'Actor'})}></Button>
<Button title="Reset"
onPress={this.handleReset.bind(this)}></Button>
<Text>Found Slayers?</Text>
{slayerList}
<Text>Found Actors?</Text>
{actorList}
</View>
);
}
};
export default App;
You should apply your filter on the lists separately then. Sample code below =>
const searchFeatured = value => {
this.setState({
featuredspeakers: customSearch(this.state.featuredspeakers, value),
latestuploads: customSearch(this.state.latestuploads, value)
});
};
const customSearch = (items, value) => {
return items.filter(item => {
let featureLowercase = (item.name + " " + item.title).toLowerCase();
let searchTermLowercase = value.toLowerCase();
return featureLowercase.indexOf(searchTermLowercase) > -1;
});
}
I have a React component with multiple dynamic input fields with different data types. I want to save the input values in state (answers) like this:
{ [id]: value }
Example of possible data output:
[
{
'72ebbdc4-8001-4b53-aac0': 'John doe'
},
{
'dd3179c1-90bc-481c-a89e':
'5b6d2f55-8ed0-4f76-98e69'
},
{
'5acff3c7-02f8-4555-9232': 4
},
{
'877817a8-6890-464b-928e': false
},
{
'69e11e5a-613f-46ac-805d': []
},
{
'0bb9c2f3-eda7-4e96-90f6': [
'ad9d4c72-0972764cf9b71c42',
'da788b55-3b68-a9c669c0ec1a'
]
},
{
'e9c2196f-871f-25e6efb2551f': '2020-12-23'
},
];
My React component is as follows. The InputField is a switch based on the questions type. When an input changes updateState is called and this.state.answers is updated. All of the question need to be filled in before the users can navigate to the next screen -> this.state.answeredAllQuestions.
export default class EditComponent extends Component {
state = {
questions: [],
answers: [],
answeredAllQuestions: false
};
async componentDidMount() {
await this.fillQuestions();
}
// I think need a working alternative for this part
componentDidUpdate() {
if (!this.state.answeredAllQuestions) {
this.checkRequiredQuestions();
}
}
fillQuestions = async () => {
const {
response: { questions }
} = await getQuestions();
// Turn questions from api into answers -> [key]:value
const answers = questions.map(el => {
return { [el.uuid]: el.value };
});
this.setState({
questions,
answers
});
};
checkRequiredQuestions = async () => {
const { answers } = this.state;
if (answers) {
const values = answers.map(x => Object.values(x)[0]);
if (
values.every(answer => {
(answer.required && answer !== null) || answer !== '';
})
) {
this.setState({ answeredAllQuestions: true });
} else {
this.setState({ answeredAllQuestions: false });
}
}
};
updateState = (value, id, nestedId) => {
const { answers } = this.state;
if (answers) {
// Check if answer already exists in the state, if so then replace it
this.setState({
answers: this.state.answers.map(el =>
Object.keys(el)[0] === id ? { [id]: value } : el
)
});
} else {
this.setState({
answers: [{ [id]: value }]
});
}
};
render() {
const { questions, answers } = this.state;
return (
<View>
<FlatList
data={questions}
renderItem={({ item: question }) => (
<View key={question.id}>
<Text>{question.label}</Text>
<InputField
type={question.type}
answers={answers}
updateState={this.updateState}
question={question}
/>
</View>
)}
/>
</View>
);
}
}
The big problem I have with this code is that when all input fields are filled in, the this.state.answeredAllQuestions is set too true. But when the user then removes a value from an input field it won't update back to false.
I don't expect someone to fix my code, but I could really use some help at the moment.
if (values.every(answer =>
(answer.required && (answer !== null || answer !== '' )) || answer === ''))
If the answer is required you need to check if isn't an empty string.
Fixed by passing a HandleInput function to all the inputs components, that checks for every data type if true or false and puts this value in the state of the EditComponent.
I am new to react, I am trying to write a react component, component has several features.
user can input a random number, then number will be displayed in the
page too.
implement a button with text value 'start', once click the button,
the number value displayed will reduce one every 1second and the
text value will become 'stop'.
continue click button, minus one will stop and text value of button
will become back to 'start'.
when number subtract to 0 will automatically stop itself.
I have implemented the first and second feature. but when I try to click stop to stop number from reducing 1, it does not work.
I am wondering since I used type=true/false to indicate the state of type is start or stop. Because in the start state, number should automatically reduce 1. And on the stop state, reducing 1 should stop. So, the timer function should accurate according to the state of type.
Also I am not sure if I used clearInterval method right.
I really appreciate if someone could give me a hand.
code is here:
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [{ id: 1, number: "" }],
type: false
};
this.handleClick = this.handleClick.bind(this);
}
changeNumber = (e, target) => {
this.setState({
details: this.state.details.map(detail => {
if (detail.id === target.id) {
detail.number = e.target.value;
}
return detail;
})
});
};
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
var myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
} else if (this.state.type === true) {
clearInterval(myTimer);
}
};
render() {
return (
<div>
{this.state.details.map(detail => {
return (
<div key={detail.id}>
Number:{detail.number}
<input
type="number"
onChange={e => this.changeNumber(e, detail)}
value={detail.number}
/>
<input
type="button"
onClick={() => this.handleClick()}
value={this.state.type ? "stop" : "start"}
/>
</div>
);
})}
</div>
);
}
}
export default App;
You need to declare var myTimer outside of the handleClick() function.
So it's something like:
var myTimer;
...
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
} else if (this.state.type === true) {
clearInterval(myTimer);
}
};
I am building a React app that - among other things - generates a random number when a button is clicked and then filters an array of JSON objects to only the one at the index of that random number (i.e. JSON[random]). Normally the app is supposed to re-render after the array of JSON objects is filtered, but for some reason, on the first time the button is clicked and a random is picked, it requires two clicks to update. From then on it updates as expected, with a new random rendering each time the button is clicked.
I'm not sure if the problem is coming from App.js or somewhere lower down. On the first click, it generates a new random and supposedly saves this to state, but fails to re-render right away. On subsequent clicks, things seem to update based on the previously-generated random, while a new random is put in the queue. I would prefer the this all happens in one go: click, generate random, save to state, update to reflect the new random à la JSON[random].
This might have something to do with the way I have implemented lifecycle methods, as I'm admittedly not sure of all the nuances of each and have just tried to use whichever ones seemed to do what I wanted. If you have any suggestions there, please let me know...
Thanks!
Here are the relevant files:
App.js - where the random is generated and stored when a new click is registered in Header.state.randomClicks
class App extends Component {
constructor(props){
super(props)
this.state = {headerLink: "", searchValue: "", random: 0, randomClicks: 0}
this.generateRandom = this.generateRandom.bind(this);
}
getLinkFromHeader = (link) => {
if (this.state.headerLink !== link) {
this.setState({
headerLink: link,
})
}
}
getSearchValueFromHeader = (string) => {
this.setState({
searchValue: string,
});
}
getRandomMax = (max) => {
this.setState({
randomMax: max,
})
}
getRandomClicks = (value) => {
this.setState({
randomClicks: value,
})
}
generateRandom(number) {
let random = Math.floor(Math.random() * number) + 1;
console.log("generateRandom = ", random)
return random
}
shouldComponentUpdate(nextProps, nextState) {
return this.state.randomClicks !== nextState.randomClicks;
}
componentWillUpdate() {}
componentDidUpdate(prevState) {
let randomClicks = this.state.randomClicks;
console.log("this.state.randomClicks: ", this.state.randomClicks)
// console.log("prevState: ", prevState)
// console.log("prevState.randomClicks = ", prevState.randomClicks)
// ^^ is this a bug ? ^^
let random = this.generateRandom(this.state.randomMax);
if (this.state.random !== random) {
this.setState({random: random})
}
}
render() {
return (
<div className="App background">
<div className="content">
<Header getLinkFromHeader={this.getLinkFromHeader} getSearchValueFromHeader={this.getSearchValueFromHeader} randomClick={this.randomClick} getRandomClicks={this.getRandomClicks}/>
<TilesContainer link={this.state.headerLink} searchValue={this.state.searchValue} getRandomMax={this.getRandomMax} random={this.state.random} randomClicks={this.state.randomClicks}/>
</div>
</div>
);
}
}
export default App
Header.js* - where the randomClick count is incremented each time RandomButton is clicked
class Header extends Component {
constructor(props){
super(props);
this.state = { selectorLink: "", searchValue: "", randomClicks: 0 }
this.randomClick = this.randomClick.bind(this);
}
getLinkFromSelector = (link) => {
this.setState({
selectorLink: link,
})
}
getSearchValue = (string) => {
this.setState({
searchValue: string,
})
}
shouldComponentUpdate(nextProps, nextState) {
console.log("this.state !== nextState: ", this.state !== nextState)
return this.state !== nextState;
}
componentDidUpdate(previousState){
if(this.state.selectorLink !== previousState.selectorLink) {
this.props.getLinkFromHeader(this.state.selectorLink);
}
this.props.getSearchValueFromHeader(this.state.searchValue);
this.props.getRandomClicks(this.state.randomClicks);
console.log("Header Did Update")
}
randomClick(){
this.props.randomClick;
this.setState({
randomClicks: this.state.randomClicks += 1,
});
}
render(){
return(
<div id="header" className="header">
<div className="title-div">
<div className="h1-wrapper title-wrapper">
<h1>Pokédex Viewer App</h1>
</div>
</div>
<PokedexSelector getLinkFromSelector={this.getLinkFromSelector}/>
<SearchBar getSearchValue={this.getSearchValue}/>
<button type="button" id="random-button" onClick={this.randomClick}>Random Pokémon</button>
<button type="button" id="show-all-button" onClick={this.showAllClick}>Show All</button>
</div>
)
}
}
export default Header
TilesContainer.js - where the random number from App is sent and the tiles list is filtered/re-rendered
class TilesContainer extends Component {
constructor(props){
super(props);
this.state = {
pokemon: [],
filteredPokemon: [],
randomMax: 0,
showDetails: false,
};
this.getPokemon = this.getPokemon.bind(this);
this.tiles = this.tiles.bind(this);
this.getPokemon(this.props.link);
}
getPokemon(pokedexLink) {
let link = "";
(pokedexLink === "")
? link = "https://pokeapi.co/api/v2/pokedex/national/"
: link = this.props.link;
fetch(link)
.then(response => response.json())
.then(myJson => {
let list = myJson['pokemon_entries'];
this.setState({
pokemon: list,
randomMax: list.length,
})
this.props.getRandomMax; // send randomMax to App
})
}
filterPokemon(string) {
if (string !== "") {
console.log("string: ", string)
string = string.toString().toLowerCase()
let filteredPokemon = this.state.pokemon.filter(pokemon => {
const name = pokemon.pokemon_species.name;
const nameStr = name.slice(0,string.length);
const number = pokemon.entry_number;
const numberStr = number.toString().slice(0, string.length);
return (this.state.random !== 0) ? number.toString() === string : nameStr === string || numberStr === string;
})
if (this.props.randomClicks !== 0) { // i.e. using a random
this.setState({
filteredPokemon: filteredPokemon,
})
} else {
this.setState({
filteredPokemon: filteredPokemon,
randomMax: filteredPokemon.length,
})
}
} else {
this.setState({
filteredPokemon: [],
randomMax: this.state.pokemon.length,
})
}
}
componentDidUpdate(prevProps, prevState) {
if (this.props.link !== prevProps.link) {
this.getPokemon(this.props.link)
}
if (this.props.searchValue !== prevProps.searchValue) {
this.filterPokemon(this.props.searchValue)
}
if (this.state.randomMax !== prevState.randomMax){
this.props.getRandomMax(this.state.randomMax);
}
if (this.props.random !== prevProps.random) {
console.log("TilesContainer random: ", this.props.random)
this.filterPokemon(this.props.random)
}
}
tiles() {
console.log("tiles() filteredPokemon: ", this.state.filteredPokemon)
console.log("tiles() searchValue: ", this.props.searchValue)
console.log("tiles() random: ", this.props.random)
if (this.state.pokemon.length > 0) {
if (this.state.filteredPokemon.length == 0 && this.props.searchValue === ""){
return (
this.state.pokemon.map(pokemon => (
<Tile key={pokemon.entry_number} number={pokemon.entry_number} name={pokemon.pokemon_species.name} url={pokemon.pokemon_species.url}/>
))
)
} else if (this.state.filteredPokemon.length > 0){
return (
this.state.filteredPokemon.map(pokemon => (
<Tile key={pokemon.entry_number} number={pokemon.entry_number} name={pokemon.pokemon_species.name} url={pokemon.pokemon_species.url}/>
))
)
}
}
}
render(){
return (
<div id="tiles-container"
className="tiles-container">
{this.tiles()}
</div>
)
}
}
export default TilesContainer
You should not use current state in setState and should not modify state directly. And you do no actually call this.props.randomClick and it is undefined. Change
randomClick(){
this.props.randomClick;
this.setState({
randomClicks: this.state.randomClicks += 1,
});
}
to
randomClick(){
if (typeof(this.props.randomClick) === 'function') this.props.randomClick();
this.setState(olState => ({
randomClicks: olState.randomClicks + 1,
}));
}
Also check your shouldComponentUpdate methods. They might be buggy or redundant. Looks like you prevent updating App when state.random changes. So every time you click the button you store the new random value but use the previous one. So for the initial render and for the first click you use random: 0.
And I guess that getRandomClicks should be setRandomClicks.