React onClick add class to clicked element but remove from the others - javascript

so what i try to achieve here is very similar to what is done here Transition flex-grow of items in a flexbox
But what i wonder how this could be done with React say i have this code
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
classNameToUse: ''
};
this.onElementClicked = this.onElementClicked.bind(this);
}
onElementClicked() {
this.setState({ classNameToUse : 'big-size'})
}
render() {
return (
<div>
<div className={this.state.classNameToUse} onClick={this.onElementClicked} >
something
</div>
<div className={this.state.classNameToUse onClick={this.onElementClicked} >
something else
</div>
</div>
);
}
}
This would of course add the className to them both but what i want to achieve is that one of them grows big with animation and the other collapse. And it sohuldnt matter if i have 2 or 10 elements

You can set active index on click:
// State
this.state = {
activeIndex: null
};
// event
onElementClicked(e) {
this.setState({ activeIndex: e.target.index })
}
// class to use
className={this.index === this.state.activeIndex ? 'big-size' : ''}

const { useState, useEffect } = React;
const App = () => {
const [divs,] = useState(['blue', 'green', 'black']);
const [selected, setSelected] = useState(null);
const onClick = (id) => {
setSelected(id);
}
return <div className="container">
{divs.map(pr => <div key={pr} style={{background: pr}} className={`box ${pr === selected ? 'selected' : ''}`} onClick={() => onClick(pr)}></div>)}
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
.container {
display: flex;
height: 200px;
}
.box {
flex: 1;
cursor: pointer;
transition: all .3s ease-in;
}
.selected {
flex: 2;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/#material-ui/core#latest/umd/material-ui.development.js"></script>
<script src="https://unpkg.com/material-ui-lab-umd#4.0.0-alpha.32/material-ui-lab.development.js"></script>
<div id="root"></div>

Related

framer.motion animation is instant instead of animating

I have several boxes that I want to animate through,
Here's a simple app example (Also a codesandbox here)
Each "box" should fade in and fade out, however, in this example, the animation happens isntantly.
const Box = styled.div`
width: 100px;
height: 100px;
background: green;
`;
const Test = ({ isActive }) => {
return (
<motion.div
animate={isActive ? { opacity: 1 } : { opacity: 0 }}
transition={{ duration: 3 }}
>
<Box>hello world</Box>
</motion.div>
);
};
export default function App() {
const [currentIndex, setCurrentIndex] = useState(0);
const boxes = [
{
component: ({ isActive }) => <Test isActive={isActive} />
},
{
component: ({ isActive }) => <Test isActive={isActive} />
}
];
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div onClick={() => setCurrentIndex(currentIndex === 0 ? 1 : 0)}>
{boxes.map((box, index) => {
const isActive = index === currentIndex;
return <box.component isActive={isActive} />;
})}
</div>
</div>
);
I have never used framer.motion before, but looking at their documentation, I think you can use variants, to achieve what you need. https://www.framer.com/api/motion/examples/
I've slightly refactored your code, to get it working:
import "./styles.css";
import { motion } from "framer-motion";
import styled from "styled-components";
import { useEffect, useState } from "react";
const Box = styled.div`
width: 100px;
height: 100px;
background: green;
`;
const variants = {
open: { opacity: 1 },
closed: { opacity: 0 }
};
const Test = ({ index, currentIndex }) => {
return (
<motion.div
animate={index === currentIndex ? "open" : "closed"}
variants={variants}
transition={{ duration: 3 }}
>
<Box>hello world</Box>
</motion.div>
);
};
export default function App() {
const [currentIndex, setCurrentIndex] = useState(0);
const boxes = ["a", "b"];
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>{currentIndex}</h2>
<div onClick={() => setCurrentIndex(currentIndex === 0 ? 1 : 0)}>
{boxes.map((box, i) => {
return <Test index={i} currentIndex={currentIndex} />;
})}
</div>
</div>
);
}
the currentIndex is passed as props to the child Test components, they check themselves whether their index matches the currentIndex and update their animations accordingly.
Edited codesandbox here: https://codesandbox.io/s/suspicious-austin-tymvx
In framer motion, you have useCycle properties. Here is an example.
Code in example:
import * as React from "react";
import { render } from "react-dom";
import { Frame, useCycle } from "framer";
import "./styles.css";
export function MyComponent() {
const [animate, cycle] = useCycle(
{ scale: 1.5, rotate: 0, opacity: 1 },
{ scale: 1.0, rotate: 90, opacity: 0 }
);
return (
<Frame
animate={animate}
onTap={() => cycle()}
size={150}
radius={30}
background={"#fff"}
/>
);
}
const rootElement = document.getElementById("root");
render(<MyComponent />, rootElement);
and some simple css:
body {
margin: 0;
padding: 0;
}
#root {
font-family: sans-serif;
text-align: center;
width: 100vw;
height: 100vh;
display: flex;
place-content: center;
place-items: center;
background: rgba(0, 85, 255, 1);
margin: 0;
padding: 0;
perspective: 1000px;
}
I don't recommend you to use this type of construction: animate={index === currentIndex ? "open" : "closed"} , because you might have some lagging/breaking animation.
Try always to search examples/elements of MotionAPI lib. You will have less code lines and mostly "clean" code with no useless variables.

