I would like to add a transition effect when the button is clicked in the following React component but as this code changes the actual JSX content rather than the className I don't think this can be done in CSS. How can I add a transition effect in this case?
import React, { useState } from 'react';
import ChatItem from './ChatItem';
import ChatButton from './assets/ChatButton';
import classes from './ChatBot.module.css';
const ChatList = (props) => {
const [selected, setSelected] = useState(props.items[0]);
function handleChangeSelected(item) {
setSelected(item);
}
const questionButtons = props.items.map((item) => {
if (item.id === selected.id) return null;
return (
<ChatButton onClick={handleChangeSelected.bind(null, item)}>
{item.question}
</ChatButton>
);
});
return (
<div className={classes.faq}>
<section className="textbox">
<ChatItem
key={selected.id}
id={selected.id}
question={selected.question}
answer={selected.answer}
/>
</section>
<div className={classes.questions}>{questionButtons}</div>
</div>
);
};
export default ChatList;
It is possible with some sort of library like react transition group. But I think it might be easier to apply a className for this if it's not a complex animation. Then apply your transition on the button. If in the way you could use visibility.
<ChatButton className={item.id === selected.id && 'active'} onClick={handleChangeSelected.bind(null, item)}>
{item.question}
</ChatButton>
Related
I am an absolute beginner with React, I have a component where I need to render the value of a state to the Dom but it doesnt work let me show you my code.
Links.jsx
import React, { useEffect, useState } from 'react'
import { Collapse } from 'antd'
const Links = () => {
const [isMobile, setIsMobile] = useState(false)
return (
<div>
<div>asd2</div>
<div>{ isMobile }</div>
<div>asd5</div>
</div>
)
}
export default Links
I am using it like
footer.jsx
import Links from './links'
const Footer = ({
globalData,
user
}) => {
return(
<Links/>
)
}
it does render the text asd2 and asd5 meaning the component itself works just not the state value. I've looked everywhere this is how everybody does it .What am I doing wrong?
Instead
<div>{ isMobile }</div>
Try this
<div>{ isMobile.toString()}</div>
Or this
<div>{ isMobile ? "true" : "false"}</div>
Even this:
<div>{`${isMobile}`}</div>
Type boolean cannot be rendered in JSX.
You need to convert isMobile it to a string. false, null & undefined these values are not rendered.
You can't render a Boolean.
Use it like this:
{ isMobile.toString() }
You can't render a Boolean value. Use it like this:
{ isMobile.toString() }
This is my first time dealing with Gatsby and React, so I might be using the wrong approach on this matter. Anyway, this is what is going on.
From the gatsby-starter-hello-world, I'm building this site that will be composed of a front page with a Hero on the top, holding the intro information. Right bellow, I'm intending to insert some content (I'm not sure about what yet), with this <Header /> appearing on scroll. For that part, I'm intending on use Headroom.js, which already works in the site.
The thing is I need it to be triggered only after the bottom of the Hero component touches the top of the viewport. And this will happen only in desktop and laptops. On mobile I intend to make it a fixed navbar.
Anyway, right now, this is what I have for a Layout.js
import React from "react"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import Hero from "./index/hero"
import Header from "./header"
import Headroom from "react-headroom"
const Layout = ({ children }) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`)
function() getHeroSize {
var heroHeight = Hero.clientHeight;
}
return (
<>
<Hero siteTitle={data.site.siteMetadata.title} ref="inner" />
<div className={"container mx-auto"}>
<Headroom pinStart={heroHeight}>
<Header siteTitle={data.site.siteMetadata.title} />
</Headroom>
<div
//style={{
// margin: `0 auto`,
// maxWidth: 960,
// padding: `0px 1.0875rem 1.45rem`,
// paddingTop: 0,
//}}
>
<main>{children}</main>
<footer>
© {new Date().getFullYear()}, Construído com
{` `}
Gatsby
</footer>
</div>
</div>
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
The function getHeroSize above is more like an intention display of what I'm thinking. It doesn't really work.
I'm using Tailwind CSS and sourcing some content from Trello using gatsby-source-trello. Not yet sure how to make this Layout.js, but this is what I've got from some testing that worked pretty good, so far. I understand that there'll be some work to do within gatsby-node.js, but I believe this header will be there in any other page I create, so.
Any thoughts, suggestions or links to documentation would be really appreaciated. Thanks in advance!
Your Hero component would need to use forwardRef to pass the ref to the nearest React element:
const Hero = React.forwardRef((props, ref) =>
<div ref={ref}>
{props.title}
</div>
Then you'll need to create a ref in Layout and pass that to Hero:
import React, { useRef } from "react"
const Layout = ({ children }) => {
const heroRef = useRef()
// ...
return (
<>
<Hero title={data.site.siteMetadata.title} ref={heroRef} />
{/* ... */}
</>
)
}
Finally, you'll want to use a hook to measure the actual height of the heroRef.current element. Here's one I use:
import { useEffect, useState } from "react"
import debounce from "lodash/debounce"
export default (ref, ttl = 100) => {
const [dimensions, setDimensions] = useState()
useEffect(() => {
if (!ref.current) return
const measure = debounce(() => {
if (ref.current) {
setDimensions(ref.current.getBoundingClientRect())
}
}, ttl)
measure()
window.addEventListener("resize", measure)
return () => {
window.removeEventListener("resize", measure)
}
}, [ref, ttl])
return dimensions
}
And here's how you might add that to Layout:
import useElementDimensions from "hooks/useElementDimensions"
const Layout = ({ children }) => {
const heroRef = useRef()
const heroDims = useElementDimensions(heroRef) || { top: 0, height: 0 }
const heroBottom = heroDims.top + heroDims.height
return (
<Headroom pinStart={heroBottom}>
<Header siteTitle={data.site.siteMetadata.title} />
</Headroom>
)
}
I have three components in my React app, all set to grey but can turn a different color when clicked, and the idea is to have the other components change back to grey whenever one of the components turns from grey to it's chosen color. Another way to phrase it is that I want only one components to show it's color at a time.
Here is the index.js for my page.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Redlight from "./Components/Redlight";
import Yellowlight from "./Components/Yellowlight";
import Greenlight from "./Components/Greenlight";
// probably add "isActive" line here? need to
// figure out how to get the components to listen
// to eachother
function App() {
return (
<div className="App">
<Redlight />
<Yellowlight />
<Greenlight />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
And here is one of the components. All the components are written the exact same way, so consider this one to count as all three besides the obvious differences of color.
import React from "react";
class Redlight extends React.Component {
state = {
className: "Offlight"
};
handleClick = e => {
e.preventDefault();
console.log("The red light was clicked.");
const currentState = this.state.className;
this.setState({
className: !currentState
});
};
render() {
return (
<div
className={this.state.className ? "Offlight" : "Redlight"}
onClick={this.handleClick}
/>
);
}
}
export default Redlight;
So far all the components show up and change color on click, but it's the matter of getting them all to listen to each other that is really hanging me up.
Try moving your state to your root component, which in your case is App, then make App stateful and Redlight, Bluelight, and Greenlight stateless.
If you turn your App component into a stateful component, you can pass the state of the lights down to the children component while at the same time you can manage their states in the parent's component level.
For example, for your App.js, do something like:
import React, { Component } from "react";
import Redlight from "./Redlight";
import Yellowlight from "./Yellowlight";
import Greenlight from "./Greenlight";
import "./App.css";
class App extends Component {
state = {
red: "Offlight",
yellow: "Offlight",
green: "Offlight"
};
clickHandler = (light, e) => {
e.preventDefault();
console.log("The " + light + " light was clicked.");
const currentState = this.state[light];
const newState = {
red: "Offlight",
yellow: "Offlight",
green: "Offlight"
};
this.setState({
...newState,
[light]: !currentState
});
};
render() {
return (
<div className="App">
<Redlight light={this.state.red} clicked={this.clickHandler} />
<Yellowlight light={this.state.yellow} clicked={this.clickHandler} />
<Greenlight light={this.state.green} clicked={this.clickHandler} />
</div>
);
}
}
export default App;
If you see, the state in the parent is controlling the class name for the lights, and the clickHandler turns all of them off, them turns the clicked one on.
Your children components can be cleaner, like this:
Yellowlight.js
import React from "react";
class Yellowlight extends React.Component {
render() {
return (
<div
className={this.props.light ? "Offlight" : "Yellowlight"}
onClick={(e) => this.props.clicked('yellow', e)}
/>
);
}
}
export default Yellowlight;
Redlight.js
import React from "react";
class Redlight extends React.Component {
render() {
return (
<div
className={this.props.light ? "Offlight" : "Redlight"}
onClick={(e) => this.props.clicked('red', e)}
/>
);
}
}
export default Redlight;
Greenlight.js:
import React from "react";
class Greenlight extends React.Component {
render() {
return (
<div
className={this.props.light ? "Offlight" : "Greenlight"}
onClick={(e) => this.props.clicked('green', e)}
/>
);
}
}
export default Greenlight;
You can check the final code in this sandbox where I tried to replicate your problem: https://codesandbox.io/s/friendly-haibt-i4nck
I suppose you are looking for something like this
What you need, is to lift up the state in order to hold the selected color/light into it. For this example, I'm using the state hook instead of the class component approach. I've also created one <Light /> component as they share the same logic.
You can find inline comments of what is going on for each of the steps.
Let me know if you find any trouble reading or implementing it.
What you want to do is to lift the state up to a common ancestor. You can create a common ancestor to host the information that all 3 light components need. You can pass a boolean isLightOn to tell the component to switch on or off. You will also pass an event handler to allow a light component to set the light color.
const LightContainer = () => {
const [lightColor, setLightColor] = useState('');
const RED_COLOR = 'RED';
const YELLOW_COLOR = 'YELLOW';
const GREEN_COLOR = 'GREEN';
return (
<div>
<Light className="Redlight" isLightOn={lightColor === RED_COLOR} onLightSwitch={() => { setLightColor(RED_COLOR); }} />
<Light className="Yellowlight" isLightOn={lightColor === YELLOW_COLOR} onLightSwitch={() => { setLightColor(YELLOW_COLOR); }} />
<Light className="Greenlight" isLightOn={lightColor === GREEN_COLOR} onLightSwitch={() => { setLightColor(GREEN_COLOR); }} />
</div>
);
};
On your light components, we can also make it more generic since we move most of the logic to the parent component.
const Light = ({ isLightOn, onLightSwitch, className }) => {
return (
<div
className={isLightOn ? 'Offlight' : className}
onClick={onLightSwitch}
/>
);
};
Where's the right place to put code that interacts with the DOM in a gatsby site? I want to toggle the visibility of some components by adding/removing a class when another element is clicked.
The gatsby-browser.js file seems like it should contain this code but the API doesn't seem to call any of the functions after the DOM has loaded.
Similarly, using Helmet calls it too soon. Using window.onload never seems to trigger at all regardless of where it's included.
window.onload = function () {
// add event listener to the toggle control
}
Is there an event I can use to run my code when the DOM is ready?
Do you really need to wait for the DOM to be ready? When working in react you need to change the way you think about these things. For example you could add an on click that changes state and then reflect the state change in your classname prop.
Code Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the initial state
return (
<div>
<div className={visible ? "visible-class" : "hidden-class"}>
My content
</div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
</div>
)
}
export default MyApp
Or you could take it a step further and not even render that content to the DOM until you want to.
Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the inital state
return (
<div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
{visible && <div>My content here</div>}
</div>
)
}
export default MyApp
You can use the React cyclelife with componentDidMount().
This need to update your component like that :
import React from 'react'
class YourComponent extends React.Component {
componentDidMount() {
// Your Javascript function here
}
render() {
return(
<div className="YourComponentHere"></div>
)
}
}
export default YourComponent
Hope that help you!
If your component is a functional component, try using React Hook useEffect, which will guarantee the execution after the component is rendered.
import React, { useEffect } from 'react'
const MyComponent = () => {
useEffect(() => {
console.log("Document loaded");
});
return (
<main>
<text>Pretty much the component's body code around here</text>
</main>
)
}
export default MyComponent
Working with an array of mapped items, I am attempting to toggle class in a child component, but state change in the parent component is not passed down to the child component.
I've tried a couple different approaches (using {this.personSelectedHandler} vs. {() => {this.personSelectedHandler()} in the clicked attribute, but neither toggled class successfully. The only class toggling I'm able to do affects ALL array items rendered on the page, so there's clearly something wrong with my binding.
People.js
import React, { Component } from 'react';
import Strapi from 'strapi-sdk-javascript/build/main';
import Person from '../../components/Person/Person';
import classes from './People.module.scss';
const strapi = new Strapi('http://localhost:1337');
class People extends Component {
state = {
associates: [],
show: false
};
async componentDidMount() {
try {
const associates = await strapi.getEntries('associates');
this.setState({ associates });
}
catch (err) {
console.log(err);
}
}
personSelectedHandler = () => {
const currentState = this.state.show;
this.setState({
show: !currentState
});
};
render() {
return (
<div className={classes.People}>
{this.state.associates.map(associate => (
<Person
name={associate.name}
key={associate.id}
clicked={() => this.personSelectedHandler()} />
))}
</div>
);
}
}
export default People;
Person.js
import React from 'react';
import classes from './Person.module.scss';
const baseUrl = 'http://localhost:1337';
const person = (props) => {
let attachedClasses = [classes.Person];
if (props.show) attachedClasses = [classes.Person, classes.Active];
return (
<div className={attachedClasses.join(' ')} onClick={props.clicked}>
<img src={baseUrl + props.photo.url} alt={props.photo.name} />
<p>{props.name}</p>
</div>
);
};
export default person;
(Using React 16.5.0)
First of all, in your People.js component, change your person component to:
<Person
name={associate.name}
key={associate.id}
clicked={this.personSelectedHandler}
show={this.state.show}}/>
You were not passing the prop show and also referring to a method inside the parent class is done this way. What #Shawn suggested, because of which all classes were toggled is happening because of Event bubbling.
In your child component Person.js, if you change your onClick to :
onClick={() => props.clicked()}
The parenthesis after props.clicked executes the function there. So, in your personSelectedHandler function, you either have to use event.preventDefault() in which case, you also have to pass event like this:
onClick={(event) => props.clicked}
and that should solve all your problems.
Here's a minimal sandbox for this solution:
CodeSandBox.io