I'm trying to build a Flip card with ReactJS, that have inside 2 others components which are : Frontside and BackSide. These components should have children such as BackgroundCard or Sectioned Card. When I test the component I'm not getting anything on the screen and there is no errors in the console!
FlipContent.js
function FlipContent() {
const [setFront, setFrontState] = useState(true);
const [setBack, setBackState] = useState(false);
const [setFlipped, setFlippedState] = useState("");
function FlippingCard() {
setFrontState(setFront === true ? false : true);
setBackState(setBack === false ? true : false);
setFlippedState(setFlipped === "" ? "flipped" : "");
}
return (
<div className={`flip-content ${setFlipped}`} onClick={FlippingCard} >
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{setFront ? <FrontSide></FrontSide> : null}
{setBack ? <BackSide> </BackSide> : null}
</div>
</div>
);
}
And For the FrontSide/BackSide same this as this code
function FrontSide({ children }) {
return (
<div className="flip-content-front">
<div style={{ cursor: "pointer" }}>
{children}
</div>
</div>
);
}
and here how I'm trying to preview the component
function FlipPreview() {
return (
<Column>
<Row className={css(styles.title)} wrap flexGrow={1} horizontal="space-between" breakpoints={{ 768: 'column' }}>
Accordion <br></br>
</Row>
<FlipContent>
<FrontSide>
<CardBackgroundComponent title="Testing" image={image}></CardBackgroundComponent>
</FrontSide>
<BackSide>
<SectionedCardComponent
title="Notarum Black"
content="Powerful and reliable, this 15” HD laptop will not let you down. 256GB SSD storage, latest gen."
link=""
linkDescription="Add To Cart"
/>
</BackSide>
</FlipContent>
</Column>
);
}
I think you have not inserted something inside both component FrontSide, BackSide
<div className={`flip-content ${setFlipped}`} onClick={FlippingCard} >
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{setFront ? <FrontSide> It's front side </FrontSide> : null}
{setBack ? <BackSide> It's back-side </BackSide> : null}
</div>
</div>
So in your component you are not rendering children. So you need to update two things.
1) Taking the props in the FlipContent component as shown below
function FlipContent(props)
2) Use the props when rendering inside the component as shown below
{setFront ? <FrontSide>{props.children}</FrontSide> : null}
{setBack ? <BackSide>{props.children} </BackSide> : null}
the problem is in second step is it will load all the props of children , so you need to render only the specific component. See the below one
Update
There are multiple ways to solve this one will list one by one
solution one
By using the name prop of the children
function FlipContent(props) {
const [view, setView] = useState("FrontSide");
function FlippingCard() {
setView(view === "FrontSide" ? "BackSide" : "FrontSide");
}
const component = React.Children.map(props.children, child => {
if (view === child.type.name) {
return child;
}
});
return (
<div className={`flip-content`} onClick={FlippingCard}>
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{component}
</div>
</div>
);
}
Working codesandbox
Solution Two
Instead of adding statically the names can be driven from the prop, this can't handle same component multiple times
function FlipContent(props) {
const [view, setView] = useState(props.children[0].type.name);
const ref = useRef(0);
function FlippingCard() {
if (props.children.length - 1 === ref.current) {
ref.current = 0;
setView(props.children[0].type.name);
return;
}
setView(props.children[ref.current + 1].type.name);
ref.current += 1;
}
let component = <span />;
React.Children.forEach(props.children, child => {
if (view === child.type.name) {
component = child;
return;
}
});
return (
<div className={`flip-content`} onClick={FlippingCard}>
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{component}
</div>
</div>
);
}
Working codesandbox
Solution three
Rendering multiple components and in the same wrapper itself.
function FlipContent(props) {
const [component, setComponent] = useState(props.children[0]);
const ref = useRef(0);
function FlippingCard() {
if (props.children.length - 1 === ref.current) {
ref.current = 0;
setComponent(props.children[0]);
return;
}
setComponent(props.children[ref.current + 1]);
ref.current += 1;
}
return (
<div className={`flip-content`} onClick={FlippingCard}>
<div className="flip-content-container" style={{ cursor: "pointer" }}>
{component}
</div>
</div>
);
}
Working codesandbox
I feel solution three is the simplest one and you have the scalable way.
Related
I have created a react component that will display movie details after getting details from the TMDb API. The app working perfectly but there is one condition I am trying to handle i.e. when the movie is not found. In that, I case I want my background to be white instead of the movie poster. I want the same thing for title, overview, rating etc. I have used ternary operator for this.
However, my app still crashes this: -
Uncaught TypeError: Cannot read properties of undefined (reading 'backdrop_path')
at HeroArea
Here is my code: -
import React, { useState } from 'react'
import MovieDetailModal from '../MovieDetailsModal/MovieDetailModal';
import './HeroArea.css';
function HeroArea({ movie }) {
const [displayModal, setDisplayModal] = useState(false);
const displayMovieModal = () => setDisplayModal(true);
//Default background if movie.backdrop isn't found
const backdropImage = movie.backdrop_path !== null ?
{ backgroundImage: `url(https://image.tmdb.org/t/p/original/${movie.backdrop_path})` }
: {backgroundColor : "white"};
return (
<>
<MovieDetailModal status={displayModal} movie={movie} setStatus={setDisplayModal} />
<div className="hero-container" style={backdropImage} >
<div className="content-width info-container">
<div className="inner-container">
<h1>{movie.title ? movie.title : "No results found!"}</h1>
<p>{movie.overview ? movie.overview.substring(0, 250) : ""}...</p>
<button
onClick={displayMovieModal}
className="common-button view-more-button-hero">Display more</button>
</div>
</div>
</div>
</>
)
}
export default HeroArea
The short answer is, because movie is undefined, you can't access its properties.
You would need to make sure that movie is set before anything else for example:
const backdropImage = movie.backdrop_path !== null
? { backgroundImage:`url(https://image.tmdb.org/t/p/original/${movie.backdrop_path})` }
: {backgroundColor : "white"};
BECOMES
const backdropImage = movie && movie.backdrop_path !== null
? { backgroundImage:`url(https://image.tmdb.org/t/p/original/${movie.backdrop_path})` }
: {backgroundColor : "white"};
This also means you will need to make sure you only render this component if movie is set. for example:
return (
<>
<MovieDetailModal status={displayModal} movie={movie} setStatus={setDisplayModal} />
<div className="hero-container" style={backdropImage} >
<div className="content-width info-container">
<div className="inner-container">
<h1>{movie.title ? movie.title : "No results found!"}</h1>
<p>{movie.overview ? movie.overview.substring(0, 250) : ""}...</p>
<button
onClick={displayMovieModal}
className="common-button view-more-button-hero">Display more</button>
</div>
</div>
</div>
</>
)
BECOMES
if(movie){
return (
<>
<MovieDetailModal status={displayModal} movie={movie} setStatus={setDisplayModal} />
<div className="hero-container" style={backdropImage} >
<div className="content-width info-container">
<div className="inner-container">
<h1>{movie.title ? movie.title : "No results found!"}</h1>
<p>{movie.overview ? movie.overview.substring(0, 250) : ""}...</p>
<button
onClick={displayMovieModal}
className="common-button view-more-button-hero">Display more</button>
</div>
</div>
</div>
</>
)
}
return null;
I've got this code from a tutorial video , but first of all I didn't get the purpose of clk function and how it is related to h1 tag and that trinary operator.
second , how can I use normal if-else instead of ternary operator and not only for adding class but changing its style too.
import React, {useState} from 'react';
import "./App.css";
function App(){
let [isRed,setRed] = useState(false);
function clk(){
setRed(true);
}
return(
<div>
<h1 className={isRed?"red":""}>Change My Color</h1>
<button onClick={clk}>ClickHere</button>
</div>
);
}
export default App;
You can do that by applying this
<h1 className={`${isRed ? "red" : ""}`}>Change My Color</h1>
Or
{
isRed ? (
<h1 className={"red"}>Change My Color</h1>
) : (
<h1 className={"other"}>Change My Color</h1>
)
}
Enclose your elements inside of a {} makes it interpreted as js code
`
{if(isRed) return < h1>...< /h1> else return < h1>...< /h1>}
`
should work.. Maybe you can use the same inside the class attribute, but it will be hard to read.
As for the click function, it is setting the value of isRed to true. This will create the reactive change to your style.
You can bind a memo to your <h1> element that gets calculated when that particular state changes. Please note that JSX is not JavaScript. It may appear similar, and you may be able to use 99% of the syntax, but there are differences.
You can learn more about memoized values here: React / Docs / Hooks / useMemo
const { useMemo, useState } = React;
const App = () => {
let [isRed, setRed] = useState(false);
let [isBlue, setBlue] = useState(false);
const onClickRed = (e) => setRed(!isRed); // onclick callback
const onClickBlue = (e) => setBlue(!isBlue); // onclick callback
const headerPropsRed = useMemo(() => {
console.log('Red updated!');
let props = {};
if (isRed) {
props = {
...props,
className: 'red',
style: {
...props.style,
fontStyle: 'italic'
}
}
}
return props;
}, [ isRed ]);
const headerPropsBlue = useMemo(() => {
console.log('Blue updated!');
let props = {};
if (isBlue) {
props = {
...props,
className: 'blue',
style: {
...props.style,
fontStyle: 'italic'
}
}
}
return props;
}, [ isBlue ]);
return (
<div>
<h1 {...headerPropsRed}>Change My Color</h1>
<button onClick={onClickRed}>Click Here</button>
<h1 {...headerPropsBlue}>Change My Color</h1>
<button onClick={onClickBlue}>Click Here</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('react'));
.as-console-wrapper { max-height: 3em !important; }
h1 { font-size: 1em; }
.red { background: red; }
.blue { background: blue; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can use ternary operator working like this:
if(condition) ? "True part here" : "else part here (false part)"
Use case example :
const id = 1;
if(id === 1) ? "You are on right place" : "Sorry please check"
You can't use if-else in inline jsx but there exists some workarounds and you can choose whichever you want.
variant 1:
if(isRed){
const header = <h1 className='red' style={{ backgroundColor: 'red' ... }}>Change My Color</h1>
} else {
const header = <h1 style={{ backgroundColor: 'green' ... }}>Change My Color</h1>
}
return (
<div>
{header}
<button onClick={clk}>ClickHere</button>
</div>
);
variant 2: (but still ternary opeartor used)
if(isRed){
} else {
const header =
}
return (
<div>
{isRed ? (
<h1 className='red' style={{ backgroundColor: 'red' ... }}>Change My Color</h1>
) : (
<h1 style={{ backgroundColor: 'green' ... }}>Change My Color</h1>
)}
<button onClick={clk}>ClickHere</button>
</div>
);
There is no other way to replace ternary operator with if-else statement
I am making a Accordion and when we click each individual item then its opening or closing well.
Now I have implemented expand all or collapse all option to that to make all the accordions expand/collapse.
Accordion.js
const accordionArray = [
{ heading: "Heading 1", text: "Text for Heading 1" },
{ heading: "Heading 2", text: "Text for Heading 2" },
{ heading: "Heading 3", text: "Text for Heading 3" }
];
.
.
.
{accordionArray.map((item, index) => (
<div key={index}>
<Accordion>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text expandAll={expandAll}>
<p className="text">{item.text}</p>
</Text>
</Accordion>
</div>
))}
And text.js is a file where I am making the action to open any particular content of the accordion and the code as follows,
import React from "react";
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
{this.props.expandAll ? (
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
) : (
<div className={`content ${this.props.text ? "open" : ""}`}>
{this.props.text ? this.props.children : ""}
{this.props.text
? this.props.render && this.props.render(this.props.text)
: ""}
</div>
)}
</div>
);
}
}
export default Text;
Here via this.props.expandAll I am getting the value whether the expandAll is true or false. If it is true then all accordion will get the class className={`content open`} so all will gets opened.
Problem:
The open class is applied but the inside text content is not rendered.
So this line doesn't work,
{this.props.render && this.props.render(this.props.text)}
Requirement:
If expand all/collapse all button is clicked then all the accordions should gets opened/closed respectively.
This should work irrespective of previously opened/closed accordion.. So if Expand all then it should open all the accordion or else needs to close all accordion even though it was opened/closed previously.
Links:
This is the link of the file https://codesandbox.io/s/react-accordion-forked-sm5fw?file=/src/GetAccordion.js where the props are actually gets passed down.
Edit:
If I use {this.props.children} then every accordion gets opened.. No issues.
But if I open any accordion manually on click over particular item then If i click expand all then its expanded(expected) but If I click back Collapse all option then not all the accordions are closed.. The ones which we opened previously are still in open state.. But expected behavior here is that everything should gets closed.
In your file text.js
at line number 9. please replace the previous code by:
{this.props.children}
Tried in the sandbox and worked for me.
///
cant add a comment so editing the answer itself.
Accordian.js contains your hook expandAll and the heading boolean is already happening GetAccordian.js.
I suggest moving the expand all to GetAccordian.js so that you can control both values.
in this case this.props.render is not a function and this.props.text is undefined, try replacing this line
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
by this:
<div className={`content open`}>
{this.props.children}
</div>
EDIT: //
Other solution is to pass the expandAll property to the Accordion component
<Accordion expandAll={expandAll}>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text>
<p className="text">{item.text}</p>
</Text>
</Accordion>
then in getAccordion.js
onShow = (i) => {
this.setState({
active: this.props.expandAll ? -1: i,
reserve: this.props.expandAll ? -1: i
});
if (this.state.reserve === i) {
this.setState({
active: -1,
reserve: -1
});
}
};
render() {
const children = React.Children.map(this.props.children, (child, i) => {
return React.cloneElement(child, {
heading: this.props.expandAll || this.state.active === i,
text: this.props.expandAll || this.state.active + stage === i,
onShow: () => this.onShow(i)
});
});
return <div className="accordion">{children}</div>;
}
};
Building off of #lissettdm answer, it's not clear to me why getAccordion and accordion are two separate entities. You might have a very valid reason for the separation, but the fact that the two components' states are interdependent hints that they might be better implemented as one component.
Accordion now controls the state of it's children directly, as before, but without using getAccordion. Toggling expandAll now resets the states of the individual items as well.
const NormalAccordion = () => {
const accordionArray = [ //... your data ];
const [state, setState] = useState({
expandAll: false,
...accordionArray.map(item => false),
});
const handleExpandAll = () => {
setState((prevState) => ({
expandAll: !prevState.expandAll,
...accordionArray.map(item => !prevState.expandAll),
}));
};
const handleTextExpand = (id) => {
setState((prevState) => ({
...prevState,
[id]: !prevState[id]
}));
};
return (
<>
<div className="w-full text-right">
<button onClick={handleExpandAll}>
{state.expandAll ? `Collapse All` : `Expand All`}
</button>
</div>
<br />
{accordionArray.map((item, index) => (
<div key={index}>
<div className="accordion">
<Heading handleTextExpand={handleTextExpand} id={index}>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text shouldExpand={state[index]}>
<p className="text">{item.text}</p>
</Text>
</div>
</div>
))}
</>
);
};
Heading passes back the index so the parent component knows which item to turn off.
class Heading extends React.Component {
handleExpand = () => {
this.props.handleTextExpand(this.props.id);
};
render() {
return (
<div
style={ //... your styles}
onClick={this.handleExpand}
>
{this.props.children}
</div>
);
}
}
Text only cares about one prop to determine if it should display the expand content.
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
<div
className={`content ${this.props.shouldExpand ? "open" : ""}`}
>
{this.props.shouldExpand ? this.props.children : ""}
</div>
</div>
);
}
}
I am working on a project and i want to display a hidden <div> below another <div> element using an event handler but when i click the icon that is meant to display the div, the whole page becomes blank
This is image I want:
This is what i get
I have tried to check through the internet for some places where i could get the solution. Well i found something similar to what i had done but the error still happens for me.
class PostItTeaser extends Component {
state = {
postIt: false,
moreIt: false,
}
togglePostIt = e => {
e ? e.preventDefault() : null
this.setState({ postIt: !this.state.postIt })
}
_toggle = e => {
e ? e.preventDefault() : null
this.setState({
moreIt: !this.state.moreIt,
})
}
Child = () => <div className="modal">Hello, World!</div>
render() {
let { postIt } = this.state
let { moreIt } = this.state
let {
type,
group,
disabled,
session: { id, username },
} = this.props
return (
<div>
<div
className="post_it inst"
style={{ marginBottom: type == 'group' && 10 }}
>
<img src={`/users/${id}/avatar.jpg`} alt="Your avatar" />
<div className="post_teaser">
<span
className="p_whats_new"
onClick={disabled ? null : this.togglePostIt}
>
What's new with you, #{username}? #cool
</span>
<span className="m_m_exp" data-tip="More" onClick={this._toggle}>
<MaterialIcon icon="expand_more" />
</span>
</div>
</div>
{moreIt && <Child />}
{postIt && (
<PostIt back={this.togglePostIt} type={type} group={group} />
)}
</div>
)
}
}
From skimming through the code I believe you need to bind the scope, since the function you're calling is using this.setState, it needs this to be the react component, not the event you're listening to:
onClick={this._toggle.bind(this)}
You can also bind the functions scope in the constructor. Or, a less memory performant & ugly way:
onClick={() => { this._toggle(); } }
I made a stateless component with an internal variable to reference an input, as below. This is working fine.
const MyStatelessComp = ({ team, teamProgress, onSet, editing, enableEdit }) => {
let input
return (
<div>
<div className="team__goal-target_header" >Team's Savings Target</div>
<div className="team__goal-target_value" >
M$
<input
ref={ el => input = el }
style={{width: '75px', border: 'none'}}
onChange={() => onSet({teamId: team.id, goalValue: parseInt(input.value, 10) || 0}) }
/>
<div
ref={ el => input }
style={{
display: !input || (!isNaN(parseFloat(input.value)) && isFinite(input.value)) ? 'none' : 'block'
}}
>Must be numeric</div>
</div>
</div>
)
}
I want to validate input and display a notification Must be numeric is the anything that cannot be converted to a number is entered into my input field. That is not working however. How do I make input in the context of the "warning div" reference the value of the input?
Realize that this is not an unorthodox way to working with stateless components, but it would save me lots of pain.
Thank you.
Why use a stateless component when he can be a simple statefull component ?
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
render() {
const isNumber = !isNaN(this.state.value);
return (
<div>
<div className="team__goal-target_header">Team's Savings Target</div>
<div className="team__goal-target_value">
M$
<input
style={{ width: "75px", border: "none" }}
onChange={this.handleChange}
value={this.state.value}
/>
{isNumber ? "" : <div>Must be numeric</div>}
</div>
</div>
);
}
}
You can also toggle the div content or create a new alert component and toggle this component.