Why isn't the map function iterating over the array? - javascript

I am using React, which I am new to and it says to use the map method to iterate over the array. When I do that the entire array logs to the console and not the individual words I want.
I am trying to call stock symbols when a user types them in the text box. But under the function getSymbol() the state of symbol is still an array even when I used map in render. Can anyone point me in the right direction? Or what can I do to get single elements.
Here is my code:
class Symbol extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: '',
symbol: [],
isLoaded: false
}
}
typeSymbol = (e) => {
this.setState({
userInput: e.target.value.toUpperCase()
}, () => {
console.log(this.state.userInput)
})
}
getSymbol = (e) => {
e.preventDefault(),
this.setState({
symbol: this.state.symbol
}, () => {
console.log(this.state.symbol)
})
}
componentDidMount() {
fetch(`https://finnhub.io/api/v1/stock/symbol?exchange=US&token=${key}`)
.then(res => res.json())
.then(
(results) => {
this.setState({
isLoaded: true,
symbol: results
});
// console.log(this.state.symbol)
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { symbol, userInput } = this.state
if (userInput === symbol) {
return (
symbol.map((symbol, i) => (<span className="symbol" key={i}>{symbol.displaySymbol}</span>)),
console.log('same')
)
} else {
return (
<div className="enterstock">
<h1 className="title">Enter Stock Symbol</h1>
<form className="inputfields" onSubmit={this.getSymbol}>
<input type="text" className="symfields" name="symbolname" onChange={this.typeSymbol}></input>
<button type="submit" className="btn">Send</button>
</form>
</div >
)
}
}
}

You're using symbol in two different ways, and they're colliding. Try renaming the parameter of your map() function:
symbol.map((s, i) => (<span className="symbol" key={i}>{s.displaySymbol}</span>)),
symbol is not a reserved word in JavaScript, so you should be good there.

The problem is with your if block. The map method works fine. What is happening is you are comparing the userInput(String) to symbol(an array) it will not work. I don't know what check you're trying to do. But if it is to check whether the userInput is in the array you're doing it wrongly.
import React from "react";
class Symbol extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: "",
symbol: [],
isFiltered: false,
isLoaded: false,
};
}
typeSymbol = (e) => {
this.setState(
{
userInput: e.target.value.toUpperCase(),
},
() => {
console.log(this.state.userInput);
}
);
};
getSymbol = (e) => {
e.preventDefault();
const filter = this.state.symbol.filter(
(el) => el.displaySymbol === this.state.userInput
);
// console.log(filter);
this.setState(
{
symbol: filter,
isFiltered: true,
},
() => {
console.log(this.state.symbol);
}
);
};
componentDidMount() {
fetch(`https://finnhub.io/api/v1/stock/symbolexchange=US&token=${key}`)
.then((res) => res.json())
.then(
(results) => {
this.setState({
isLoaded: true,
symbol: results,
});
console.log(this.state.symbol);
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
}
render() {
const { symbol, userInput } = this.state;
//console.log(userInput);
if (this.state.isFiltered) {
return symbol.map((symbol, i) => {
return (
<span className="symbol" key={i}>
{symbol.displaySymbol}
</span>
);
});
} else {
return (
<div className="enterstock">
<h1 className="title">Enter Stock Symbol</h1>
<form className="inputfields" onSubmit={this.getSymbol}>
<input
type="text"
className="symfields"
name="symbolname"
onChange={this.typeSymbol}
></input>
<button type="submit" className="btn">
Send
</button>
</form>
</div>
);
}
}
}
export default Symbol;

Related

Set initial class variable from axios request in React

When i call this function
getQuestions = () => {
this.setState({ loading: true })
const { data } = this.props
axios
.get(data.questions)
.then((res) => {
this.setState({
loading: false,
questions: res.data,
})
this.initialQuestions = res.data
})
.catch((err) =>
this.setState({
loading: false,
questions: [],
})
)
}
it updates the array questions in state and array initialQuestions variable in constructor. The state questions represents the values form inputs. The inputs are handled in child component with this code
onChange = (e) => {
const { hasChanged, setQuestions } = this.props
// Update questions
let questions = this.props.questions
questions[e.target.getAttribute('data-id')][e.target.name] =
e.target.value
setQuestions(questions)
}
setQuestions is passed in props as setQuestions={(state) => this.setState({ questions: state })}
So when i change the value of inputs the onChange function is called and it changes the parent component questions in state. But the parent variable this.initialQuestions also is being changed to the questions value from state, but I don't know why
Edit:
That's the code you should be able to run
const { Component } = React;
const Textarea = "textarea";
const objectsEquals = (obj1, obj2) =>
Object.keys(obj1).length === Object.keys(obj2).length &&
Object.keys(obj1).every((p) => obj1[p] === obj2[p])
class QuestionList extends React.Component {
static propTypes = {
questions: PropTypes.array,
removeQuestion: PropTypes.func.isRequired,
hasChanged: PropTypes.func.isRequired,
setQuestions: PropTypes.func.isRequired,
}
constructor(props) {
super(props)
this.questions = props.questions
this.onChange = this.onChange.bind(this)
}
onChange = (e) => {
const { hasChanged, setQuestions } = this.props
// Update questions
let questions = this.props.questions
questions[e.target.getAttribute('data-id')][e.target.name] =
e.target.value
setQuestions(questions)
if (hasChanged && this.questions.length > 0) {
// array of booleans, true if object has change otherwise false
const hasChangedArray = this.props.questions.map(
(_, index) =>
!objectsEquals(
this.questions[index],
this.props.questions[index]
)
)
console.log("hasChangedArray = ", hasChangedArray)
console.log("this.questions[0] = ", this.questions[0])
console.log("this.props.questions[0] = ", this.props.questions[0])
// If true in array than the form has changed
hasChanged(
hasChangedArray.some((hasChanged) => hasChanged === true)
)
}
}
render() {
const { removeQuestion, questions } = this.props
const questionList = questions.map((question, index) => (
<div className="card" key={index}>
<div className="card__body">
<div className="row">
<div className="col-sm-7">
<div className="form-control">
<label className="form-control__label">
Question:
</label>
<input
type="text"
id={`question-${index}`}
data-id={index}
onChange={this.onChange}
name="question"
value={
this.props.questions[index].question
}
className="form-control__input form control__textarea"
placeholder="Pass the question..."
rows="3"
/>
</div>
<div className="form-control">
<label className="form-control__label">
Summery:
</label>
<Textarea
id={`summery-${index}`}
data-id={index}
onChange={this.onChange}
name="summery"
value={this.props.questions[index].summery}
className="form-control__input form-control__textarea"
placeholder="Pass the summery..."
rows="3"
/>
</div>
</div>
</div>
</div>
</div>
))
return questionList
}
}
class Questions extends React.Component {
constructor(props) {
super(props)
this.initialQuestions = []
this.state = {
loading: true,
questions: [],
hasChanged: false,
}
this.getQuestions = this.getQuestions.bind(this)
this.resetForm = this.resetForm.bind(this)
}
resetForm = () => {
console.log("this.initialQuestions =", this.initialQuestions)
this.setState({
questions: this.initialQuestions,
hasChanged: false,
})
}
getQuestions = () => {
this.setState({ loading: true })
const { data } = this.props
// axios
// .get(data.questions)
// .then((res) => {
// this.setState({
// loading: false,
// questions: res.data,
// })
// this.initialQuestions = res.data
// })
// .catch((err) =>
// this.setState({
// loading: false,
// questions: [],
// })
// )
// You can't do a database request so here is some example code
this.setState({
loading: false,
questions: [
{
question: 'example-question',
summery: 'example-summery',
},
{
question: 'example-question-2',
summery: 'example-summery-2',
},
],
})
this.initialQuestions = [
{
question: 'example-question',
summery: 'example-summery',
},
{
question: 'example-question-2',
summery: 'example-summery-2',
},
]
}
componentDidMount = () => this.getQuestions()
render() {
const { loading, questions, hasChanged } = this.state
if (loading) return <h1>Loading...</h1>
return (
<form>
<QuestionList
questions={questions}
hasChanged={(state) =>
this.setState({ hasChanged: state })
}
setQuestions={(state) =>
this.setState({ questions: state })
}
/>
<button
type="reset"
onClick={this.resetForm}
className={`btn ${
!hasChanged
? 'btn__disabled'
: ''
}`}
>
Cancel
</button>
<button
type="submit"
className={`btn btn__contrast ${
!hasChanged
? 'btn__disabled'
: ''
}`}
>
Save
</button>
</form>
)
}
}
ReactDOM.render(<Questions />, document.querySelector("#root"));
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/prop-types#15/prop-types.min.js"></script>
<div id="root"></div>
Both state questions and class variable initialQuestions hold reference of res.data. Now when you update questions in onChange method, you are updating it by reference i.e directly mutating it and hence the class variable is also updated
You must not update it by reference but clone and update like below
onChange = (e) => {
const { hasChanged, setQuestions } = this.props
// Update questions
let questions = this.props.questions
questions = questions.map((question, idx) => {
if(idx === e.target.getAttribute('data-id')) {
return {
...question,
[e.target.name]: e.target.value
}
}
return question;
});
setQuestions(questions)
}

My forEach statement is repeating more than it should

I am using firebase and react js. my forEach loop is only supposed to repeat for the number of objects I have stored in the firebase database but instead of repeated the expected 3 times it will repeat 8 or 9 or something like that. Also when I run the code a second time it will repeat super fast and fill up my console.
constructor() {
super()
this.state = {
warmupList: [],
title: "",
id: ""
}
}
handleInputChange = (e) => {
this.setState({
title: e.target.value
})
}
SetActiveWarmup(id) {
const warmupRef = firebase.database().ref("Warmups")
warmupRef.on("value",(snapshot) => {
const warmups = snapshot.val()
this is where the for each is
Object.keys(warmups).forEach(key => {
if(id === key) {
warmupRef.child(key).update({
activity: "True"
})
} else {
warmupRef.child(key).update({
activity: "False"
})
}
console.log("has run")
})
})
}
createWarmup = (e) => {
e.preventDefault()
if(this.state.title === "") return
const warmupRef = firebase.database().ref("Warmups")
var key = warmupRef.push().key
warmupRef.child(key).set({
title: this.state.title,
activity: "True",
id: key
})
this.setState({
title: ""
})
}
componentWillMount() {
this._isMounted = true;
const warmupRef = firebase.database().ref("Warmups")
warmupRef.on("value",(snapshot) => {
const warmups = snapshot.val()
const warmupList = []
for(let id in warmups) {
warmupList.push(warmups[id])
}
this.setState({
warmupList: warmupList
})
})
}
render() {
return (
<div className={`warmup${this.props.activity}`}>
<div className="newWarmup__bottom">
<form onSubmit={this.createWarmup}>
<input placeholder="My Warmup..."
value={this.state.title}
onChange={this.handleInputChange} />
<CircleButton click={this.createWarmup}
Icon={AddIcon} color="Green" />
</form>
</div>
<div className="warmup__select">
{this.state.warmupList.map(warmup =>
<SelectWarmup key={v4()}
text={warmup.title}
activity={warmup.activity}
click={() => this.SetActiveWarmup(warmup.id)} />
)}
</div>
</div>
)
}
}

