Convert React Class to a Functional Component - reactjs

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

Related

Refreshing module data on button click in react

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.

Reset functional child components from parent

I need to rerender the components GridSquare from component GridPixels. When the users clicks the button "Reset Colors" the component should reset the child components generated by a map.
The flow is:
User clicks "Reset Colors" button. This button is inside GridPixels component.
Gridpixels component should rerender.
The GridSquare component should be reset. This means that his state should be reset. The purpose of this is that inside GridSquare there is a css class called "set-color-red". When resetting the GridSquare component, the state inside GridSquare component should contain "".
All the GridSquare components are rerendered but the state is mantained. I need to reset the state for every GridSquare component from the GridPixels component.
I tried adding one to the index map each time the "Reset Colors" button is clicked but the state is conserved, is not reset.
import { useState } from 'react';
import GridSquare from './GridSquare'
function GridPixels(props) {
var foo = new Array(192).fill(0);
const [checked, setChecked] = useState(false);
const toggleChecked = () => setChecked(value => !value);
function reset(){
toggleChecked() //This is for rerender the GridSquare component. It doesn't work.
}
return (
<div className="grid-container">
<div className="grid">
{foo.map((item, index) => {
console.log(checked)
return <GridSquare key={ index } />
})
}
</div>
<div><button onClick={reset}> </button></div>//Reset button Colors
</div>
)
}
export default GridPixels
import { useState } from "react";
function GridSquare(props) {
const [a, setA] = useState("");
const changeStyle = () => {
if (a === "set-color-red") {
setA("")
return
}
setA("set-color-red");
}
return <div onClick={changeStyle} className={a}></div>
}
export default GridSquare
Edit: I was able to do what I asking for with the following javascript code:
function reset(){
var classesToRemove = document.querySelectorAll(".set-color-red")
classesToRemove.forEach((item) => {
item.classList.remove("set-color-red")
})
}
I post this to generate a better idea of what I am trying to do.
Edit2: Here is a sandbox of what I am trying to do. There is a grid, and when you click an square, it changes color. There is a reset button, at the right of the grid, to clear all colors from the squares. This is the functionality I can't do.
Sandbox with the code
You can use a key prop on your parent.
The special key prop is used by React to help it understand which components are to be rerendered with prop changes and which should be scrapped and rebuilt.
We run into this most often when mapping over something to build a list.
Pass a callback down to your children that will update the value of key.
See the forked sandbox: https://codesandbox.io/s/react-functional-component-forked-vlxm5
Here are the docs: https://reactjs.org/docs/lists-and-keys.html#keys
const App = (props) => {
const [key, setKey] = useState(nanoid());
return (
<div>
<GridPixels key={key} reset={() => setKey(nanoid())} />
</div>
);
};
// then in your grid component
// ...
<button onClick={props.reset}> Reset Colors</button>

React load in component onClick

I'm trying to load in a component when a button is clicked but when I click on the button () in the below code nothing appears to be happening. I'm just trying to display a copied message and then have it disappear shortly after it appears to show the user the selected text was copied to their clipboard.
This is my current code:
import React, { useState } from 'react'
import Clipboard from 'react-clipboard.js';
const AddComponent = () => {
console.log("copied")
return (
<p className="copied">copied to clipboard!</p>
)
};
export default function Item(props) {
const { itemImg, itemName } = props
return (
<>
<Clipboard data-clipboard-text={itemName} onClick={AddComponent} className="item-container display-flex">
<img src={itemImg} alt={itemName} className="item-img" />
<h3>{itemName}</h3>
</Clipboard>
{AddComponent}
</>
)
}
mostly you want to have a state control, to conditionally render the given component like { isTrue && <MyComponent /> }. && operator only evaluates <MyComponent /> if isTrue has truthy value. isTrue is some state that you can control and change to display MyComponent.
in your case your onClick should be responsible to control the state value:
import React, { useState } from 'react'
export default function Item(props) {
const { itemImg, itemName } = props
const [isCopied, setIsCopied] = useState(false)
const onCopy = () => {
setIsCopied(true)
setTimeout(() => {
setIsCopied(false)
}, 600)
}
return (
<>
<div data-clipboard-text={itemName} onClick={onCopy} className="item-container display-flex">
<img src={itemImg} alt={itemName} className="item-img" />
<h3>bua</h3>
</div>
{isCopied && <AddComponent/>} // short circuit to conditional render
</>
)
}
you could consider check the repo react-toastify that implements Toast messages for you.
You'll want to have onClick be a regular function instead of a functional component, and in the regular function implement some logic to update the state of Item to record that the Clipboard was clicked. Then in Item, instead of always including <AddComponent />, only include it based on the state of Item.

Results are not populating after fetching from API using React

I am working with some examples to fetch the data from an API using fetch. But, it is returning nothing in view. What i am doing wrong? Why it is not populating? Here is a link to the code.
Application code here:
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import axios from 'axios';
import './style.css';
const url='https://10degrees.uk/wp-json/wp/v2/posts';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
searchInput: 'tirur',
avatarDatas: []
};
this.fetchData = this.fetchData.bind(this);
}
componentDidMount(){
this.fetchData(url);
}
fetchData = (apiToFetch)=>{
fetch(apiToFetch)
.then(result=>result.json())
.then(avatarData=>{
this.setState({
avatarDatas : avatarData
})
})
.catch((error) => console.log(error));
}
render() {
console.log(this.state.avatarDatas)
return (
<div>
<Hello name={this.state.name} />
<p>
{this.state.avatarDatas.map(item=>{
<div>{item}</div>
})}
</p>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Your code is fetching data from the remote URL correctly. However, you're using the map function in your render method wrong. You have:
{this.state.avatarDatas.map(item=>{
<div>{item}</div>
})}
This does indeed map through all the entries in avatarDatas, but the function the callback you've provided the map isn't returning anything. You've written a function block with a JSX statement in it, and no return statement. What you should have written is:
{this.state.avatarDatas.map(item=>{
return (<div>{item}</div>);
})}
or even this, where there isn't an actual function block but just a return value:
{this.state.avatarDatas.map(item=>
<div>{item}</div>
)}
At this point, the avatarDatas still won't be listed, because the JSX in your map callback is trying to have item rendered. item is an object, and React won't know how to convert it to a string. Rather do this:
{this.state.avatarDatas.map(item=>
<div>{item.title.rendered}</div>
)}
(Or any other of the many fields that each avatarData has.)
Finally, React may still issue a warning, saying that each item in a list must have a unique key. Whenever you use map to create a number of elements, React expects you to give a unique identifier to each element, which it will use to identify that element when it needs to update your list during rerendering. That means that you should do this:
{this.state.avatarDatas.map((item, index) =>
<div key={index}>{item.title.rendered}</div>
)}
Now, your map callback assigns an index to each <div> and all is well with the world.

How can I change what is rendred with a click event in React

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;

Resources