React load in component onClick - reactjs

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.

Related

Convert React Class to a Functional Component

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

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.memo - why is my equality function not being called?

I have a parent component that renders a collection of children based on an array received via props.
import React from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import { Content } from 'components-lib';
import Child from '../Child';
const Parent = props => {
const { items } = props;
return (
<Content layout='vflex' padding='s'>
{items.map(parameter => (
<Child parameter={parameter} key={shortid.generate()} />
))}
</Content>
);
};
Parent.propTypes = {
items: PropTypes.array
};
export default Parent;
Every time a new item is added, all children are re-rendered and I'm trying to avoid that, I don't want other children to be re-rendered I just want to render the last one that was added.
So I tried React.memo on the child where I'll probably compare by the code property or something. The problem is that the equality function never gets called.
import React from 'react';
import PropTypes from 'prop-types';
import { Content } from 'components-lib';
const areEqual = (prevProps, nextProps) => {
console.log('passed here') // THIS IS NEVER LOGGED!!
}
const Child = props => {
const { parameter } = props;
return <Content>{parameter.code}</Content>;
};
Child.propTypes = {
parameter: PropTypes.object
};
export default React.memo(Child, areEqual);
Any ideas why?
In short, the reason of this behaviour is due to the way React works.
React expects a unique key for each of the components so it can keep track and know which is which. By using shortid.generate() a new value of the key is created, the reference to the component changes and React thinks that it is a completely new component, which needs rerendering.
In your case, on any change of props in the parent, React will renrender all of the children because the keys are going to be different for all of the children as compared to the previous render.
Please reference this wonderful answer to this topic
Hope this helps!
I was having the same issue and the solution turned out to be just a novice mistake. Your child components have to be outside of the parent component. So instead of:
function App() {
const [strVar, setStrVar] = useState("My state str");
const MyChild = React.memo(() => {
return (
<Text>
{strVar}
</Text>
)
}, (prevProps, nextProps) => {
console.log("Hello"); //Never called
});
return (
<MyChild/>
)
}
Do it like this:
const MyChild = React.memo(({strVar}) => {
return (
<Text>
{strVar}
</Text>
)
}, (prevProps, nextProps) => {
console.log("Hello");
});
function App() {
const [strVar, setStrVar] = useState("My state str");
return (
<MyChild strVar = {strVar}/>
)
}
Another possibility for unexpected renders when including an identifying key property on a child, and using React.memo (not related to this particular question but still, I think, useful to include here).
I think React will only do diffing on the children prop. Aside from this, the children prop is no different to any other property. So for this code, using myList instead of children will result in unexpected renders:
export default props => {
return (
<SomeComponent
myLlist={
props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)
}
/>
)
}
// And then somewhere in the MyComponent source code:
...
{ myList } // Instead of { children }
...
Whereas this code (below), will not:
export default props => {
return (
<SomeComponent
children={
props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)
}
/>
)
}
And that code is exactly the same as specifying the children prop on MyComponent implicitly (except that ES Lint doesn't complain):
export default props => {
return (
<SomeComponent>
{props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)}
</SomeComponent>
)
}
I don't know the rest of your library but I did some changes and your code and (mostly) seems to work. So, maybe, it can help you to narrow down the cause.
https://codesandbox.io/s/cocky-sun-rid8o

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;

Conditonal component still renders - ReactJS

I'm learning react and wanted to try creating a loading component that shows a loading text until a condition is met i.e. the props has the correct information.
The problem is that the element is still loading even if the condition is not met:
Leading Component:
import React from 'react';
const Loading = ({ condition, children }) => (<div>{condition ? children :
'Loading'}</div>);
export default Loading;
Here is my render method for a component that uses the Loading Component:
return
(<Loading condition={props.data && props.data.result && props.data.result.length > 1}>
<div> { ViewHelper.getCatalogItems(props.data) }</div></Loading>);
Now my problem is I'm getting an error when calling { ViewHelper.getCatalogItems(props.data) } becauuse props.data is undefined however I was hoping that the Loading Component wouldn't call the function if the ternary condition in the LoadingComponent was false.
if I change ViewHelper.getData to just some string value, everything seems to work and 'Loading ' is displayed.
Thanks
The fact that Loading component doesn't use children doesn't mean that children aren't rendered. Otherwise there would be nothing to pass as props.children to parent component.
As can be seen in this example, child expression is evaluated despite children prop ignored in parent component.
A proper way to handle this and prevent eager children rendering is to use render prop recipe, which is also known as function as a child:
const Loading = ({ condition, children }) => (
<div>{condition && children ? children() : 'Loading'}</div>
);
...
<Loading condition={props.data && props.data.result && props.data.result.length > 1}>
{() => (
<div> { ViewHelper.getCatalogItems(props.data) }</div>
)}
</Loading>
Notice that since props.children is a function, it's used as children() in ternary expression.
Or use a HOC for components:
const withLoading = (Comp) =>
({ condition, ...props }) => (
<div>{condition ? <Comp {...props} /> : 'Loading'}</div>
)
);
...
const LoadingCatalogItemsComponent = withLoading(CatalogItemsComponent);
the children parameter received in the Loading component will have already rendered in the parent (try logging the content of children from Loading)
getCatalogItems will be called regardless of the conditional
cases where props.data need to be handled here. there are multiple ways to do this:
defaultProps or type checking in general
input validation for getCatalogItems
destructuring assignment
default function parameters
Updated answer: as estus points out below, render props are probably a good way to do this. Here's an example:
import React from "react";
import ReactDOM from "react-dom";
const Loading = ({ condition, render }) => {
if (condition) {
return render();
} else {
return "Loading";
}
};
const Thing = ({ data }) => {
console.log(data);
return data.map(d => <li>{d}</li>);
};
class App extends React.Component {
state = {
data: [1, 2, 3]
};
render() {
return (
<Loading
condition={this.state.data.length > 1}
render={() => {
return <Thing data={this.state.data} />;
}}
/>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox here.
As #estus said, HOCs and render props are two popular methods. Moving:
<div>{ ViewHelper.getCatalogItems(props.data)}</div>
into its own component (which is passes the prop, e.g., <CatalogItems {...props} />) will also stop you from getting errors. As long as the code isn't in the actual render method; otherwise, it's fired, regardless of whether React would have actually rendered it.
Example:
const Loading = ({ condition, children }) => (
<div>{condition ? children : "Loading in 3 seconds"}</div>
);
// now that it's in its own component the code isn't run until the component actually renders
const CatalogItems = ({ data }) => data.result.map(item => item);
class App extends React.Component {
state = {
data: null
};
// dummy API call
componentDidMount() {
setTimeout(
() => this.setState({ data: { result: ["cat ", "dog ", "mouse "] } }),
3000
);
}
render() {
const props = this.state; // let's just pretend these were inherited props
return (
<Loading
condition={
props.data && props.data.result && props.data.result.length > 1
}
data={props.data}
>
<CatalogItems {...props} />
</Loading>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id='root'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.3.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.3.1/umd/react-dom.development.js"></script>

Resources