How do I stop from getting one of my this.state properties to stop getting undefined?

I am new to react and I am trying to develop a simple app that calls stock data when the users inputs a certain symbol and then pushes click. The information shows up when I console.log(data.symbol) under the fetch method but when I try to use it on the handleSubmit function, it tells me symbol is not defined when it under this.state.symbol in my constructor method. I have tried to look up similar examples to my problems but it doesn't resolve the issue. Can someone point me in the right direction?
class Symbol extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
symbol: [],
isLoaded: false,
};
}
getSymbol = (e) => { //use arrow function to get value of this
this.setState({ // change state of state
value: e.target.value.toUpperCase()
});
console.log(this.state.value)
}
handleSubmit = (e) => {
e.preventDefault(),
this.setState({
value: symbol
})
console.log(symbol)
this.handleSubmit = this.handleSubmit.bind(this)
}
componentDidMount() { //run after the render method
fetch(`https://finnhub.io/api/v1/stock/symbol?exchange=US&token=${key}`)
.then(res => res.json())
.then((data) => {
this.setState({
isLoaded: true,
symbol: data.map(data => {
data.symbol
})
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { symbol, data, value } = this.state
if (value === symbol) {
return console.log('same')
}
return (
<div className="enterstock">
<h1 className="title">Enter A Stock Symbol</h1>
<span className="symbol">{"(" + " " + this.state.value + " " + ")"}</span>
<form onSubmit={this.handleSubmit} className="inputfields">
<input type="text" name="sym" className="sym" onChange={this.getSymbol}></input>
<button type="submit" className="btn">Send</button>
</form>
</div>
)
}
}
ReactDOM.render(
<Symbol />,
document.getElementById('root')
)
There are multiple things that are wrong with your component here
You don't need to bind this to an arrow function
Where is symbol coming from inside handleSubmit?
Where is the error coming from after .then() inside componentDidmount
as SetState works Asynchronously, you won't find this.state.value right on the next line of a setState use a callback to setState like this
this.setState({value: e.target.value.toUpperCase()}, () => console.log(this.state.value))
Here is the updated code, which should work for you:
class Symbol extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
symbol: [],
isLoaded: false,
};
}
getSymbol = (e) => {
//use arrow function to get value of this
this.setState({ value: e.target.value.toUpperCase() },
() => console.log(this.state.value)
);
};
handleSubmit = (e) => {
e.preventDefault(),
this.setState({
value: this.state.symbol,
});
};
componentDidMount() {
//run after the render method
fetch(`https://finnhub.io/api/v1/stock/symbol?exchange=US&token=${key}`)
.then((res) => res.json())
.then((data) => {
this.setState({
isLoaded: true,
symbol: data.map((item) => item.symbol),
});
})
.catch((error) =>
this.setState({ isLoaded: true, error, })
);
}
render() {
const { symbol, data, value } = this.state;
if (value === symbol) {
return console.log("same");
}
return (
<div className="enterstock">
<h1 className="title">Enter A Stock Symbol</h1>
<span className="symbol">
{"(" + " " + this.state.value + " " + ")"}
</span>
<form onSubmit={this.handleSubmit} className="inputfields">
<input
type="text"
name="sym"
className="sym"
onChange={this.getSymbol}
></input>
<button type="submit" className="btn">
Send
</button>
</form>
</div>
);
}
}
ReactDOM.render(<Symbol />, document.getElementById("root"));

