I have two components, CryptoPrice with a coin prop which calls an API to get the price, and Nav where I search for a coin, and it renders the CryptoPrice component assigning the onSubmit value to CryptoPrice coin prop.
The display works good until I do a second onSubmit from the Nav. When I do a second onSubmit, nothing changes.
App.js code:
import CryptoPrice from "./components/CryptoPrice";
import Nav from "./components/Nav";
function App() {
return (
<div className="App">
<header className="App-header">
<h1>Crypto Prices</h1>
<div className="flex">
<CryptoPrice coin="bitcoin" />
<CryptoPrice coin="ethereum" />
</div>
<div>
<Nav></Nav>
</div>
</header>
</div>
);
}
CryptoPrice component:
import styles from "./css/CryptoPrice.module.css";
export default class CryptoPrice extends React.Component {
constructor(props) {
super(props);
this.state = {
price: [],
url: `https://api.coingecko.com/api/v3/simple/price?ids=${this.props.coin}&vs_currencies=usd`,
};
}
componentDidMount = () => {
this.loadData();
setInterval(this.loadData, 20000);
};
loadData = () => {
fetch(this.state.url)
.then((response) => response.json())
.then((data) => {
let key = Object.keys(data);
return data[key];
})
.then((coin) => {
let price = coin.usd;
this.setState({ price });
});
};
render() {
return (
<div className={styles.padding}>
<h2>{this.props.coin} price</h2>
<div>{this.state.price}$</div>
</div>
);
}
}
Nav Component
import CryptoPrice from "./CryptoPrice";
export default class Nav extends React.Component {
constructor(props) {
super(props);
this.state = {
coin: "",
isSubmitted: false,
};
}
componentDidMount() {
this.setState({ isSubmitted: false });
}
render() {
return (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
this.setState({ isSubmitted: true });
}}
>
<input
type="text"
onChange={(e) => {
this.setState({ coin: e.target.value });
}}
></input>
<input type="submit" value="Add"></input>
</form>
{this.state.isSubmitted && <CryptoPrice coin={this.state.coin} />}
</div>
);
}
}
Thanks so much for any help/feedback
Your issue is because you are setting the url in state so it will not update when the props update. Try changing you fetch function to use props directly(also remember to clear the setInterval when you unmount):
loadData = () => {
fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${this.props.coin}&vs_currencies=usd`)
.then((response) => response.json())
.then((data) => {
let key = Object.keys(data);
return data[key];
})
.then((coin) => {
let price = coin.usd;
this.setState({ price });
});
};
Related
I have a "Banner" object that I want to use to display my text and I made a button to toggle whether the message shows or is hidden:
function App() {
function Banner(props) {
if (!props.warn) {
return null;
}
return (
<div className="message">
{this.state.message}
</div>
);
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showMessage: false, message: 'foo'};
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(state => ({
showMessage: !state.showMessage
}));
}
componentDidMount() {
fetch(
"https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((json) => {
this.setState({
message: json,
DataisLoaded: true
});
})
}
render() {
return (
<div class="App">
<Banner warn={this.state.showMessage} />
<button onClick={this.handleToggleClick}>
{this.state.showMessage ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
return (
<div className="App">
<Page />
</div>
);
}
export default App;
Why is the text of the message variable not the text in the banner? If I replace the {this.state.message} with a string literal the banner displays it, but as the code is now when I press the button the page clears. Any ideas?
You should pass data to Banner and with useEffect and useState get and update the data when it changes
const { useEffect, useState } = React;
function Banner(props) {
const [message, setMessage] = useState('');
useEffect(() => {
setMessage(props.message);
}, [props]);
if (!props.warn) {
return null;
}
return <div className="message">{JSON.stringify(message)}</div>;
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {
showMessage: false,
message: 'foo',
};
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState((state) => ({
showMessage: !state.showMessage,
}));
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then((res) => res.json())
.then((json) => {
this.setState({
message: json,
DataisLoaded: true,
});
});
}
render() {
return (
<div class="App">
<Banner message={this.state.message} warn={this.state.showMessage} />
<button onClick={this.handleToggleClick}>
{' '}
{this.state.showMessage ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
function App() {
return (
<div className="App">
<Page />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I'm trying to create a Github search for user repos and display them in cards. My ListComponent looks good when displayed, but I'm having trouble mapping the repo info to each card. When I console.log the data it shows up as expected, but will not map to the cards. I'm using fetch for the api calls and lodash for mapping. Any help would be appreciated!
GithubSearch:
import React, { Component, Fragment } from 'react';
import ListComponent from './ListComponent';
import _ from 'lodash';
class GithubSearch extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
repos: [],
apiMsg:'',
userInfo: {}
}
};
handleChange = (e) => {
this.setState({ username: e.target.value });
setTimeout(this.handleSubmit, 1000)
}
handleSubmit = () => {
fetch(`https://api.github.com/users/${this.state.username}/repos`)
.then(res => res.json())
.then(data => {
console.log(data)
})
.catch(err => {
this.setState({
repos: [],
apiMsg: err.message
})
})
}
render() {
return (
<Fragment>
<div className='header'>Github Search</div>
<form className='search'>
<input
placeholder='Github user'
name='github user'
type='text'
onChange={this.handleChange}
value={this.state.username}
/>
<button type='submit' onSubmit={this.handleSubmit}>Search</button>
</form>
<p>{this.state.apiMsg}</p>
<div className='right-container'>
{_.map(this.state.repos, repo => <ListComponent key={repo.id} {...repo}/>)}
</div>
</Fragment>
)
}
}
export default GithubSearch;
ListComponent:
import React from 'react'
import '../App.css'
const ListComponent = ({ name, description, language, html_url }) => {
return (
<div className='card'>
<div className='card-body'>
<h2>{name}</h2>
</div>
<div className='card-body'>
<p>{description}</p>
</div>
<div className='card-body'>
<p>Languages: {language}</p>
</div>
</div>
);
}
export default ListComponent;
You have forgotten to set state for repo after API response , you try below code
handleSubmit = () => {
fetch(`https://api.github.com/users/${this.state.username}/repos`)
.then(res => res.json())
.then(data => {
console.log(data)
this.setState({repo:data})
})
.catch(err => {
this.setState({
repos: [],
apiMsg: err.message
})
})
}
there is one more error TypeError: Cannot destructure property 'handleShow' of 'object null' as it is null.
output when i consoled log is
pr
SearchModal.js:35 {username: "pr"}
SearchModal.js:38 [{…}]0: {id: "602df77cea2b563d7ceda4ac", username: "pratik", email: "pratik#gmail.com"}length: 1__proto_: Array(0)
also it is not searching when i type p its giving searc:'' and when i add prat then search : 'pra' only
Also it is not rendering username just check userdetails.map it is console logging the details but not rendering on page
import React, { Component } from 'react';
import { SearchUser } from '../services/SearchService';
import {Modal} from 'react-bootstrap';
class SearchModal extends Component {
constructor(props){
super(props);
this.state = {
show: false,
search: '',
userdetails:[]
}
this.handleShow = this.handleShow.bind(this);
this.handleClose = this.handleClose.bind(this);
this.onTextboxChangeSearch = this.onTextboxChangeSearch.bind(this);
}
handleShow() {
this.setState({ show: true })
}
handleClose(){
this.setState({ show: false })
}
async onTextboxChangeSearch(event) {
this.setState({
search: event.target.value
});
let {search,userdetails} = this.state;
console.log(search)
const data = {username:search};
console.log(data)
let SearchStatus = await SearchUser(data);
userdetails=SearchStatus.user
console.log(userdetails);
}
render() {
let {search,userdetails}= this.state;
return (
<div>
<Modal show={this.state.show} onHide={this.handleClose}>
<Modal.Header closeButton>
<Modal.Title>
<input
type="text"
placeholder="Search.."
value={search}
onChange={this.onTextboxChangeSearch}
></input>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<h3>Users</h3>
<div>
<ul>
{userdetails.map(element => {
<li>{element.username}</li>
})}
</ul>
</div>
</Modal.Body>
</Modal>
</div>
)
}
}
export default SearchModal;
Dashboard
import React, { Component } from 'react';
import { Link,Redirect } from 'react-router-dom';
import UserService from "../services/userservice";
import SearchModal from './SearchModal'
export default class Dashboard extends Component{
constructor(props) {
super(props);
this.state = {
currentUser: UserService.getCurrentUser(),
isLoading:false,
};
this.logOut = this.logOut.bind(this);
this.onClick = this.onClick.bind(this);
}
logOut() {
UserService.logout()
}
SearchModalRef = ({handleShow}) => {
this.showModal = handleShow;
}
onClick = () => {
this.showModal();
}
render(){
const { currentUser ,isLoading } = this.state;
console.log(currentUser)
if (isLoading) {
return (<div><p>Loading...</p></div>);
}
if(!currentUser){
return(
<div>
<Redirect to='/login' />
</div>
)
}
else{
return(
<div>
<header>
<h1>Dashboard</h1>
{' '}
<div>
<Link to={`/dashboard/profile/:${currentUser.user._id}`}>Profile</Link>
</div>
{' '}
<div>
<Link to="/login" onClick={this.logOut}>LogOut</Link>
</div>
{' '}
<SearchModal ref={this.SearchModalRef} ></SearchModal>
<button type="button" onClick={this.onClick}>
Search
</button>
</header>
<div>
</div>
</div>
);
}
}
}
Issue
it is not searching when i type p its giving searc:'' and when i add
prat then search : 'pra' only
React state updates are asynchronous and batch processed between render cycles. This means when you enqueue a state update it won't be available until the next render cycle. Any further references to state in the same function will be the state value from the current render cycle.
async onTextboxChangeSearch(event) {
this.setState({
search: event.target.value // <-- next state
});
let {search,userdetails} = this.state; // <-- still current state!
console.log(search)
const data = {username:search};
console.log(data)
let SearchStatus = await SearchUser(data);
userdetails=SearchStatus.user
console.log(userdetails);
}
Solution
I suggest factoring out the search logic into its own function to be called by the componentDidUpdate lifecycle methods when state updates.
onTextboxChangeSearch(event) {
const { value } = event.target;
this.setState({
search: value // <-- (1) update state
});
}
searchForUser = async () => { // <-- (3) refactored search function
const { search, userdetails } = this.state;
const data = { username: search };
const { user } = await SearchUser(data);
this.setState(prevState => ({
userdetails: [...prevState.userdetails, user], // append user
}));
}
componentDidUpdate(prevProps, prevState) {
if (prevState.search !== this.state.search) {
this.searchForUser(); // <-- (2) search state updated, do search for user
}
}
So, I have three components(Search, Pages, HanddlesApi) plus App.js and I'm passing props around via functions to other child components with no problem.
The Search component passes it's state of userInput to HandleApi component and updates the api using componentDidUpdate. (this works great, np here).
I added the Pages component to update the api's page number so that the user can cycle through pages of content. This works, but with issues. The user can do a search and cycle though the pages, but if they enter a new query, they will land on the same page number of the new query. For example, If
I searched "ducks" and clicked to the next page(2). Then did a search for "dogs" they would land on page two of "dogs"
So my question is how do I reset state for my Pages component only when a user enters a new query?
I saw that componentWillReceiveProps is being deprecated, so I can't use that.
getDerivedStateFromProps seemed like it might be a good idea, but from what I read it should only be used in rare cases.
So, the two most likely options seemed to be, use componentDidUpdate in a way I don't understand or use key?
Overall I'm just confused on what to do
In my HanddlesApi Component I'm passing the follwoing into the API:
q: this.props.inputValue ? this.props.inputValue : 'news',
page: this.props.pageNum ? this.props.pageNum: 0
then..
componentDidMount() {
this.fetchNews()
}
componentDidUpdate(prevProps, prevState) {
if (this.props.inputValue !== prevProps.inputValue || this.props.pageNum !== prevProps.pageNum) {
this.setState({
news: []
}, this.fetchNews);
}
}
Then in my Pages Component, I have
import React, { Component } from 'react'
class Pages extends Component {
constructor(props) {
super(props)
this.state = {
nextPage: 1,
prevPage: 0
}
}
handleNextClick = () => {
this.setState({
nextPage: this.state.nextPage + 1,
})
}
handlePrevClick = () => {
this.setState({
prevPage: this.state.prevPage - 1,
})
}
render() {
return (
<div className='pageNav'>
<button className="PrevButton" onClick={() => {
this.handlePrevClick()
this.props.onNextButtonClick(this.state.prevPage)
}}>Previous </button>
<button className="nextButton" onClick={() => {
this.handleNextClick()
this.props.onNextButtonClick(this.state.nextPage)
}}>Next </button>
</div>
)
}
}
export default Pages
Search Component
import React, { Component } from 'react';
class SearchBar extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: ""
}
}
handleChange = (e) => {
this.setState({
inputValue: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.onSubmittedSearch(this.state.inputValue)
}
render() {
//{this.props.onSubmittedSearch(this.state.inputValue)}
return (
<section>
<form onSubmit={this.handleSubmit}>
<label htmlFor="searching"></label>
<input type="text" placeholder="Search Something" value={this.state.inputValue} onChange={this.handleChange} />
<button type="submit">Search </button>
</form>
</section>
)
}
}
export default SearchBar
App.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: null,
pageNum: 1
}
}
// used to pass props from SearchBar to NewsList
onSubmittedSearch = (inputValue) => {
this.setState({
inputValue: inputValue
})
}
onNextButtonClick = (pageNum) => {
this.setState({
pageNum: pageNum
})
}
render() {
return (
<main>
<SearchBar onSubmittedSearch={this.onSubmittedSearch} />
<NewsList inputValue={this.state.inputValue} pageNum={this.state.pageNum} />
<Pages onNextButtonClick={this.onNextButtonClick} />
<Footer />
</main>
)
}
}
export default App;
You should let App in charge of changing and holding the current page number. So you can reset it each time your search component submit. Here is a working exemple:
class Pages extends React.Component {
render() {
return (<div className='pageNav'>
<button disabled={this.props.page <= 1} className="PrevButton" onClick={this.props.onPrevButtonClick}>Previous
</button>
<span>{this.props.page}</span>
<button className="nextButton" onClick={this.props.onNextButtonClick}>Next
</button>
</div>)
}
}
class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = {
inputValue: ""
}
}
handleChange = (e) => {
this.setState({inputValue: e.target.value})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.onSubmittedSearch(this.state.inputValue)
}
render() {
//{this.props.onSubmittedSearch(this.state.inputValue)}
return (<section>
<form onSubmit={this.handleSubmit}>
<label htmlFor="searching"></label>
<input type="text" placeholder="Search Something" value={this.state.inputValue} onChange={this.handleChange}/>
<button type="submit">Search
</button>
</form>
</section>)
}
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
inputValue: null,
pageNum: 1
}
}
// used to pass props from SearchBar to NewsList
onSubmittedSearch = (inputValue) => {
this.setState({inputValue: inputValue, pageNum: 1})
}
onNextButtonClick = () => {
this.setState(state => ({
pageNum: state.pageNum + 1
}))
}
onPrevButtonClick = (pageNum) => {
this.setState(state => ({
pageNum: Math.max(state.pageNum - 1, 1)
}))
}
render() {
return (<main>
<SearchBar onSubmittedSearch={this.onSubmittedSearch}/>
<Pages onNextButtonClick={this.onNextButtonClick} onPrevButtonClick={this.onPrevButtonClick} page={this.state.pageNum}/>
</main>)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
I'm creating a simple CRUD React app that let's you manage products. Products only have a name and a price.
In my AddProduct component, my onSubmit method can successfully log this.nameInput.value, this.priceInput.value, but when I change the log to this.props.onAdd I get this.props.onAdd is not a function.
I'm following a tutorial, so I'm sure I'm missing one small thing, but could use another set of eyes on my code.
Here's my addProduct component - the onSubmit method has the this.props.onadd(...):
import React, { Component } from 'react';
class AddProduct extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(event) {
event.preventDefault();
this.props.onAdd(this.nameInput.value, this.priceInput.value);
}
render() {
return (
<form onSubmit={this.onSubmit}>
<h3>Add Product</h3>
<input placeholder="Name" ref={nameInput => this.nameInput = nameInput}/>
<input placeholder="Price" ref={priceInput => this.priceInput = priceInput}/>
<button>Add</button>
<hr />
</form>
);
}
}
And here's my App.js:
import React, { Component } from 'react';
import './App.css';
import ProductItem from './ProductItem';
import AddProduct from './AddProduct';
const products = [
{
name: 'iPad',
price: 200
},
{
name: 'iPhone',
price: 500
}
];
localStorage.setItem('products', JSON.stringify(products));
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: JSON.parse(localStorage.getItem('products'))
};
this.onAdd = this.onAdd.bind(this);
this.onDelete = this.onDelete.bind(this);
}
componentWillMount() {
const products = this.getProducts();
this.setState({ products });
}
getProducts() {
return this.state.products;
}
onAdd(name, price) {
const products = this.getProducts();
products.push({
name,
price
});
this.setState({ products })
}
onDelete(name) {
const products = this.getProducts();
const filteredProducts = products.filter(product => {
return product.name !== name;
});
this.setState({ products: filteredProducts });
}
render() {
return (
<div className="App">
<h1>Products Manager</h1>
<AddProduct
/>
{
this.state.products.map(product => {
return (
<ProductItem
key={product.name}
{...product}
onDelete={this.onDelete}
/>
);
})
}
</div>
);
}
}
export default App;
What's the matter with my code? When I click the Add button, I get the error.
It's because you pass no prop to the <AddProduct /> you render.
You should add it like so:
<AddProduct onAdd={this.onAdd}/>
You need to pass the props to the component:
<AddProduct onAdd={onAdd} />
Just change the following line
<form onSubmit={this.onSubmit}>
to
<form onSubmit={this.onSubmit.bind(this)}>
onAdd is a field in the props of AddProduct. It means you should delivery a function to AddProduct in App. like: <AddProduct onAdd={() => console.log('works')} />