I am just diving into ReactJS so I am quite a newbie in this Reactjs world. I have read the FB documentations and some tutorials on the internet and started my test project.
In my test project I am trying to include a progress bar for users to see their progress of filling down some forms across 3 pages. This part works all great till I wanted to add some transition magic to the process bar.
I've written the code below and I thought it would be the right way to archive my goal by pushing a prop from the parent to this child progressBar component for determining the percentage of the progress bar.
In my constructor I set the default width at 0 to update it by componentDidMount to a percentage which comes from the parent. I've managed to receive and set te style but the transition isn't working at all. I try to archive a fancy progress bar which runs from 0% width to the given width in percentage via the props.
My code look likes as follow:
ProgressBar component
import './style.scss';
import React from 'react';
import classnames from 'classnames';
class ProgressBar extends React.Component {
constructor(props) {
super(props);
this.state = { progressionStyle : { } }
}
componentDidMount() {
this.setState({
progressionStyle : {
width : this.props.progression,
transition : 'all 1500ms ease'
},
scene1 : (this.props.scene1 == 'active') ? 'active' : (this.props.scene1 == 'done') ? 'done' : '',
scene2 : (this.props.scene2 == 'active') ? 'active' : (this.props.scene2 == 'done') ? 'done' : '',
scene3 : (this.props.scene3 == 'active') ? 'active' : (this.props.scene3 == 'done') ? 'done' : '',
});
}
/**
*
* Render
* #return {JSX}
*/
render() {
return (
<div className="progress-bar">
<div className="progress-bar__inner">
<div className="progress-bar__progress">
<div className={classnames('progress-bar__progress-fill', this.props.active)} style={this.state.progressionStyle}></div>
</div>
<div id="scene1" className="progress-bar__element">
<i className={classnames('progress-bar__icon', this.state.scene1)}></i>
<span className="progress-bar__label">Scene 1</span>
</div>
<div id="scene2" className="progress-bar__element">
<i className={classnames('progress-bar__icon', this.state.scene2)}></i>
<span className="progress-bar__label">Scene 2</span>
</div>
<div id="scene3" className="progress-bar__element">
<i className={classnames('progress-bar__icon', this.state.scene3)}></i>
<span className="progress-bar__label">Scene 3</span>
</div>
</div>
</div>
);
}
}
export default ProgressBar;
You cannot set the nested state directly, you should be doing it like
componentDidMount() {
var style = {...this.state.style}
style.width = this.props.progression
style.transition = 'all 500ms ease-in'
this.setState({style});
}
Also, you need to update your state in the componentWillReceiveProps function as you are updating state based on the props.
componentWillReceiveProps(nextProps) {
var style = {...this.state.style}
style.width = nextProps.progression
style.transition = 'all 500ms ease-in'
this.setState({style});
}
To make this effect work, I've found out the solution was to wrap the style into a function and call via request animation frame via as fol
componentDidMount() {
requestAnimationFrame(()=> {
this.showProgress();
});
}
showProgress() {
var style = { };
style.width = this.props.progression;
style.transition = 'all 1500ms ease-in';
this.setState({style});
}
https://stackoverflow.com/a/43779273/968898
Related
I have following React code
Code
What I would like is to when I hover my "E-commerce" picture App component background should change on "E-commerce" picture background.
So respectively and for other pictures.
I will be very grateful if you help me solve this problem.
Context, according to the React docs, should be used only for truly global state like current user or theme. Using context for components makes them less reusable.
updated code
Your component tree is App -> SolutionBox -> SolutionItem.
You want to "react" to an event in SolutionItem in App but there is SolutionBox inbetween them so you have to thread the event thru SolutionBox to App.
Step 1
Add a prop to SolutionItem called on OnHover, this will be a function call back that any parent component can use to react to changes.
function SolutionsSectionBoxItem({ solutionIMG, onHover }) {
let callOnHover = state => {
if (_.isFunction(onHover)) {
onHover(state);
}
};
return (
<div className="solutions-section-item-box">
<img
src={solutionIMG}
alt=""
onMouseEnter={() => {
callOnHover(true);
}}
onMouseLeave={() => {
callOnHover(false);
}}
className="solutions-section-item-img"
/>
</div>
);
}
Step 2
Add a prop to SolutionBoxItem called on BGChanged, this will again be a function call back that will be called when any solutionitem onhover happens. This function will take a menuName string and pass either the current menu name or default.
function SolutionsSectionBox({ onBGChanged }) {
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
return (
<div className="solutions-section-box-box">
<SolutionItem
solutionIMG={Ecommerce}
onHover={state => {
callBGChanged(state === true ? "Ecommerce" : "default");
}}
/>
<SolutionItem
solutionIMG={SalesMarketing}
onHover={state => {
callBGChanged(state === true ? "SalesMarketing" : "default");
}}
/>
<SolutionItem
solutionIMG={Analytics}
onHover={state => {
callBGChanged(state === true ? "Analytics" : "default");
}}
/>
<SolutionItem
solutionIMG={Middleware}
onHover={state => {
callBGChanged(state === true ? "Middleware" : "default");
}}
/>
</div>
);
}
Step 3
In the App component listen for the changes. In here we now set state when ever the mouse enters or leaves a solution item. From here you have to change the background, you are using css to control the background url, this will be harder since you now need css class for each background type. You could use the bgImage state value to change the name of the extra css className like 'AppSalesMarketing', 'AppEcommerce', etc.
export default function App() {
const [bgImage, setbgImage] = useState(E);
const onBGChanged = menuName => {
setbgImage(menuName);
};
return (
<div className={`App ${bgImage === "default" ? "" : `App${bgImage}`}`}>
<SolutionBox onBGChanged={onBGChanged} />
</div>
);
}
In CSS
Leave the original App class but based on the bgImage value add an additional one using the name of the bgImage + App like below to cascade down the updated background-image value.
.AppEcommerce {
background-image: url(https://placekitten.com/600/600);
}
.AppSalesMarketing {
background-image: url(https://placekitten.com/500/800);
}
.AppAnalytics {
background-image: url(https://placekitten.com/800/500);
}
.AppMiddleware {
background-image: url(https://placekitten.com/700/700);
}
Extra
I added lodash to test that the incoming props are functions before I call them, it is good to do defensive programming because you never know who may use your component in the future.
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
Two ways to solve the problem. One is passing down a function to update state, the other is to useContext. In this case it makes sense to use context because you are passing down a function through multiple components that do not care about the function.
First thing to do is make the background image dynamic in the div's style and use context:
// Put this outside the component
export const BackgroundContext = React.createContext(null);
// -- snip
const [backgroundImage, setBackgroundImage] = useState(Ecommerce);
const updateBackgroundImage = newImage => setBackgroundImage(newImage);
// -- snip
<BackgroundContext.Provider value={updateBackgroundImage}>
<div className="App" style={{ backgroundImage: `url(${backgroundImage})` }}>
{/* -- snip */}
</BackgroundContext.Provider>
Now in your SolutionsSectionBoxItem component you can import the background context:
import BackgroundContext from "../App";
Then using that context and react's mouseover api, update the selected background image:
const setBackgroundImage = useContext(BackgroundContext);
// -- snip
<img onMouseOver={() => setBackgroundImage(solutionIMG)} {/* -- snip -- */} />
You can read more here: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down
I made a toggle component (Accordion to be exact)
I am mapping through an array of objects and listing them like:
{object.map((o) => (
<Accordion key={o.id} title={o.question} className="item">
<div className="text"> { o.answer } <div/>
</Accordion>
))}
It renders something like this:
> Question 1
> Question 2
> Question 3
Now, every time I click a question, it toggles down to show the answer. All this works fine(I used hooks).
I want to be able to change the opacity of all the un toggled elements in this list when ONE of the questions is opened.
So if I open question 2, it becomes the "current item" and the opacity of question 2 and its answer should be 100% and all others(question1 an question3) should dim out or turn 50% opacity.. I am able to do it using :hover using css but that only works on hover.
Basically in theory, I should be able to select an item and remove the base class from all other items except the selected one. I don't know how to do that in reality. Help.
I feel like I'm missing something obvious.
const Accordion = ({ title, children, opened = false }) => {
const [show, setShow] = useState(opened);
const rotation = classnames('icon', {
'rotate': show,
});
const content = classnames('contents', {
'closed': !show,
});
useEffect(() => {
setShow(opened);
}, [opened]);
const toggle = useCallback(() => {
setShow(!show);
}, [show]);
return (
<div className='titleContainer' onClick={toggle}>
<div className={rotations}>
<i className='icon' />
</div>
<h5 className='title'>{title}</h5>
</div>
<div className={content}>{children}</div>
);
};
I finally understand what you mean, I think this is the answer:
const questionColor = (questionIndex, activeQuestion) => {
if (activeQuestion !== null && activeQuestion !== questionIndex) {
return "rgba(0,0,0,0.1)";
} else return "rgba(0,0,0,1)";
};
Working solution here:
https://codesandbox.io/s/cocky-hellman-fxrmc
I have a react function which returns a button.
<div className="col-6 btn-group btn-group w-100">
<AuditMenuButtons buttonValue='Pending' buttonName='Inbox' changeFilterForButton={this.props.changeFilterForButton} icon={icon_inbox}/>
<AuditMenuButtons buttonValue='Rejected' buttonName='Rejected' changeFilterForButton={this.props.changeFilterForButton} icon={icon_rejected}/>
<AuditMenuButtons buttonValue='Accepted' buttonName='Accepted' changeFilterForButton={this.props.changeFilterForButton} icon={icon_accepted}/>
</div>
Function is added below
function AuditMenuButtons(props) {
return(
<button className="w-25 btn menu-btn p-lg-3" name={props.buttonName} value={props.buttonValue} onClick={props.changeFilterForButton}><img src={props.icon} className="pr-3 menu-btn-icons">
</img>{props.buttonName}</button>
);
}
You will see 3 buttons in above code. I want to change the button icon when one button is clicked. actually button icon color should be green when button is clicked. Images are .png file (with green and silver border). I tried button:active in css it didn't work for me. Image should remain until I clicked another button or page was refreshed
In this case, the icon part is a UI state, it has to maintained in your state and passed down to AuditMenuButtons has props.
use these props in AuditMenuButtons to do the desired check.
import React,{Component} from 'react';
class demoComponent extends from Component{
this.state={
isClicked:false,
buttonIcons:{
pending:{active_Icon:"../IconURL",Icon:"../IconURL"},
rejected:{active_Icon:"../IconURL",Icon:"../IconURL"},
accepted:{active_Icon:"../IconURL",Icon:"../IconURL"}
}
}
clickHandler = (event) =>{
this.setState(
{
isClicked:!this.state.isClicked // this is gonna toggle everytime you click //
}
);
}
render(){
return <div className="col-6 btn-group btn-group w-100">
<AuditMenuButtons clickhandler={this.clickHandler} buttonValue='Pending' buttonName='Inbox' isClicked={this.state.isClicked} buttonIcons={this.state.buttonIcons} changeFilterForButton={this.props.changeFilterForButton} icon={icon_inbox}/>
<AuditMenuButtons clickhandler={this.clickHandler} buttonValue='Rejected' buttonName='Rejected' isClicked={this.state.isClicked} buttonIcons={this.state.buttonIcons} changeFilterForButton={this.props.changeFilterForButton} icon={icon_rejected}/>
<AuditMenuButtons clickhandler={this.clickHandler} buttonValue='Accepted' buttonName='Accepted' isClicked={this.state.isClicked} buttonIcons={this.state.buttonIcons} changeFilterForButton={this.props.changeFilterForButton} icon={icon_accepted}/>
</div>
}
}
export default demoComponent;
You can try something like this in your .css file.
.button:focus{background: url('your new green image');
You can manage the image path in react state and call a method attach
it to onClick, where you use setState() and update the state.
Refer
https://reactjs.org/docs/handling-events.html
https://reactjs.org/docs/react-component.html#setstate
this.state = {
image_path: 'your image url here'
}
changeUrl = () => {
this.setState({image_path:'new path'});
}
<AuditMenuButtons onClick={this.changeUrl} src={this.state.image_path}/>
You can try with this:
changeFilterForButton: function (props) {
props.currentTarget.style.backgroundColor = '#ccc';
}
function AuditMenuButtons(props) {
return(
<button className="w-25 btn menu-btn p-lg-3" name={this.props.buttonName}
value={this.props.buttonValue} onClick={this.props.changeFilterForButton}><img src={this.props.icon} className="pr-3 menu-btn-icons">
</img>{this.props.buttonName}</button>
);
}
or if you want to use react methodology then you can use construtor like this
constructor(props) {
super(props);
this.state = {isColor: false};
// This binding is necessary to make `this` work in the callback
this.changeFilterForButton= this.changeFilterForButton.bind(this);
}
changeFilterForButton() {
this.setState(state => ({
isColor: !state.isColor
}));
}
function AuditMenuButtons(props) {
return (
<button className="w-25 btn menu-btn p-lg-3" name={props.buttonName} value={props.buttonValue} onClick={props.changeFilterForButton} style="background-color: {this.state.isColor? '#CCC' : ''} "><img src={props.icon} className="pr-3 menu-btn-icons">
</img>{props.buttonName}</button>
);
}
I don't understand whats wrong here, I've done this before and I'm sure this is the way. I even copy and pasted two separate examples. Just trying to get the CSS transition Group going so that when a button is clicked and the state changes, the right component appears but with an effect. I'm getting nothing
The component showing depends on the state value: 1, 2 or 3. I just want for when that state changes and another component is being displayed rather than the one before for there to be an animation to make one leave and another arrive
Whatever visible component there is it is clear that there is a way to navigate back and forth between options as the each are sent an onClick method to change the state of the class
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
class LoginClass extends Component{
constructor(props) {
super(props);
this.state = {loginCompState: 0};
this.showInitial = this.showInitial.bind(this);
this.showPro = this.showPro.bind(this);
this.showUser = this.showUser.bind(this);
}
showPro(){
this.setState({
loginCompState: 1
});
};
showUser(){
this.setState({
loginCompState: 2
});
};
showInitial(){
this.setState({
loginCompState: 0
});
};
render(){
return(
<div>
<ReactCSSTransitionGroup
transitionName='fade'
transitionEnterTimeout={500}
transitionLeaveTimeout={500}>
{(this.state.loginCompState === 0) ?
<InitialComp proOnClick={this.showPro} userOnClick={this.showUser} /> :
(this.state.loginCompState === 1) ?
<ProLoginChoices backInitial={this.showInitial} /> :
<UserLoginChoices backInitial={this.showInitial} />}
</ReactCSSTransitionGroup>
</div>
)
}
}
accompanying CSS:
.fade-enter {
right: 100px;
}
/* End State of Animation */
.fade-enter-active {
right: 0px;
transition: .5s ease-in all;
}
Currently upgrading from a jquery / php webapp to react. All is going well and I understand the concepts behind react etc. The only issue I can't find a workaround is how to dynamically add / delete a class based on where the user clicks. I need this because I have a few dropdowns which trigger when the user click it and need to be hidden if they click somewhere else.
From: Give a class of "selected" when clicked on another <a> then remove the class if it's the same <a>
I've taken this example as it is simple, Jquery solution to the problem:
var h2 = $("h2 a");
h2.on("click", function() {
if ($(this).is(".selected")) {
$(this).removeClass("selected");
} else {
h2.removeClass("selected")
.filter(this).addClass("selected")
}
});
How to mimic the same functionality in react (and / or redux)?
Image to further clarify
The thing is, you want to toggle some data on click event and change the classname of a html element accordingly.
Your data that 'selected' class based on could come from anywhere, from parent or component state. You would do something like this :
<div className={ myData ? 'selected' : '' } ></div>
But there is a better way to display classname changes with a library called classnames. The same thing is accomplished as :
<div className={ classNames({ 'selected' : myData }) } ></div>
Until now, we have seen how to display changes on render function. Second thing you need is to listen to click events and fire the function that will eventually toggle the data that controls the 'selected' classname, in my example 'myData'.
Here is a working example, there might be various ways to accomplish this. But I strongly recommend using classnames library to toggle classnames.
The workaround about removing class when another element ( apart from li elements we observe ) clicked could be solved by a click event listener.
For instance :
import React, { Component } from 'react'
import classNames from 'classnames'
class DropDown extends Component {
constructor(props){
super(props)
this.state = {
activeSelected : ''
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount(){
global.document.addEventListener( 'click', this.handleClick, false )
}
componentWillUnmount(){
global.document.removeEventListener( 'click', this.handleClick, false )
}
handleClick(event){
if( event.target.className.includes('not-changing-css-class') &&
this.state.activeSelected !== ''
) this.setState( { activeSelected : '' } )
}
render(){
let { activeSelected } = this.state
return (
<ul>
<li
className={ classNames({
'not-changing-css-class' : true,
'selected' : activeSelected === 'item1'
}) }
onClick={ event => this.setState({ activeSelected : activeSelected === 'item1' ? '' : 'item1' }) }
>
Item 1
</li>
<li
className={ classNames({
'not-changing-css-class' : true,
'selected' : activeSelected === 'item2'
}) }
onClick={ event => this.setState({ activeSelected : activeSelected === 'item2' ? '' : 'item2' }) }
>
Item 2
</li>
<li
className={ classNames({
'not-changing-css-class' : true,
'selected' : activeSelected === 'item3'
}) }
onClick={ event => this.setState({ activeSelected : activeSelected === 'item3' ? '' : 'item3' }) }
>
Item 3
</li>
</ul>
)
}
}
You can hold the selected element index (or ID if you use IDs) in the component state. You can use redux store if you think this state will be relevant to any other component in your app, but starting with state is simpler.
Once you have this state. You can check within your render() function whether a link is selected or not, by comparing with the component state. You would also update the selectedIndex whent the links are clicked.
A simple example to render the links could be as follows. Note that you can extract parts of these to be functions instead of using expressions in JSX.
```
allLinks.map( (link, index) =>
<a href={link.target} className={this.state.selectedIndex === index ? 'selected' : null}/>
)
```