Cannot delete input content in Typeahead react-bootstrap

Trying to delete the contents of the input field by pressing backspace. However, nothing can be deleted. When I delete onChange = {this.handleSelectContractor} the field can be cleared, but after adding onChange = {this.handleSelectContractor} nothing can be done.
Pass selected todo to the handleSelect and assign it to the variable selected. The variable selected should appear in the input field
Demo here: https://stackblitz.com/edit/react-agfvwn?file=index.js
class App extends Component {
constructor() {
super();
this.state = {
todos: [],
selected: [],
todo: {},
todo2: 'ddd'
};
}
componentDidMount() {
this.getTodos().then(() => {
const todo = this.state.todos.find(todo => todo.id === 4);
this.setState({
selected: todo ? [todo] : []
})
})
}
getTodos = () => {
return axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: "GET"
})
.then(res => {
this.setState({
todos: res.data
});
})
.catch(error => {
console.log(error);
})
}
handleSelect = (todo) => {
let newArray = [...this.state.selected];
newArray.push(todo)
if(todo) {
this.setState({
todo: newArray
})
} else {
this.setState({
todo2: ''
})
}
}
render() {
console.log(this.state.todo)
return (
<div>
<Typeahead
id={'sasas'}
selected={this.state.selected}
labelKey="title"
onChange={this.handleSelect}
options={this.state.todos}
ref={(ref) => this._typeahead = ref}
/>
</div>
);
}

