I am new to react and using "inspirational-quotes" from https://www.npmjs.com/package/inspirational-quotes trying to load a new quote on button click using bind().
I do not know what i do wrong.
This is the code if have right now (App.js):
enter code here
import React, { useState } from "react";
import './App.css';
const a = 'New Quote';
const Quote = require('inspirational-quotes');
const quotes = Quote.getQuote();
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isAppon: true,
quoteText: quotes.text,
quoteAuthor: quotes.author
};
this.newQuote = this.newQuote.bind(this);
}
newQuote() {
// alert('hee')
// this.setState({ quoteText: this.state.quoteText, quoteAuthor: this.state.quoteAuthor });
this.setState(prevState => ({ isAppon: !prevState.isAppon }));
}
render() {
return (<div className="App" >
<header >
{/* <p> A new quote: {this.state.quoteText} from {this.state.quoteAuthor}</p> */}
<button onClick={this.newQuote}> {a} < /button>
<div> A new quote: {this.state.quoteText} from {this.state.quoteAuthor}</div>
< /header> < /div>
);
}
}
export default App;
So you have a couple of things wrong here.
You're trying to use React Hooks (useEffect for example) in a class component, so that wont work. You'd need to use lifecycle events like componentDidMount.
I'm also not sure if you paste your code correctly as it isn't valid when I paste it, there are just a couple things missing but I can see what you wanted to paste so it's okay.
That said, you're also making your life difficult using a class based component when functional components are a thing in combination with hooks :)
(by the way i'm not saying class components don't have their place, they do. But they are not as beginner friendly)
Here's what you need to do:
import { useState, useEffect } from 'react';
import * as Quote from 'inspirational-quotes';
function App() {
const [quote, setQuote] = useState('');
const getNewQuote = () => {
setQuote(Quote.getQuote())
}
// on mount get a new quote
useEffect(() => {
getNewQuote();
}, [ ])
return (
<>
<button onClick={getNewQuote}>new quote</button>
<p>{ quote.text } - { quote.author } </p>
</>
);
}
export default App;
That said, your the library your using kinda sucks as the getQuote uses a "random" index that is calculated outside of the getQuote method. Because of this clicking the button won't create a new quote. If you can find a way to create a new instance of quote then you'll be in business. I tried a couple ways to achieve this but no luck.
I suggest looking into using a random quote API and modifying the getNewQuote method to use an async fetch request to get your next quote. I'll help you with this in the comments or an edit if you need.
Edit: Update as this question is based on a challenge that asks for a page refresh. See below for updated example:
import { useState, useEffect } from 'react';
import Quote from 'inspirational-quotes';
const App = () => {
const [quote, setQuote] = useState(null);
const getNewQuote = () => {
setQuote(Quote.getQuote());
}
// reload the current page
const refreshPage = () => {
window.location.reload();
}
// on mount get a new quote
useEffect(() => {
getNewQuote();
}, [ ])
return (
<>
<button onClick={refreshPage}>new quote</button>
<p>{ quote?.text } - { quote?.author } </p>
</>
);
}
export default App;
In This example we are keeping track of the quote in state using useState and assigning its value to quote.
We then use useEffect with an empty dependency array to tell React we want to do something when the page loads
We create two handle functions:
getNewQuote is for setting a quote to state (as an object).
refreshPage is a function to reload the current page.
Related
On my older repository, I have used Class Components with the following CruiseListHeader component code, as an example (used for showing Cruise Buttons).
import React from 'react';
import {getCruiseLines} from '../api/api'
import ListofShips from './ListofShips'
class CruiseListHeader extends React.Component {
constructor(props) {
super(props)
//setting intial state for Cruise Headings and initialize cruiseHeaders as an empty array
this.state = {
cruiseHeaders: []
}
//binding methods for setting up Cruise Line Headings
this.setUpCruiseLines = this.setUpCruiseLines.bind(this)
}
componentDidMount() {
console.log('cdm')
this.setUpCruiseLines()
}
setUpCruiseLines() {
console.log('getcruiselines')
getCruiseLines()
.then(res => {
this.setState({
cruiseHeaders: res
})
})
}
render() {
return (
<React.Fragment>
{/* There will be Headings for all the Cruise Lines. */}
{/* Map the Cruiseline Headings for each Ship to display them on the page. I want to map ship, because I need each ship displayed in a List, when Cruise Line Heading is clicked. */}
<div className = "cruiseContainer">
{this.state.cruiseHeaders.map (ship => {
return (
<div key={ship.cruise_line}>
{/* ListofShips component needs cruise_line, because when user clicks on Cruise Line Heading button,
we need to fetch ships that belongs to that particular cruiseline. */}
{/* We need to render multiple ListofShips components, with one for each cruiseline */}
<ListofShips cruise_line={ship.cruise_line}></ListofShips>
</div>
)
})}
</div>
</React.Fragment>
)
}
}
export default CruiseListHeader
Please note that this is all related to a Cruise Lines Page shown below, that has a main CruiseLines.jsx component with the CruiselistHeader.jsx mentioned above imported into it.
Cruise Buttons on Cruise lines Page
Now I want to start the change by converting this React Class Component into a Functional one.
This is what I have for my CruiseListHeader as a Functional Component, so far.
Please note that ListofShips is now called ShipsList in this new repository.
import React, { useEffect, useState} from 'react'
import {getCruiseLines } from '../api/api'
import ShipsList from './ShipsList'
function CruiseListHeader() {
// Declare cruiseHeaders State variable
const [cruiseHeaders] = useState({
})
useEffect (() => {
// Note: This was the original ComponentDidMount that took Binding this.setUpCruiseLines()
// Now it is coming from the CruiseListHeader.js useEffect to the DOM
}
)
return (
<>
<div>
<div key={ship.cruise_line}>
<ShipsList cruise_line={ShipsList.cruise_line}></ShipsList>
</div>
</div>
</>
)
}
export default CruiseListHeader
What I am wanting to understand, is how does the Functional Component handle the state from my api, the binding and the mapping that I was previously using in my Class Component ?
If anyone has any ideas of how I can handle this, then that would be of great valuable help thanks.
You're ignoring the setter for the state, your useState line should be:
const [cruiseHeaders, setCruiseHeaders] = useState({});
Then you'd use that setCruiseHeaders function to set the state:
useEffect (() => {
getCruiseLines()
.then(res => {
setCruiseHeaders({
cruiseHeaders: res
})
});
}, []); // Make sure to also pass an array here, or you'll be triggering this effect on every render
As an aside, you probably meant to initialize your state value to an array instead of an object:
const [cruiseHeaders, setCruiseHeaders] = useState([]);
Since the "cruise headers" data in your original code was an array.
import React, { useEffect, useState} from 'react'
import {getCruiseLines } from '../api/api'
import ShipsList from './ShipsList'
function CruiseListHeader() {
// Declare cruiseHeaders variable and set it's array using useState
const [cruiseHeaders, setCruiseHeaders] = useState([]);
// Note: This was the original ComponentDidMount that took Binding this.setUpCruiseLines()
// Now it is coming from CruiseListHeader.jsx useEffect to the DOM
useEffect (() => {
getCruiseLines()
.then(res => {
setCruiseHeaders({
cruiseHeaders: res
})
});
}, []); // Make sure to also pass an array here, or you'll be triggering this effect on every render
return (
<>
{/* <div className = "cruiseContainer"> I don't think I need this because I am using Tailwind CSS*/}
{/* When I use Sass in my next final repo I may not need a div either */}
{cruiseHeaders.map (ship => {
// return ( Do not need return here, I think
<div key = {ship.cruise_line}>
{/* ListofShips component needs cruise_line, because when user clicks on
Cruise Line Heading button, we need to fetch ships that belongs to that particular
cruiseline. */}
{/* We need to render multiple ShipsList components, with one for each cruiseline */}
<ShipsList cruise_line={ship.cruise_line}/>
{/* </ShipsList> I think I don't I need this closing tag*/}
</div>
// ) I think, I do not need return closing bracket here
})}
{/* </div> I think, I do not need closing div from cruiseContainer here*/}
</>
)
}
export default CruiseListHeader
I have a component I call that is a passed a recordID and returns the text associated to the Id. 33 should = Tower
will render "Tower" on the screen. All good, but...
When I try to use the component in the following IF statement it does not work.
...
if (<GetAssetTypeNameComponent datafromparent = {assettype_assettypeId}/> === "Tower")
{
this.props.history.push(`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`);
}
Using the passed parameter does work if I change the code to:
...
if (assettype_assettypeId === "33")
{
this.props.history.push(`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`);
}
...
What am I doing wrong?
Rob
Component Code that needs to be a Function....
...
class GetAssetTypeNameComponent extends Component {
constructor (props){
super(props)
this.state = {
assettype:[]
}
}
componentDidMount()
{
AssetTypeService.getAssetTypeById(this.props.datafromparent).then( (res) =>{
let assettype = res.data;
this.setState({isLoading:false});
this.setState({
assettypeName: assettype.assettypeName,
assettypeType: assettype.assettypeType
});
});
}
render() {
return (
<div>
{this.state.assettypeName}
</div>
);
}
}
export default GetAssetTypeNameComponent;
...
Following Function code compiles:
...
import React, { useState} from 'react';
import AssetTypeService from './AssetTypeService'
const GetAssetTypeNameFunction = (props) =>{
// destructuring
const { assettype_assettypeId } = props;
const [assetType,setAssetType] = useState()
AssetTypeService.getAssetTypeById(assettype_assettypeId).then( (res) =>
setAssetType(res.data));
const arrayMap = assetType.map((post)=>{
return(
<ul>
{post.assettypeName}
</ul>
);})
return (
{arrayMap}
);
}
export default GetAssetTypeNameFunction;
...
Get execution error:
I think because I calling the function from within an eventHandler:
...
editAssets(assetsid,assettype_assettypeId){ if (GetAssetTypeNameFunction(assettype_assettypeId) === "Tower") { this.props.history.push(/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}); }]
...
----- Error: Invalid hook call. Hooks can only be called inside of the body of a function component. I am responding to a onClick in a list to route to a specific component based on the function $
How do I get around this?
A component renders content to be displayed in the page. The retuned value of rendering a component is a tree of nodes that contain your content. All this means that <GetAssetTypeNameComponent> may contain the text content Tower, but it is not equal to the string "Tower". It just doesn't make any sense to render a component as the test for a conditional like this.
In React you want to use logic to tell react how to render. You do not want to render and then use the result in your logic.
It's hard to give advice on the best way to fix that with so little code, but maybe you want a a simple function to coverts the id into some text for you.
function getAssetName(id) {
return someLogicSomewhere(id).result.orWhatever
}
And now you can do something like:
if (getAssetName(assettype_assettypeId) === 'Tower')
{
this.props.history.push(
`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`
);
}
I'm new to React, and I'm trying to figure out how to adjust what appears in render based on a click event. My component receives two props "front" and "back". I want the component to display this.props.front upon rendering and change to this.props.back when the div is clicked. I'm having trouble figuring out how to accomplish this in my handleClick function.
Any help would be appreciated!
import React, { Component } from 'react';
class Card extends Component {
handleClick = event => {
}
render() {
return (
<div className="Card" onClick={this.handleClick}>
<h1>{this.props.front}</h1>
</div>
);
}
}
export default Card;
You could add a state to this component which is a boolean that toggles itself
class Card extends Component {
constructor(props) {
this.state = {
showFront: true
}
}...
And than use your handleClick method to switch the state back and forth
handleClick = (e) => {
this.setState({showFront: !this.state.showFront})
}
And in your render function you could put a conditional to show
render() {
return (
<div className="Card" onClick={this.handleClick}>
{
this.state.showFront
? <h1>{this.props.front}</h1>
: <h1>{this.props.back}</h1>
}
</div>
);
}
A comment to this answer was made but was deleted - i think it's a subject worth touching.
the comment said you should use the setState(updater()) and not pass an object.
it's true that when the app becomes more complex, you have several state updates together and data states may not be what you believe they are at that moment, updater function is apropriate (setState is async and could batch calls this is why we have the function that flushes all and helps us maintain state integrity comparing old states with new ones.
but for this answer and the complexity of the question an updater isn't necessary and the code should work just fine (and it gets to the point of using state and toggling which is the right way of doing what was asked).
you can use the updater function any time you please - even for the most simplest state change. And like said here, maybe it is best practice to just always use it :)
for more reference
React.Compoment setState & Updater function
In react you trigger render by changing the state of component. If this component needs to recieve props "front" and "back" then parent component should have saved in state if the state is "front" or "back" and pass down to component callback function to handle change. Something like:
import React, { Component } from 'react';
class ParentCard extends Component {
state = { isFront: true };
handleClick = event => {
this.setState({isFront: !this.state.isFront})
}
render = () => {
const { front } = this.state;
return (
<Card front={front} onClick={this.handleClick} />
);
};
export default ParentCard;
Also you can make Card component "pure" just by creating it as function which returns JSX.
import React from 'react';
const Card = ( { isFront, onClick } ) => {
return (
<div className="Card" onClick={onClick}>
<h1>{isFront ? `text if is front` : `text if it is not`}</h1>
</div>
);
}
}
export default Card;
Hope it helps :)
I'd say in that case you want to use state rather than props here, particularly when the state you want to change is being dictated by the component itself.
class Card extends Component {
state = {
mode: 'front' // default state to front
}
handleClick = () => this.setState({ mode: 'back' })
render() {
return (
<div className="Card" onClick={this.handleClick}>
<h1>{this.props.mode}</h1>
</div>
);
}
}
export default Card;
If this is really a toggle then of course you can use a Boolean flag instead, but you get the idea.
This component itself is currently not set up as a stateless functional component so if thats what you also wanted to achieve. Youll want to make these changes as well as pass props of a boolean in your stateful component.
import React from 'react';
const Card = (props) => {
return (
<div className="Card" onClick={props.handleClick}>
{props.showFront
?
<h1>props.front</h1>
:
<h1>props.back</h1>
}
</div>
);
}
export default Card;
you'll want to utilize the previous state to toggle your state because it could cause issues later down the road with batching, so your stateful component should look something like:
import React, {Component} from "React";
class StatefulCard extends Component {
state = {
showFront: true // default state to front
}
handleClick = () => {
this.setState(prevState => {
return {
showFront: !prevState.showFront
}
}
}
render() {
return (
<div>
<Card
handleClick={this.handleClick}
showFront={this.state.showFront}
/>
</div>
);
}
}
export default Card;
In my react JS application, I have a notification icon added in header component. I have created a separate component where I am doing api calls to get the data and display it. what I am trying to achieve here is to change the color of the icon in a Header component if there is some notification alerts.
My Header component-
import React from "react";
import { connect } from "react-redux";
import {
setPoiData,
getNotification,
updateNotification
} from "../../actions/action";
import { Link } from "react-router-dom";
const axios = require("axios");
class Notification extends React.Component {
render() {
const data = this.props.getNotificationStatus;
const highlightBellIcon = Object.keys((data.length === 0))
return (
<div className="notification-parent">
<Link to="/notification-details">
<span className={"glyphicon glyphicon-bell " + (!highlightBellIcon ? 'classA' : 'classB')} />
</Link>
</div>
);
}
}
const mapStateToProps = state => ({
getNotificationStatus: state.root.getNotificationStatus
});
export default connect (mapStateToProps)(Notification)
Here, getNotificationStatus is the state that holds the value in Redux.
Notification-details Component-
import React from "react";
import { connect } from "react-redux";
import {
getNotification
} from "../../actions/action";
import { Spinner } from "../Spinner";
import { setTimeout } from "timers";
import NotificationTile from "../NotificationTile/NotificationTile";
const axios = require("axios");
class NotificationDetails extends React.Component {
componentDidMount = () => {
this.intervalId = setInterval(() => this.handleNotification(), 2000);
setTimeout(
() =>
this.setState({
loading: false
}),
10000
);
};
componentWillUnmount = () => {
clearInterval(this.intervalId);
};
handleNotification = () => {
let postData = {
//inputParams
}
//call to action
this.props.dispatch(getNotification(postData));
};
getNotificationDetails = data => {
const payloadData =
data.payLoad &&
data.payLoad.map(item => {
console.log(this);
return <NotificationTile {...item} history={this.props.history} />;
});
//console.log(payloadData);
return payloadData;
console.log("InitialState" + payloadData);
};
render() {
const { loading } = this.state;
const data = this.props.getNotificationStatus;
return (
<div className="notificationContainer container">
<div className="notification-alert">
{!loading ? (
this.getNotificationDetails(data)
) : (
<h1>
Waiting for notifications..
<Spinner />
</h1>
)}
</div>
</div>
);
}
}
const mapStateToProps = state => ({
getNotificationStatus: state.root.getNotificationStatus
});
export default connect(mapStateToProps)(NotificationDetails);
The problem I am facing is always classB is getting added since the api call happens on click of the bell icon. So when I land to the page first time, api call doesn't happen unless I click on the bell icon. My code is absolutely working fine, It is just that I need to add the class to my Notification component (which is a global component) based on the response received in NotificationDetail Comp which is a sibling comp.Any suggestions where I am going wrong?
When you have to update your REDUX store, based on an Asynchronous call, you should be using something called Action Creators, These action creators will give you the ability
to dispatch an action after your response from you async call.
Use Redux-thunk
In this below code setTimeout is where your async call goes in
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
function increment() {
return {
type: INCREMENT_COUNTER
};
}
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
Update you REDUX Store and you call this action creator from componentDidMount()
so that you get your notifications the very first time.
notification-details is a separate component which fetches the data and adds it to store and
there is <Link to="/notification-details"> which loads this component, in this case, your store
initially will not have data until you click on the bell icon, which is correct behavior.
Solution:
Please go one step up in your component tree, I mean go to a component which is superior to
Notification ( its Container component ), and load notification-details in there ( you can use any creational lifecycle hook),
,also have a flag ( something like isNotificationLoaded ) which checks where state.root.getNotificationStatus
is filled with data and load the Notification panel only after that is true.
something like
render() {
const data = this.props.getNotificationStatus;
const noticationPanel = data.length > 0 ? <Notification/> : null;
return({noticationPanel});
}
This will make sure loads only when there is data, until then
you can show some activity indicator.
Hope this helps to solve your problem.
I'm trying to get MobX to work with functional components in react. I want to do this without having to use decorators. I have set up an app with create-react-app, added MobX and MobX-react as dependencies.
However, I can't seem to get observables working within functional components.
import React from 'react';
import { extendObservable } from 'mobx';
import { observer } from 'mobx-react';
const Test = () => {
extendObservable(this, {
button: false
});
const handleB1 = () => {
this.button = false;
}
const handleB2 = () => {
this.button = true;
}
const getButton2 = () => {
console.log('button2');
return (
<button type="button" onClick={handleB2}>Button 2</button>
);
};
const getButton1 = () => {
console.log('button1');
return (
<button type="button" onClick={handleB1}>Button 1</button>
);
};
return (
<div>
{this.button ? getButton1() : getButton2()}
</div>
)
};
export default observer(Test);
Clicking the button I would expect the component to get rerendered due to the observable being changed, but I get an error:
×
Error: [mobx] Invariant failed: Side effects like changing state are not
allowed at this point. Are you trying to modify state from, for example, the render
function of a React component? Tried to modify: ObservableObject#2.button
I have tried declaring the observable as part of a functional component or before like this:
const buttonState = () => {
extendObservable(this, {
button: false
});
}
but in both cases I could not get the component to rerender or i was not sure if the observable was actually correctly set.
If i write the whole thing as a class like this it works perfectly
import React from 'react';
import { extendObservable } from 'mobx';
import { observer } from 'mobx-react';
class Test extends React.Component {
constructor(props) {
super();
extendObservable(this, {
button: false
});
}
handleB1 = () => {
this.button = false;
}
handleB2 = () => {
this.button = true;
}
getButton2 = () => {
console.log('button2');
return (
<button type="button" onClick={this.handleB2}>Button 2</button>
);
};
getButton1 = () => {
console.log('button1');
return (
<button type="button" onClick={this.handleB1}>Button 1</button>
);
};
render = () => {
return (
<div>
{this.button ? this.getButton1() : this.getButton2()}
</div>
)
}
};
export default observer(Test);
In React, functional components are not persistent. They run from top to bottom, return some JSX, rinse and repeat.
let i = 0;
const FunctionalComp = (props) => {
const foo = props.foo.toUpperCase();
return <span>Rendered {i++} times. {foo}</span>;
}
All this functional component will ever do is synchronously create the value foo and then return the span. When this component's parent re-renders, this component will do the same exact same, but with potentially new values.
It can never do anything else, and that is why it is powerful. That is why we can call it a functional component: Because it only depends on the values provided to it, because it does not cause side effects that would alter the direction of the rest of the application, and because given the same arguments, this function will produce the same result for the rest of eternity.
Predictable = powerful.
Now, a class component holds persistent state. It constructs, initializes its state, methods, and properties, and then renders and returns JSX. The class (object) still exists in memory, so all of the values and methods on it exist too.
The methods of class component are not so predictable.
class Foo {
name = 'tommy';
getUpperName() {
return this.name.toUpperCase();
}
setName(name) {
this.name = name;
}
}
Foo.getUpperName will not produce the same result every time it is ever used with the same arguments (hint: it doesn't accept any arguments and depends on the context around it to determine its result), so other pieces of the application may change Foo.name and, essentially, control Foo.getUpperName's outcome, potentially by accident.
The class can update its own state, causing itself and all children components to re-compute their JSX returns.
In a plain arrow function, after it returns, all that exists is the return value that it produces and the function statement (declaration). Nothing in between.
All this said, the functional component has no this bound to it. (That is a no-no in functional programming.) It will never have state.
So you can not do anything with this inside of a functional component and it can not hold observable values because every time it re-renders it would re-instantiate each of those values.
In your example above, even if this did refer to Test, this is what would happen:
Test would create the observable value button as false.
Test would render the button, which you would then click.
Test would create the observable value button as false.
Test would render the button, which you would then click.
Test would create the observable value button as false.
Test would render the button, which you would then click.
So on and so forth.
In MobX, your observables need to live on a persistent data structure and be passed into your render functions that return UI markup.
const store = observable({
name: 'tommy'
});
const changeName = () => store.name = store.name.split('').reverse().join('');
const Foo = observer((props) => {
return (
<button onClick={changeName}>{store.name}'s button</button>
)
});
This is not a great pattern, as neither Foo nor changeName are pure, this code would work, at least.
You need to do something like so:
const store = () => {
const self = {};
self.actions = {
setName: action((name) => self.name = name);
}
return extendObservable(self, { name: 'tommy' });
}
const App = (props) => {
return <span><Foo store={store} /></span>
}
const Foo = observer((props) => {
return (
<button onClick={props.store.actions.setName}>
{store.name}'s button
</button>
)
})
Again, this is not an ideal implementation, but it would work, and I am at work and have to get back to what they pay me to do. ;)