How to make an API request In React on button click - reactjs

I'm trying to build a random quote generator that loads A quote on componentDidMount with an axios api request , then loads new quotes on button click.
This is for A freecodecamp project. I have tried making the call again on button click, then adding the new response to state, but it will not work at all.
import React, { Component } from 'react'
import Button from './Button';
import axios from 'axios'
class QuoteBox extends Component{
constructor(props){
super(props)
this.state = {
quotes: []
}
}
componentDidMount(){
axios.get('http://quotesondesign.com/wp-json/posts?
filter[orderby]=rand&filter[posts_per_page]=1')
.then(res=> this.setState({quotes: res.data[0]}))
}
getNext = (ev) =>{
ev.preventDefault()
axios.get('http://quotesondesign.com/wp-json/posts?
filter[orderby]=rand&filter[posts_per_page]=2')
.then(res=> this.setState({quotes:[...this.state,res.data[0]]}))
}
render(){
const {content,title} = this.state.quotes
const filteredContent = String(content).replace(/(<\w>)|(<\/\w>)|
(&#\d{4})/gm, "").replace(/(;)/g,"'")
console.log(content)
return(
<React.Fragment>
<h2>A little inspiration for the day</h2>
<div className='outerQuoteBox'>
<div className='innerQuoteBox'>
<p>{filteredContent}</p><br/><br/>{title}
</div>
<Button getNext={this.getNext} />
</div>
</React.Fragment>)
}
}
export default QuoteBox
And this is my button component
import React, { Component } from 'react'
export class Button extends Component {
render() {
return (
<React.Fragment>
<button onClick={this.props.getNext} className='nextBtn'
type='button'>Next</button>
</React.Fragment>
)
}
}
export default Button
When I click the button, it seems like the request isn't going through at all. If i check State in the dev tools, only the first quote from componentDidMount is in the array. I don't understand where my mistake is.
Edit: I had used the wrong prop reference, so it wasn't making the call. I fixed this and it does make the call now, and it brings in one new quote, but that's it. And it doesn't add the new one to state, it just replaces it with the one new one. and that's all it will do. The api instructions say the end point i'm using should return a new random quote, but it does not.

It looks like you're referencing the wrong prop on the button.
Change getQuote to getNext and it should work...
import React, { Component } from 'react'
export class Button extends Component {
render() {
return (
<React.Fragment>
<button onClick={this.props.getNext} className='nextBtn'
type='button'>Next</button>
</React.Fragment>
)
}
}
export default Button

Related

React-toastify notification won't return in conditional

The notification works quite easily with a button. However, I'm trying to have it activate when a props passes through (true/false).
Basically, the user clicks on this tab, if they're not signed in, it'll pop up with the notification telling them to sign in.
However, I cannot make it work without being a button. The props passess through just fine, and I can console.log it. And the conditional returns... something, but it's like an odd bunch of letters, and each refresh it changes. And does not pop up like a notification. It's just obscure letters in the middle of the screen (because of {notify} placed above the form).
import React, {Component} from 'react';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
class Alerts extends Component {
constructor(props){
super(props)
this.state = {
alertLogin: ''
}
}
render() {
const notify = () => toast("Please login before adding a recipe!");
// tried to make a conditional to check if prop alertLogin is true or false
// then calls notify function if false
if (!this.props.alertLogin) {
console.log('alert props received', this.props.alertLogin)
return notify()
}
return (
<div>
{/* <button onClick={notify}>Notify !</button> */}
{notify}
<ToastContainer />
</div>
);
}
}
export default Alerts;
import React, { Component } from "react";
import "./styles.css";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
class Alerts extends Component {
constructor(props){
super(props)
this.state = {
alertLogin: ''
}
}
componentDidMount() {
// tried to make a conditional to check if prop alertLogin is true or false
// then calls notify function if false
if (!this.props.alertLogin) {
console.log("alert props received", this.props.alertLogin);
this.notify();
}
}
notify = () => toast("Please login before adding a recipe!");
render() {
return (
<div>
<button onClick={(e) => this.notify()}>Notify !</button>
<ToastContainer />
</div>
);
}
}
export default Alerts;
Codepen for the solution
First you take the if statement with the function and put it in componentDidMount
cause i'm guessing is stopping the rendered elements themselves from rendering
second make the toast function accessible by component did mount and the button by declaring it before the render function hope i was clear enough

Why the data not displayed in nextjs?

I am making a very very simple nextjs application where I am trying to fetch the data from api.
My requirement is I should display the data in layout.js file and this layout.js file is a children in index.js file.
index.js:
import Layout from "./layout";
import React from "react";
class Home extends React.Component {
render() {
return (
<div>
<Layout />
<h4> Main content will be displayed here !! </h4>
</div>
);
}
}
export default Home;
layout.js:
import React from "react";
import fetch from "isomorphic-unfetch";
function Layout(props) {
return (
<div>
<p>Preact has {props.stars} ⭐</p>
<p> Why I couldn't get the above "props.star" ? </p>
</div>
);
}
Layout.getInitialProps = async () => {
console.log("comes into layout getinitial props");
const res = await fetch("https://api.github.com/repos/developit/preact");
const json = await res.json(); // better use it inside try .. catch
return { stars: json.stargazers_count };
};
export default Layout;
So as per the above given code, I have called the layout page inside index.js page (in my real application I need to call like this only so no changes in calling layout inside index)..
But when I made a console.log() in the function Layout.getInitialProps in layout, it doesn't print anything and hence the api data not fetched..
Complete working demo here with code
Why can't I fetch the data inside the layout.js while calling as a children from index.js?
Also provide me the right updated solution to achieve this.. I really searched for many questions but none solved my issue and I couldn't understand those solutions clearly so please help me with the above given example.
That because getInitialProps can only be added to the default component exported by a page, adding it to any other component won't work.
You should use componentDidMount() or useEffect instead, or move getInitialProps in the index and then pass the result to the component. something like (not tested) :
index.js :
import Layout from "./layout";
import React from "react";
class Home extends React.Component {
render() {
return (
<div>
<Layout />
<h4> Main content will be displayed here !! </h4>
</div>
);
}
}
export default Home;
layout.js
import React from "react";
import fetch from "isomorphic-unfetch";
class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
stars: false
};
}
async componentDidMount() {
console.log("comes into layout getinitial props");
const res = await fetch("https://api.github.com/repos/developit/preact");
const json = await res.json(); // better use it inside try .. catch
this.setState({ stars: json.stargazers_count });
}
render() {
const { stars } = this.state;
return (
<div>
<p>Preact has {stars} ⭐</p>
<p> Why I couldn't get the above "props.star" ? </p>
</div>
);
}
}
export default Layout;
Edit:
Example with class component
Bonus: If you want to add the layout for all the pages of your app this isn't the best approach, instead you should take a look to custom _app.js, example

React with Redux Popup or Modal syntax and setup

I have a MonthContainer component that renders a MonthView component. The container passes data to the view and the view does the formatting and displays a table with link buttons corresponding to month and category.
What I want to do is when a link is clicked in MonthView, a pop up displays on the page with another set of data that is based on the year, category and month for the link that was clicked. I am using react-modal to accomplish this.
In my initial setup, MonthViewContainer was rendering MonthView and PopupContainer which in turn rendered PopupView. PopupContainer was passed a full list of transaction data (unfiltered) which it then passed to PopupView. When I clicked on a link in MonthView, it would set the displayModal flag to true and my PopupView component which was wrapped in react-modal would show up with the transactions filtered based on year, month, category. This worked fine except for my challenges with updating the parent component after saving and closing the modal. However, I don't like the idea of loading all my state into the PopupView and then filtering. Ideally, I would want to get the data when the PopView loads. I'm having trouble doing this.
I have several issues with my setup. Below is my code with comments in each section I'm having trouble with.
MonthViewContainer
import React from 'react';
import MonthView from './MonthView.js';
import { connect } from 'react-redux';
import { getTransactions } from '../actions/actions.js';
import TransactionsPopupContainer from './TransactionsPopupContainer.js';
MonthViewContainer Component
class MonthViewContainer extends React.Component {
constructor() {
super();
this.popupCategory;
this.popupMonth;
this.state = {
detailPopup : false
}
this.handleGetDetail = this.handleGetDetail.bind(this);
this.handleRefresh = this.handleRefresh.bind(this);
}
componentDidMount() {
this.props.getTransactions(2016);
// this.props.getTransactionsAll(2016);
}
render() {
return (
<div className="row">
<div className="col-md-12">
<MonthView
transactions={this.props.storeTransactions.transactions}
selectedYear={this.props.storeTransactions.selectedYear}
onHandleGetDetail={this.handleGetDetail}
/>
</div>
<div>
<PopupContainer
modalActive={this.state.detailPopup}
selectedYear={this.props.storeTransactions.selectedYear}
category={this.popupCategory}
month={this.popupMonth}
onRefresh={this.handleRefresh}
/>
</div>
</div>
)
}
handleRefresh() {
console.log("handle refresh entered")
this.props.getTransactions(this.props.storeTransactions.selectedYear);
}
handleGetDetail(year,category,month) {
this.popupCategory = category;
this.popupMonth = month;
this.setState({ detailPopup: true}, function () {});
}
}
const mapStateToProps = (state) => ({
storeTransactions: state.storeTransactions
});
export default connect(mapStateToProps, {getTransactions})(MonthViewContainer);
PopupContainer
import React from 'react';
import { connect } from 'react-redux';
import PopupView from './PopupView.js';
import { getPopupTransactions } from '../actions/actions.js';
import { saveTransactions } from '../actions/actions.js';
class PopupContainer extends React.Component {
constructor() {
super();
this.editedTrans = undefined;
this.handleSave = this.handleSave.bind(this);
}
componentWillUnmount(){
//when modalActive is true, I would like to get the popup data with params coming from props
//doing something like getPopupTransactions
//the problem is I can't do it here because the component is mounted when parent loads and
//is set to active/visible when a button is clicked on the parent
}
handleSave(transToSave) {
this.props.saveTransactions(transToSave);//use the action in redux store to save these transactions
//refresh the parent (MonthViewContainer/MonthView) after saving //not sure how to do this after closing the modal
//I would like the transactions that are saved after closing the modal to be reflected in the parent component
//what I attempted is to pass in a handler what will trigger set state and case the MonthViewContainer to rerender
this.props.handleRefresh();
}
render() {
return (
<PopupView
modalActive={this.props.modalActive}
transactions={this.props.storePopupTransactions.popuptransactions}
savePopupView={this.handleSave}
/>
);
}
}
const mapStateToProps = (state) => {
return {storePopupTransactions: state.storePopupTransactions//,
}
};
export default connect(mapStateToProps, {getPopupTransactions,saveTransactions})(PopupContainer);
Been working on it and it turns out that I was indeed able to call my parent component (MonthViewContainer) after calling closeModal and before setting the modalIsOpen to false for the react-modal component (PopupView in my case). Therefore, I was able to save the data from PopupView and then refresh my state in MonthViewContainer.

Create a first visit popup in react application?

How do I make a first visit popup for my react application? Is it possible to implement using the react-popup module? I used this module below but it does not seem to work. Can you check and let me know what wrong here.
Below is my homepage:
import React, {Component} from 'react';
import './HomePage.css';
import Carousel from 'nuka-carousel';
import HeaderComponent from '../../components/Header/Header.js';
import {Decorators} from './decorators.js';
import Popup from 'react-popup'
export default class HomePage extends Component {
redirectPage = () => {
window.location = '#/dashboard';
}
componentWillMount(){
Popup.alert('my component')
}
render() {
var mixins = [Carousel.ControllerMixin];
return (
<div>
<div className='explore-button-container'>
<button id='exploreBtn' onClick={this.redirectPage}>Explore</button>
</div>
<HeaderComponent id='header' location={this.props.location}/>
<Carousel
autoplay={true}
autoplayInterval={3000}
wrapAround={true}>
//Carousel Content
</Carousel>
</div>
);
}
}
In componentDidMount you cann Access the localstorage and the sessionStorage, where you can set a flag, if this is the first visit.
something like this:
class myComponent(){
constructor(){//do stuff here}
componentDidMount(){
let visited = localStorage["alreadyVisited"];
if(visited) {
this.setState({ viewPopup: false })
//do not view Popup
} else {
//this is the first time
localStorage["alreadyVisited"] = true;
this.setState({ viewPopup: true});
}
render() {
return(<Modal
aria-labelledby='modal-label'
autoFocus={false}
style={modalStyle}
backdropStyle={backdropStyle}
show={this.state.viewPopup}
onHide={this.close}>
<div style={dialogStyle()} >
I'm the Popup Text
</div>
</Modal>);
}
}
This is how i solved it with Modal, but I'm sure you can do it with Popup, too. If you want to view the Popup on every first visit of a session you can use the sessionStorage instead of the localstorage.
Keep in mind that you have to set the styles. You can see an example here: https://react-bootstrap.github.io/react-overlays/
Put some indicator in the Setting, e.g. AsyncStorage, then check if it is the 1st time running the app:
try {
const value = await AsyncStorage.getItem('#isAppFirstTimeRunning');
if (value !== 'true'){
// not first time running
ShowThePopUp();
}
else {
AsyncStorage.setItem('#isAppFirstTimeRunning', 'true');
}
} catch (error) {
// Error retrieving data
}
Yea, you can add pop-up as soon as you logged-in or landed-in your page.
In your component, add the following snippets
import React, {Component} from 'react';
import './HomePage.css';
import Carousel from 'nuka-carousel';
import HeaderComponent from '../../components/Header/Header.js';
import {Decorators} from './decorators.js';
import Popup from 'react-popup'
class HomePage extends Component {
redirectPage = () => {
window.location = '#/dashboard';
}
componentWillMount(){
Popup.alert('my component')
}
render() {
var mixins = [Carousel.ControllerMixin];
return (
<div>
<div className='explore-button-container'>
<button id='exploreBtn' onClick={this.redirectPage}>Explore</button>
</div>
<HeaderComponent id='header' location={this.props.location}/>
<Carousel
autoplay={true}
autoplayInterval={3000}
wrapAround={true}>
//Carousel Content
</Carousel>
</div>
);
}
}
}
componentWillMount() is a lifecycle hook, which will execute the set of statements before rendering your concern components.
And, go through all lifecycle components available for react.

React + Redux: Separating the presentation from the data

I am building a weather app with React & Redux. I've decided to venture into uncharted waters as a noob to React & Redux. I'm splitting things up into presentational components and their respective container that will handle the data. I'm having some problems wrapping my head around this though. It might come down to how I'm trying to do it I'm just really unsure.
Right now I have SearchBar, CurrentWeather, & Forecast components and an AppContainer that I'm trying to integrate those components into. I have the SearchBar component integrated into the AppContainer so far and it is working with no problems. Here is where I am getting confused. So I have provided the needed actions and components to the container and the container has been connected so when the user does a search the api call will be made and the state will update through the reducers.
That data should be available through mapStateToProps now correct?
How can I go about using that data after the user has performed the action but have it not be used upon the initial render? If AppContainer is rendering these three components I will obviously be passing props to them so they render and function as they are expected to. I'm thinking this is where a lifecycle could be used I'm just unsure of which or how to use them. My code for the AppContainer, SearcBar, & CurrentWeather are below. CurrentWeather & Forecast are nearly identical (only providing different data from different endpoints for the api) so I did not provide it. I also didn't provide the actions or reducers because I know they work fine before I decided to attempt this refactor. Maybe I need more than one container to pull this off? Any advice or direction would be greatly appreciated, thanks all and have a good night.
** Do have a side question: on _weatherSearch I have event.preventDefault(); because the SearchBar is a form element. Do I even need to provide this? If event is not what is being passed but the term I think no. The event is being used as seen below in the form element of SearchBar:
onSubmit={event => getWeather(event.target.value)}
App Container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchCurrentWeather, fetchForecast } from '../actions/actions';
import SearchBar from '../components/SearchBar';
import CurrentWeather from '../components/CurrentWeather';
class AppContainer extends Component {
_weatherSearch(term) {
event.preventDefault();
// Here is where we go to fetch weather data.
this.props.fetchCurrentWeather(term);
this.props.fetchForecast(term);
}
render() {
const getWeather = term => {this._weatherSearch(term);};
return (
<div className="application">
<SearchBar getWeather={getWeather}/>
<CurrentWeather />
</div>
);
}
}
const mapStateToProps = ({ current, forecast }) => {
return {
current,
forecast
}
}
export default connect(mapStateToProps,
{ fetchCurrentWeather, fetchForecast })(AppContainer);
SearchBar:
import React from 'react';
const SearchBar = ({ getWeather }) => {
return(
<form className='input-group' onSubmit={event => getWeather(event.target.value)}>
<input
className='form-control'
placeholder='Search a US City' />
<span className='input-group-btn'>
<button className='btn btn-secondary' type='submit'>Submit</button>
</span>
</form>
);
}
export default SearchBar;
CurrentWeather: *NOTE: I have not removed any of the logic or data processing from CurrentWeather yet so it has not been refactored to a presentational only component yet.
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {unitConverter} from '../conversions/conversions_2.0';
export class CurrentWeather extends Component {
_renderCurrentWeather(cityData) {
const name = cityData.name;
const {temp, pressure, humidity} = cityData.main;
const {speed, deg} = cityData.wind;
const {sunrise, sunset} = cityData.sys;
return (
<tr key={name}>
<td>{unitConverter.toFarenheit(temp)} F</td>
<td>{unitConverter.toInchesHG(pressure)}"</td>
<td>{humidity}%</td>
<td>{unitConverter.toMPH(speed)}mph {unitConverter.toCardinal(deg)}</td>
</tr>
);
}
render() {
let currentWeatherData = [];
if (this.props.current) {
currentWeatherData = this.props.current.map(this._renderCurrentWeather);
}
return (
<table className="table table-reflow">
<thead>
<tr>
<th>Temperature</th>
<th>Pressure</th>
<th>Humidity</th>
<th>Wind</th>
</tr>
</thead>
<tbody>
{currentWeatherData}
</tbody>
</table>
);
}
}
function mapStateToProps({current}) {
return {current};
}
export default connect(mapStateToProps)(CurrentWeather);
Your render function is very dynamic. You can omit anything you like:
class AppContainer extends Component {
_weatherSearch(term) {
// event.preventDefault(); We can't do this because we don't have an event here...
this.props.fetchCurrentWeather(term);
this.props.fetchForecast(term);
}
render() {
const getWeather = term => { this._weatherSearch(term); };
return (
<div className="application">
<SearchBar getWeather={getWeather}/>
{ Boolean(this.props.current) && <CurrentWeather /> }
</div>
);
}
}
const mapStateToProps = ({ current }) => ({ current });
export default connect(mapStateToProps,
{ fetchCurrentWeather, fetchForecast })(AppContainer);
This is how you deal with missing data. You just either show nothing, or a message to search first, or if it's loading,you can show a spinner or throbber.
The technique used above to hide CurrentWeather is to pass a Boolean to React if we're wanting to hide the component. React ignores true, false, null and undefined.
Note that it's a good idea to only ever pass data in mapStateToProps that you'll actually be using inside the component itself. In your code you're passing current and forecast but you don't use them.
Redux will rerender when any of the mapStateToProps, mapDispatchToProps or props data changes. By returning data you'll never use you instruct Redux to rerender when it's not necessary.
I'm a react-redux noob myself :-) and I've come across similar issues.
As far as I can tell, the container/presentational separation you've made looks good, but you can go even a step further and separate the container's fetching and mounting.
The solution I'm referring to is what people variously call "higher-order components" and "wrapper components": (the code below isn't tested - it's just for illustration purposes)
import {connect} from blah;
const AppWrap = (Wrapped) => {
class AppWrapper extends Component {
constructor(props) {
super(props);
this.state = {foo: false};
}
componentWillMount() {
this.props.actions.fooAction()
.then(() => this.setState({foo: false}));
}
render() {
return (<Wrapped {...this.props} foo={this.state.foo}/>);
}
}
function mapState(state) { blah }
function mapDispatch(dispatch) { blah }
return connect(mapState, mapDispatch)(AppWrapper);
}
export default AppWrap;
Notice the = (Wrapped) => { part at the top. That is what's doing the actual "wrapping", and the argument can be named anything so long as you refer to it in the render hook.
Now inside your AppContainer, you get a this.props.foo which acts as a flag telling you that fooAction() has completed, and you can use it to render your presentational components accordingly. Until fooAction completes, you can be sure that the foo passed into AppContainer will be false.
To put what I just said into code, your AppContainer might look something like this:
import AppWrapper from './AppWrapper';
class AppContainer extends Component {
constructor(props) {
super(props);
}
render() {
return (!this.props.foo) ? <div>bar</div> : (
<div blah>
<SearchBar blah/>
<CurrentWeather blah/>
</div>
);
}
}
export default AppWrapper(AppContainer);
The benefit of using a wrapper component like this is that
you can take more control over how and when exactly the data gets rendered
account for "loading" mechanisms and logic
avoid quirky problems like having to make dispatches within componentWillMount hooks and having to deal with the consequences.
Take a look at this blog post for more information about HoCs: https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750

Resources