why ReactDom.render() returns null when triggered from setState callback?

I encountered this very weird behaviour of react and created a simple demo in below.
The behaviour is that when I do this.renderPortal() directly as in line 182, I can close the Dialog. But When do it from the setState callback like this:
return <div onClick={()=>{
// this.renderPortal() //worker fine
this.setState({
a : 1
}, ()=>{
this.renderPortal()
})
}}> click me</div>
I cannot close it, the console will prompt Cannot read property 'destroy' of null.
It seemed that react thinks the Dialog rendered from setState callback stateless, Why is that?
let rootDialogContainer = document.createElement("div");
document.body.appendChild(rootDialogContainer);
function createConfig(config, defaults) {
return Object.assign(
{},
defaults,
typeof config === "string" ? { content: config } : config
);
}
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
show: true
};
this.cancel = this.cancel.bind(this);
}
ok() {
if (this.props.autoClose) {
this.setState({ show: false });
}
const { onOk, afterClose } = this.props;
onOk && onOk();
afterClose();
}
cancel() {
if (this.mounted && this.props.autoClose) {
this.setState({ show: false });
}
const { onCancel, afterClose } = this.props;
onCancel && onCancel();
afterClose();
}
destroy() {
this.props.forceClose();
}
componentDidMount() {
this.mounted = true
}
componentWillUnmount() {
this.mounted = false
}
render() {
let DialogType = this.props.dialog || Dialog;
return (
<DialogType
{...this.props}
show={this.state.show}
onOk={this.ok}
onCancel={this.cancel}
/>
);
}
}
class Dialog extends React.Component{
constructor(props) {
super(props)
}
renderDialogWrap(params) {
let {
contentElm,
onCancel
} = params;
return (
<div className={"dialog"}>
<div style={{}} className={"inner-dialog"}>
{contentElm}
<div className="button" onClick={onCancel}>
cancel
</div>
</div>
</div>
);
}
renderPortal() {
return <Modal />;
}
render() {
let props = this.props;
let {
show,
className,
title,
content,
onCancel = () => {},
onOk = () => {},
children,
renderAsHiddenIfNotShow = false,
} = props;
return ReactDOM.createPortal(
<div
style={{ display: show ? "" : "none" }}
>
{this.renderDialogWrap({
onCancel,
contentElm: children,
show,
renderAsHiddenIfNotShow
})}
</div>,
document.body
);
}
}
Dialog.show = function(params) {
const config = createConfig(params, {
show: true,
autoClose: true,
onOk: () => {},
onCancel: () => {}
});
config.content = config.content || config.desc;
let container = rootDialogContainer
if (config.id) {
let containerId = `wrapper`
container = document.getElementById(containerId)
if (!container) {
container = document.createElement('div')
container.setAttribute('id', containerId)
document.body.appendChild(container)
}
}
config.forceClose = function(){
ReactDOM.unmountComponentAtNode(container);
};
config.afterClose = function() {
config.autoClose && config.forceClose();
};
return ReactDOM.render(this.buildModal(config), container);;
};
class Wrapper extends React.Component{
constructor(props){
super(props)
}
renderPortal(){
const destroy = () => {
this.myDialog.destroy();
};
this.myDialog = Dialog.show({
onOk: ()=>{},
onCancel: destroy,
onClose: destroy,
autoClose: false,
content: <div>
<p>Test</p>
</div>
});
}
render(){
return <div onClick={()=>{
// this.renderPortal() //worker fine
this.setState({
a : 1
}, ()=>{
this.renderPortal()
})
}}> click me</div>
}
}
Dialog.buildModal = function(config) {
return <Modal {...config} />;
};
function App() {
return (
<div className="App">
<Wrapper />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.dialog{
HEIGHT: 50px;
background: white;
border:1px solid gray;
top: 20px;
width: 500px;
position: absolute;
}
.inner-dialog{
width: 100%;
height: 100%;
}
.button{
position: absolute;
bottom: 0;
border:1px solid gray;
}
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
The reason is very simple, while your dialog in itself is stateless, it is rendered while rendering a stateful component and thus ReactDom.render will deem it at stateful.
A simple hack to bypass the issue is to replace:
this.setState({
a : 1
}, ()=>{
this.renderPortal()
})
})
with
this.setState({
a : 1
}, ()=>{
setTimeout(() => {
this.renderPortal()
}, 1)
})
This way your component is considered as stateless.
Full code:
let rootDialogContainer = document.createElement("div");
document.body.appendChild(rootDialogContainer);
function createConfig(config, defaults) {
return Object.assign(
{},
defaults,
typeof config === "string" ? { content: config } : config
);
}
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
show: true
};
this.cancel = this.cancel.bind(this);
}
ok() {
if (this.props.autoClose) {
this.setState({ show: false });
}
const { onOk, afterClose } = this.props;
onOk && onOk();
afterClose();
}
cancel() {
if (this.mounted && this.props.autoClose) {
this.setState({ show: false });
}
const { onCancel, afterClose } = this.props;
onCancel && onCancel();
afterClose();
}
destroy() {
this.props.forceClose();
}
componentDidMount() {
this.mounted = true
}
componentWillUnmount() {
this.mounted = false
}
render() {
let DialogType = this.props.dialog || Dialog;
return (
<DialogType
{...this.props}
show={this.state.show}
onOk={this.ok}
onCancel={this.cancel}
/>
);
}
}
class Dialog extends React.Component{
constructor(props) {
super(props)
}
renderDialogWrap(params) {
let {
contentElm,
onCancel
} = params;
return (
<div className={"dialog"}>
<div style={{}} className={"inner-dialog"}>
{contentElm}
<div className="button" onClick={onCancel}>
cancel
</div>
</div>
</div>
);
}
renderPortal() {
return <Modal />;
}
render() {
let props = this.props;
let {
show,
className,
title,
content,
onCancel = () => {},
onOk = () => {},
children,
renderAsHiddenIfNotShow = false,
} = props;
return ReactDOM.createPortal(
<div
style={{ display: show ? "" : "none" }}
>
{this.renderDialogWrap({
onCancel,
contentElm: children,
show,
renderAsHiddenIfNotShow
})}
</div>,
document.body
);
}
}
Dialog.show = function(params) {
const config = createConfig(params, {
show: true,
autoClose: true,
onOk: () => {},
onCancel: () => {}
});
config.content = config.content || config.desc;
let container = rootDialogContainer
if (config.id) {
let containerId = `wrapper`
container = document.getElementById(containerId)
if (!container) {
container = document.createElement('div')
container.setAttribute('id', containerId)
document.body.appendChild(container)
}
}
config.forceClose = function(){
ReactDOM.unmountComponentAtNode(container);
};
config.afterClose = function() {
config.autoClose && config.forceClose();
};
return ReactDOM.render(this.buildModal(config), container);;
};
class Wrapper extends React.Component{
constructor(props){
super(props)
}
renderPortal(){
const destroy = () => {
this.myDialog.destroy();
};
this.myDialog = Dialog.show({
onOk: ()=>{},
onCancel: destroy,
onClose: destroy,
autoClose: false,
content: <div>
<p>Test</p>
</div>
});
}
render(){
return <div onClick={()=>{
// this.renderPortal() //worker fine
this.setState({
a : 1
}, ()=>{
setTimeout(() => this.renderPortal(), 1)
})
}}> click me</div>
}
}
Dialog.buildModal = function(config) {
return <Modal {...config} />;
};
function App() {
return (
<div className="App">
<Wrapper />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.dialog{
HEIGHT: 500px;
background: white;
border:1px solid gray;
top: 20px;
width: 500px;
position: absolute;
}
.inner-dialog{
width: 100%;
height: 100%;
}
.button{
position: absolute;
bottom: 0;
border:1px solid gray;
}
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
Updated demo

Prevent unnecessary re-render of React component

I have a PureComponent that renders another component and implements its onClick callback:
class ColorPicker extends React.PureComponent {
render() {
console.log('ColorPicker being rendered');
const fields = this.props.colors.map((color, idx) => {
const fieldProps = {
key: `${idx}`,
color,
/*onClick: () => { // PROBLEM HERE
this.props.colorPicked(color);
}*/
};
return <ColorField { ...fieldProps}/>;
});
return (
<div className="bla-picker">
<div>{`Refresh seed: ${this.props.seed}`}</div>
{fields}
< /div>
);
}
}
There is a small issue with this component: Whenever the ColorPicker is re-rendered, the nested ColorFields need to be re-rendered, too, because their onClick property changes each time. Using a lambda function will create a new instance of that function whenever the component is rendered.
I usually solve this by moving the implementation of onClick outside of the render method, like this: onClick: this.handleClick. However, I can't do this here, because the onClick handler needs to capture the color variable.
What's the best practice to solve this kind of problem?
Here's a jsfiddle to try it out; and as a snippet:
class ColorField extends React.PureComponent {
render() {
console.log('ColorField being rendered');
const divProps = {
className: 'bla-field',
style: {
backgroundColor: this.props.color
},
onClick: this.props.onClick
};
return <div { ...divProps}/>;
}
}
class ColorPicker extends React.PureComponent {
render() {
console.log('ColorPicker being rendered');
const fields = this.props.colors.map((color, idx) => {
const fieldProps = {
key: `${idx}`,
color,
/*onClick: () => { // PROBLEM HERE
this.props.colorPicked(color);
}*/
};
return <ColorField { ...fieldProps}/>;
});
return (
<div className="bla-picker">
<div>{`Refresh seed: ${this.props.seed}`}</div>
{fields}
< /div>
);
}
}
class Layout extends React.PureComponent {
constructor(props, ctx) {
super(props, ctx);
this.state = {
seed: 1
};
}
render() {
const pickerProps = {
colors: ['#f00', '#0f0', '#00f'],
colorPicked: (color) => {
console.log(`Color picked: ${color}`);
},
seed: this.state.seed
};
return (
<div>
<div
className="bla-button"
onClick = {this.btnClicked}
>
{'Click Me'}
</div>
<ColorPicker { ...pickerProps} />
</div>
);
}
btnClicked = () => {
this.setState({ seed: this.state.seed + 1 });
};
};
ReactDOM.render( <
Layout / > ,
document.getElementById("react")
);
.bla-button {
background-color: #aaa;
padding: 8px;
margin-bottom: 8px;
}
.bla-picker {}
.bla-field {
width: 32px;
height: 32px;
}
<div id="react">
</div>
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
As long as onClick remains commented out, only ColorPicker is re-rendered when the seed changes (see output from console.log). As soon as onClick is put in, all the ColorFields are re-rendered, too.
You can implement shouldComponentUpdate in your ColorField component like:
class ColorField extends React.Component {
shouldComponentUpdate(nextProps) {
return this.props.color !== nextProps.color;
}
render(){
const { color, onClick } = this.props;
console.log('Color re-rendered');
return (
<div
className="color"
onClick={onClick}
style={{
backgroundColor: color,
height: '50px',
width: '50px',
}}
/>
)
}
}
Example here
Be attentive as in the first solution we can use just React.Component because we implement shouldComponentUpdate by ourselves.

ReactJS CSS - how do I re-trigger a #keyframes CSS animation

I have a component that switches some content and the animation of the content depending on the side it is switching it from:
import React, { Component } from "react";
class Skills extends Component {
constructor(props) {
super(props);
this.state = {
shownSkill: 0,
fallIn: true,
slideUp: false
};
}
getPreviousSkill = () => {
const { shownSkill } = this.state;
const newSkill = shownSkill < 1 ? 3 : shownSkill - 1;
this.updateShownSkill(newSkill, false);
};
getNextSkill = () => {
const { shownSkill } = this.state;
const newSkill = shownSkill > 2 ? 0 : shownSkill + 1;
this.updateShownSkill(newSkill, true);
};
updateShownSkill = (skillIndex, fallIn) => {
this.setState({
shownSkill: skillIndex,
fallIn: fallIn,
slideUp: !fallIn
});
};
getSkillData = () => {
const { skills } = this.props;
const { shownSkill } = this.state;
return skills[shownSkill];
};
render() {
const { name, skill, description } = this.getSkillData();
const { shownSkill, slideUp } = this.state;
const { skills } = this.props;
return (
<div className="route-container skills">
<div className="skills-content-container">
{slideUp ? (
<div className="skills-right-content slide-up">
<div className="subtitle">{name}</div>
{description.map((p, i) => (
<div className="text" key={i}>
{p}
</div>
))}
</div>
) : (
<div className="skills-right-content
fall-in">
<div className="subtitle">{name}</div>
{description.map((p, i) => (
<div className="text" key={i}>
{p}
</div>
))}
</div>
)}
</div>
</div>
);
}
}
export default Skills;
Then I am animating the .fall-in class with css:
#keyframes fall-in {
0% {
margin-top: -600px;
}
100% {
margin-top: 0;
}
}
.fall-in {
animation-name: fall-in;
animation-duration: 0.5s;
animation-timing-function: linear;
animation-iteration-count: 1;
}
I would like this animation to trigger once every time the content of the .subtitle and .text divs changes, regardless of whether or not the animation changed.
This example will only trigger the animation the first time the css class is added.
Hey maybe you want to give a try on my OSS.
https://github.com/bluebill1049/react-simple-animate
I think it does what you want above, maybe worth to give it a try?
import Animate from 'react-simple-img';
import React from 'react';
export default ({ready}) => {
return <Animate startAnimation={ready} startStyle={{
marginTop: '-600px',
}} endStyle={{
marginTop: '0',
}}>
<YourComponent />
</Animate>
};

Toggle div-elements in different component in React

I'm fairly new to React. I'm trying to build a site where you can click navigation item (in this case music genre) and it will list all the songs that belongs to that particular genre.
My app.js looks like this:
import React, { Component } from 'react';
import ListGenres from './ListGenres';
import './App.css';
class App extends Component {
constructor(props) {
super();
this.state = {dataList: props.dataList};
}
render() {
return (
<div>
<div className="App">
<Navigation tracks = {this.state.dataList} />
<ListGenres tracks = {this.state.dataList}/>
</div>
</div>
);
}
}
export default App;
I have a navigation component that looks like this:
import React from 'react';
import HeaderBar from './HeaderBar';
import MenuItem from 'material-ui/MenuItem';
export class Navigation extends React.Component {
constructor(props) {
super();
}
/*
onClickFunction() {
toggle elements in another component
}
*/
render() {
const genres = this.props.tracks.map((elem) => {
return elem.genre;
});
const filtered = genres.filter((elem, index, self) => {
return self.indexOf(elem) === index;
});
const genreLoop = filtered.map((elem, i) => {
return (
<MenuItem
onClick= {this.onClickFunction}
key={ i }><a>{ elem }</a>
</MenuItem>);
});
return (
<div>
{ genreLoop }
</div>
);
}
}
export default Navigation;
My list of items are rendered in another component whick looks like this:
import React from 'react';
import './ListGenres.css';
export class ListGenres extends React.Component {
constructor(props) {
super();
}
render() {
return (
<div className="genreList">
<div className="tracklist-visible tracklist-pop">
<ul>
<h3>Pop</h3>
{ this.tracklist('Pop') }
</ul>
</div>
<div className="tracklist-visible tracklist-metal">
<ul>
<h3>Pop</h3>
{ this.tracklist('Metal') }
</ul>
</div>
</div>
);
}
Is there way to maybe add css-class to tracklist-div when anchor is clicked from Navigation-component? Looks like I can't pass any props from that component since it's "stand-alone"-component?
You need to lift the state up.
Of course, you can solve this with Redux too, but let's keep it simple and only use React.
Lifting State Up
Create a component that will contains both <Navigation /> and <ListGenres /> components.
Keep the state (genre and selectedGenre) in this parent component and pass it down through props.
You also need to create a callback to handle genres changes.
Here's the example:
class App extends Component {
constructor (props) {
super(props)
this.state = {
selectedGenre: null,
genres: [...]
}
}
onGenreChange (genre) {
this.setState({ selectedGenre: genre })
}
render () {
return (
<div>
<Navigation
onGenreChange={genre => this.onGenreChange(genre)}
genres={this.state.genres}
/>
<ListGenres
genres={this.state.genres}
selectedGenre={this.state.genres}
/>
</div>
)
}
}
You didn't supply much code or example on how things should work but as i understand you are looking for a behavior similar to Tabs, where you click a Tab and a corresponding View is presented.
If this is the case, then you need a Parent component that will manage the selected Tabs and render the View respectively.
This is a simple example:
const tabs = ["Pop", "Rock", "Rap", "Electro"];
const View = ({name}) => <h1 className="view">{`This is ${name} music!`}</h1>
class Tab extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { id, onClick } = this.props;
onClick(id);
}
render() {
const { name, id, isSelected } = this.props;
const css = `tab ${isSelected && 'selected'}`;
return (
<div
className={css}
onClick={this.onClick}
>
{name}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 1
}
this.onTabChange = this.onTabChange.bind(this);
}
onTabChange(id) {
this.setState({ selectedTab: id });
}
render() {
const { selectedTab } = this.state;
return (
<div>
{
tabs.map((t, i) => {
return (
<div className="wrapper">
<Tab name={t} id={i + 1} isSelected={selectedTab === i + 1} onClick={this.onTabChange} />
{selectedTab == i + 1 && <View name={t} />}
</div>
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.tab {
display: inline-block;
margin: 0 10px;
width: 60px;
text-align: center;
cursor: pointer;
padding: 5px;
}
.selected {
border: 1px solid #eee;
box-shadow: 0 0 3px 1px #999;
}
.wrapper{
display:inline-block;
}
.view{
position: absolute;
top: 0;
left: 0;
margin-top: 50px;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Categories