How to show information from API when using search box in ReactJS?

I'm using the Star Wars API to build a React JS project. The aim of my app is to be able to search for characters.
Here is my code for the search component in the my app.
At the moment I'm able to retrieve data the API and show the information on the page but I can't work out how to show this information when it's searched for.
Any ideas?
import React, { Component } from 'react';
class Search extends Component {
constructor(props){
super(props)
this.state = {
query:'',
peoples: [],
}
}
onChange (e){
this.setState({
query: e.target.value
})
if(this.state.query && this.state.query.length > 1) {
if(this.state.query.length % 2 === 0){
this.componentDidMount()
}
}
}
componentDidMount(){
const url = "https://swapi.co/api/people/";
fetch (url,{
method:'GET'
}).then(results => {
return results.json();
}).then(data => {
let peoples = data.results.map((people) => {
return(
<ul key={people.name}>
<li>{people.name}</li>
</ul>
)
})
this.setState({peoples: peoples});
console.log("state", peoples)
})
}
render() {
return (
<form>
<input
type="text"
className="search-box"
placeholder="Search for..."
onChange={this.onChange.bind(this)}
/>
{this.state.peoples}
</form>
)
}
}
export default Search
You could put your fetch in a separate function instead of in componentDidMount and call that when the component mounts and when your query changes.
Since you might be creating multiple requests if the user types quickly, you could use a debounce to only send one request, or use something that verifies that you always use the result of the latest request, like e.g. a token.
Example
class Search extends Component {
token = null;
state = {
query: "",
people: []
};
onChange = e => {
const { value } = e.target;
this.setState({
query: value
});
this.search(value);
};
search = query => {
const url = `https://swapi.co/api/people?search=${query}`;
const token = {};
this.token = token;
fetch(url)
.then(results => results.json())
.then(data => {
if (this.token === token) {
this.setState({ people: data.results });
}
});
};
componentDidMount() {
this.search("");
}
render() {
return (
<form>
<input
type="text"
className="search-box"
placeholder="Search for..."
onChange={this.onChange}
/>
{this.state.people.map(person => (
<ul key={person.name}>
<li>{person.name}</li>
</ul>
))}
</form>
);
}
}
You have to define it in diff function to manage easy.
import React, { Component } from 'react';
class Search extends Component {
constructor(props) {
super(props)
this.state = {
query: null,
peoples: [],
}
}
componentDidMount() {
this.serachPeople(this.state.query);
}
onChange(e) {
this.setState({ query: e.target.value }, () => {
if (this.state.query && this.state.query.length > 1) {
if (this.state.query.length % 2 === 0) {
this.serachPeople(this.state.query);
}
} else {
this.serachPeople(this.state.query);
}
})
}
serachPeople(query) {
const url = "https://swapi.co/api/people/";
if (query) {
// if get value ion query so filter the data based on the query.
fetch(url, {
method: 'GET'
}).then(results => {
return results.json();
}).then(data => {
let peoples = data.results.filter(people => people.name === query).map((people) => {
return (
<ul key={people.name}>
<li>{people.name}</li>
</ul>
)
})
this.setState({ peoples: peoples });
console.log("state", peoples)
})
} else {
fetch(url, {
method: 'GET'
}).then(results => {
return results.json();
}).then(data => {
let peoples = data.results.map((people) => {
return (
<ul key={people.name}>
<li>{people.name}</li>
</ul>
)
})
this.setState({ peoples: peoples });
console.log("state", peoples)
})
}
}
render() {
return (
<form>
<input
type="text"
className="search-box"
placeholder="Search for..."
onChange={this.onChange.bind(this)}
/>
{this.state.peoples}
</form>
)
}
}
export default Search;
I hope this will help for u. Let me know if u have any query.

Categories

Resources