Set clicked child component active while all of its siblings inactive? - javascript

Sorry if the phrasing of my question is unclear. I'm new to React and making an app with three different screens/pages. There should be a footer that follows the state of the payment process thanks to 3 boxes. The box that is being selected should show up in another color (here I've used a solid blue border).
When clicking on another box (box 3 for example), I would like the third box to be selected and the other two boxes to be unselected. In order to achieve this, I've set up a state.active in the child element. If the state is active, then a different style is applied to the element (the blue border).
I have two components, "ProgressBar" being the parent, and "ProgressElement" being the child. Here is what the code looks like :
class ProgressElement extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: false
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
if (this.props.eleType === "pattern") {
this.setState({selected: true})
}
}
handleClick() {
if (!this.state.selected) {
this.setState({selected: true})
}
if (this.state.selected) {
this.setState({selected: false})
}
}
render() {
let eleNumber = NaN
if (this.props.eleType === "pattern") {
eleNumber = 1;
this.state.selected === true;
}
if (this.props.eleType === "gift") {
eleNumber = 2;
}
if (this.props.eleType === "personal") {
eleNumber = 3;
}
if (this.state.selected) {
return <div className="square-box selected"
onClick={this.handleClick}>{eleNumber}</div>
} else {
return <div className="square-box"
onClick={this.handleClick}>{eleNumber}</div>
}
}
}
class ProgressBar extends React.Component {
render() {
return <div className="d-flex custom-progress-bar">
<ProgressElement eleType="pattern"/>
<ProgressElement eleType="gift"/>
<ProgressElement eleType="personal"/>
</div>
}
}
It's working fine, but my only problem is that I don't know how to make the other boxes unselected. For that, I would need the other sibling elements to be aware of the state of the other ones. I've thought of two ways to do this but have been unable to make one of these work so far :
first one would be to pass informations about the state of the child to the parent, but I think that goes against the principle of unidirectional data flow
second one would be to put the "active" state in the parent instead of the child, but I'm not sure how to keep tracks of all child like this. I've tried to do it with an array but didn't succeed so far.
Could anyone help me with this ? I know this is probably basic stuff but I'm having a hard time with React. Thanks for reading me and thanks in advance.

You need to keep track of currently active child within the parent component and pass it down as a prop:
const { Component } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
class ProgressElement extends React.Component {
handleClick = eleType => this.props.onSetActive(eleType)
render(){
return (
<div
className={this.props.eleType === this.props.activeEleType && 'active'}
onClick={() => this.handleClick(this.props.eleType)}
>
{this.props.eleType}
</div>
)
}
}
class ProgressBar extends React.Component {
state = {activeEleType: null}
onSetActive = activeEleType => this.setState({
activeEleType
})
render() {
return (
<div className="progress-bar">
{
['pattern', 'gift', 'personal'].map(key => (
<ProgressElement
activeEleType={this.state.activeEleType}
eleType={key}
key={key}
onSetActive={this.onSetActive}
/>
))
}
</div>
)
}
}
render (
<ProgressBar />,
rootNode
)
.progress-bar {
display: flex;
}
.progress-bar>div {
width: 100px;
height: 100px;
background: grey;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
margin: 10px;
cursor: pointer;
}
.progress-bar>div.active {
border: 2px solid blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

Try and apply this, I did not test the code myself so make sure that you apply the logic to your code (I'm rusted with classes)
class ProgressElement extends React.Component {
render() {
if (this.props.eleType === this.props.selected) {
return <div className="square-box selected"
onClick={this.props.act({selectedValue:""})}>{this.props.eleType}</div>
} else {
return <div className="square-box"
onClick={this.props.act({selectedValue:this.props.eleType})}>{this.props.eleType}</div>
}
}
}
class ProgressBar extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedValue: ""
}
}
updateSelected = (selected) => {
this.setState(selected);
}
render() {
return <div className="d-flex custom-progress-bar">
<ProgressElement eleType="pattern" act={this.updateSelected} selected={this.state.selectedValue}/>
<ProgressElement eleType="gift" act={this.updateSelected} selected={this.state.selectedValue}/>
<ProgressElement eleType="personal" act={this.updateSelected} selected={this.state.selectedValue}/>
</div>
}
}

Related

How do you remove a class from elements inside a component in React.JS when clicking outside given component?

I am trying to emulate a behavior similar to clicking on the overlay when a Modal popup is open. When clicking outside the sidenav component, I want to close all elements that are currently in a flyout mode.
I have a multi-tier nested navigation menu that is stored in its own component, Sidebar. I have the following piece of code that handles clicks that occur outside the Sidebar component:
class Sidebar extends React.Component {
...
handleClick = (e) => {
if (this.node.contains(e.target)) {
return;
}
console.log('outside');
};
componentDidMount() {
window.addEventListener('mousedown', this.handleClick, false);
}
componentWillUnmount() {
window.removeEventListener('mousedown', this.handleClick, false);
}
render() {
return (
<div
ref={node => this.node = node}
className="sidebar"
data-color={this.props.bgColor}
data-active-color={this.props.activeColor}
>
{renderSideBar()}
</div>
);
}
...
}
This part works fine - but when the flyout menus get displayed on clicking a parent menu option, I would like it to close -any- flyout menus that are currently opened.
-|
|
- Menu Item 1
|
|-option 1 (currently open)
|-option 2
- Menu Item 2
|
|-option 1 (closed)
|-option 2 (closed, clicked to expand - this is when it should close [Menu Item 1/Option 1]
The menu items are generated using <li> tags when mapping the data object containing the menu structure.
Is there a way to basically select all registered objects that have the class of 'collapse' / aria-expanded="true" and remove it? Similar to how jQuery would select dom elements and manipulate them.
I know that this is not the premise in which React works, it is just an example of the behavior I want to emulate.
As far as I understand you want to modify the DOM subtree from another component. To achive your goal you can use ref.
Using ref is helpful when you want to access HtmlElement API directly - in my example I use animate(). Please, read the documentation as it describes more of ref use cases.
Below is the simple example of animating <Sidebar/> shrinking when user clicks on <Content />.
const { useRef } = React;
function Main() {
const sidebar = useRef(null);
const handleClick = () => {
sidebar.current.hide();
};
return (
<div className="main">
<Sidebar ref={sidebar} />
<Content onClick={handleClick} />
</div>
);
}
class Sidebar extends React.Component {
constructor(props) {
super(props);
this.state = { visible: true };
this.show = this.show.bind(this);
this.sidebar = React.createRef(null);
}
show() {
if (!this.state.visible) {
this.sidebar.current.animate(
{ flex: [1, 2], "background-color": ["teal", "red"] },
300
);
this.setState({ visible: true });
}
}
hide() {
if (this.state.visible) {
this.sidebar.current.animate(
{ flex: [2, 1], "background-color": ["red", "teal"] },
300
);
this.setState({ visible: false });
}
}
render() {
return (
<div
ref={this.sidebar}
className={this.state.visible ? "sidebar--visible" : "sidebar"}
onClick={this.show}
>
Sidebar
</div>
);
}
}
function Content({ onClick }) {
return (
<div className="content" onClick={onClick}>
Content
</div>
);
}
ReactDOM.render(<Main />, document.getElementById("root"));
.main {
display: flex;
height: 100vh;
}
.sidebar {
flex: 1;
background-color: teal;
}
.sidebar--visible {
flex: 2;
background-color: red;
}
.content {
flex: 7;
background-color: beige;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Creating re-orderable card component in React

I'm currently working my way through React, and I'm building a portfolio site. On the work page, I have 3 tiles, which will eventually be overlapping. When a card is clicked, it opens a side panel, and (hopefully) the active card goes to the front, pushing the others behind it to fill in the space.
I currently have the cards, or tabs working, I just need help with how to rearrange them on click.
This is the Tab.js component
import PropTypes from 'prop-types';
class Tab extends Component {
onClick = () => {
const { label, onClick } = this.props;
console.log('that tickles', {onClick})
onClick(label);
}
render() {
const {
onClick,
props: {
activeTab,
label,
position,
inactivePosition,
style
},
} = this;
console.log(this)
let className = 'tab-list-item';
if (activeTab === label) {
className += ' tab-list-active';
}
return (
<div
style={style}
className={className}
onClick={onClick}
>
{label}
</div>
);
}
}
export default Tab;
The tabs.js component that brings things together
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './tabstyle.css'
import Tab from './tab.js';
class Tabs extends Component {
static propTypes = {
children: PropTypes.instanceOf(Array).isRequired,
}
constructor(props) {
super(props);
this.state = {
activeTab: this.props.children[0].props.label,
};
}
onClickTabItem = (tab) => {
this.setState({
activeTab: tab
});
}
render() {
const {
onClickTabItem,
props: {
children,
position,
},
state: {
activeTab,
inactiveTab1,
inactiveTab2,
}
} = this;
return (
<React.Fragment>
{children.map((child) => {
const { label } = child.props;
return (
<Tab
activeTab={activeTab}
key={label}
label={label}
onClick={onClickTabItem}
/>
);
})}
<div className="tab-content">
{children.map((child) => {
if (child.props.label !== activeTab) return undefined;
return child.props.children;
})}
</div>
</React.Fragment>
);
}
}
export default Tabs;
the actual work page
export default function Work() {
return (
<React.Fragment >
<Tabs>
<div label="Example1" position='1'>
what in the world
</div>
<div label="Example2" position='2'>
lorem ipsum
</div>
<div label="Example3" position='3'>
Nothing to see here, this tab is!
</div>
</Tabs>
</React.Fragment>
);
}
}
.tab-list-item {
background-color:#fff;
position: relative;
transition: all .5s;
}
.tab-content {
color: #fff;
background-color: rgb(19, 47, 74);
grid-area:1/13/span 9/ span 8;
z-index: 5;
padding:3em;
}
.tab-list-active {
background-color: #f0f;
grid-area: 2/3/span 4/ span 4;
}
.position2{
grid-area:3/4/span 4/ span 4;
}
.position3{
grid-area:4/5/span 4/ span 4;
}
In my mind, I pictured building an array, and simply having the first card be active, then when another card is clicked, that card is sent to position 0, and so on.
I hope that's everything. As I said, I'm pretty new to React, bit less new to vanilla JavaScript, but still pretty green. Any guidance would be massively appreciated, as I'm at a bit of an impasse at the moment.
For a working demo, I'm using AWS Amplify to host it temporarily https://master.d2wqg4b36m462q.amplifyapp.com/work

Using state in child component received from parent component

When I click on the menu in GeneralNav I successfully switch between true or false.
This menuState once again is passed successfully to Overlay via HomePage.
Though I'm not able to toggle the right classes in Overlay to hide or show the menu. Can someone explain me a correct workflow to add the classes on my EasyFlexCol component to show or hide it? Been stuck for a while now.
Thanks!
class GeneralNav extends Component {
render() {
return (
<div
className="nav-burger-box menu-action"
onClick={this.props.toggleMenu}
>
<div className="nav-burger-top" />
<div className="nav-burger-bottom" />
</div>
);
}
}
class HomePage extends Component {
state = {
showMenu: false
};
toggleMenu = e => {
e.preventDefault();
this.setState(state => ({ showMenu: !state.showMenu }));
};
render() {
return (
<React.Fragment>
<OverlayMenu menuState={this.state.showMenu}/>
<HeaderFullscreen />
</React.Fragment>
);
}
}
class OverlayMenu extends Component {
state = {
showMenu: "overlay-menu-wrapper bg-color-dark overlay-menu-wrapper display-block",
hideMenu: "overlay-menu-wrapper bg-color-dark overlay-menu-wrapper"
};
render() {
let menuState = this.props.menuState
console.log(menuState)
return (
<EasyFlexCol style={"here I want to add the right class to show or hide the overlay menu"}>
</EasyFlexCol>
);
}
}
You can do using ternary operation like this :
i.e if menuState is true show showMenu else vice versa
<EasyFlexCol className={menuState ? showHide.showMenu : showHide.hideMenu}>
</EasyFlexCol>
here is working example for your refrence : https://stackblitz.com/edit/react-wj49ol
Use ternary operator when rendering.
class OverlayMenu extends Component {
render() {
const showHide= { // Assuming that strings below are valid CSS class names
showMenu: "overlay-menu-wrapper bg-color-dark overlay-menu-wrapper display-block",
hideMenu: "overlay-menu-wrapper bg-color-dark overlay-menu-wrapper"
};
let menuState = this.props.menuState
console.log(menuState)
return (
<EasyFlexCol className={menuState ? showHide.showMenu : showHide.hideMenu}>
</EasyFlexCol>
);
}
}
Alternatively you can compose style of <EasyFlexCol/> component dynamically
class OverlayMenu extends Component {
render() {
style={ display: 'block' }
let menuState = this.props.menuState
return (
<EasyFlexCol className={'some-default-class'} style={menuState ? style : {}}>
</EasyFlexCol>
);
}
}
Both example assume that <EasyFlexCol/> has either className property or style property.

ReactJS clearing an input from parent component

I'm teaching myself react with a super simple app that asks the user to type a word presented in the UI. If user enters it correctly, the app shows another word, and so on.
I've got it almost working, except for one thing: after a word is entered correctly, I need to clear the input element. I've seen several answers here about how an input element can clear itself, but I need to clear it from the component that contains it, because that's where the input is checked...
// the app
class AppComponent extends React.Component {
constructor() {
super();
this.state = {
words: ['alpha', 'bravo', 'charlie'],
index: 0
};
}
renderWordsource() {
const word = this.state.words[this.state.index];
return <WordsourceComponent value={ word } />;
}
renderWordinput() {
return <WordinputComponent id={1} onChange={ this.onChange.bind(this) }/>;
}
onChange(id, value) {
const word = this.state.words[this.state.index];
if (word == value) {
alert('yes');
var nextIndex = (this.state.index == this.state.words.count-1)? 0 : this.state.index+1;
this.setState({ words:this.state.words, index:nextIndex });
}
}
render() {
return (
<div className="index">
<div>{this.renderWordsource()}</div>
<div>{this.renderWordinput()}</div>
</div>
);
}
}
// the input component
class WordinputComponent extends React.Component {
constructor(props) {
this.state = { text:''}
}
handleChange(event) {
var text = event.target.value;
this.props.onChange(this.props.id, text);
}
render() {
return (
<div className="wordinput-component">
<input type="text" onChange={this.handleChange.bind(this)} />
</div>
);
}
}
See where it says alert('yes')? That's where I think I should clear the value, but that doesn't make any sense because it's a parameter, not really the state of the component. Should I have the component pass itself to the change function? Maybe then I could alter it's state, but that sounds like a bad idea design-wise.
The 2 common ways of doing this is controlling the value through state in the parent or using a ref to clear the value. Added examples of both
The first one is using a ref and putting a function in the child component to clear
The second one is using state of the parent component and a controlled input field to clear it
class ParentComponent1 extends React.Component {
state = {
input2Value: ''
}
clearInput1() {
this.input1.clear();
}
clearInput2() {
this.setState({
input2Value: ''
});
}
handleInput2Change(evt) {
this.setState({
input2Value: evt.target.value
});
}
render() {
return (
<div>
<ChildComponent1 ref={input1 => this.input1 = input1}/>
<button onClick={this.clearInput1.bind(this)}>Clear</button>
<ChildComponent2 value={this.state.input2Value} onChange={this.handleInput2Change.bind(this)}/>
<button onClick={this.clearInput2.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent1 extends React.Component {
clear() {
this.input.value = '';
}
render() {
return (
<input ref={input => this.input = input} />
);
}
}
class ChildComponent2 extends React.Component {
render() {
return (
<input value={this.props.value} onChange={this.props.onChange} />
);
}
}
ReactDOM.render(<ParentComponent1 />, document.body);
<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>
I had a similar issue: I wanted to clear a form which contained multiple fields.
While the two solutions by #noveyak are working fine, I want to share a different idea, which gives me the ability to partition the responsibility between parent and child: parent knows when to clear the form, and the items know how to react to that, without using refs.
The idea is to use a revision counter which gets incremented each time Clear is pressed and to react to changes of this counter in children.
In the example below there are three quite simple children reacting to the Clear button.
class ParentComponent extends React.Component {
state = {revision: 0}
clearInput = () => {
this.setState((prev) => ({revision: prev.revision+1}))
}
render() {
return (
<div>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<button onClick={this.clearInput.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent extends React.Component {
state = {value: ''}
componentWillReceiveProps(nextProps){
if(this.props.revision != nextProps.revision){
this.setState({value : ''});
}
}
saveValue = (event) => {
this.setState({value: event.target.value})
}
render() {
return (
<input value={this.state.value} onChange={this.saveValue} />
);
}
}
ReactDOM.render(<ParentComponent />, document.body);
<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>
EDIT:
I've just stumbled upon this beautifully simple solution with key which is somewhat similar in spirit (you can pass parents's revision as child's key)
Very very very simple solution to clear form is add unique key in div under which you want to render form from your child component key={new Date().getTime()}:
render(){
return(
<div className="form_first_step fields_black" key={new Date().getTime()}>
<Form
className="first_step">
// form fields coming from child component
<AddressInfo />
</div>
</Form>
</div>
)
}

change scrollTop in reactjs

I just learn react, and want to achieve a function :
both A,B are components, if A scroll, then B scroll
The following is my code
<A onScroll="handleScroll"></A>
//what i write now
handleScroll: function(event){
var target = event.nativeEvent.target;
//do something to change scrollTop value
target.scrollTop += 1;
// it looks the same as not use react
document.getElementById(B).scrollTop = target.scrollTop;
}
but actually I want my code like this
//what i want
<A scrollTop={this.props.scrollSeed}></A>
<B scrollTop={this.props.scrollSeed}></B>
//...
handleScroll(){
this.setState({scrollSeed: ++this.state.scrollSeed})
}
it is similar to input
<input value="this.props.value"/>
<input value="this.props.value"/>
<input ref='c' onChange={handleChange}>
//...
handleChange: function() {
// enter some code in c and then render in a and b automatically
}
In other words, I want some attribute, like scrollTop(different
form <input value={}> ,because <A scrollTop={}> doesn't work) ,is bind with some state, so that I can just use setState, and they will update by themselves.
I googled before but can't find the answser. I hope that my poor English won't confuse you.
There are a number of patterns to achieve this. This sample is what I came up with to get you up and going.
First create a component class which has an oversize element for scroll effect. When dragging the scroll bar, this component calls its handleScroll React property to notify its parent component, with the value of scrollTop.
var Elem = React.createClass({
render() {
return (
<div ref="elem"
onScroll={ this.onScroll }
style={{ width: "100px", height: "100px", overflow: "scroll" }}>
<div style={{ width: "100%", height: "200%" }}>Hello!</div>
</div>
);
},
componentDidUpdate() {
this.refs.elem.scrollTop = this.props.scrollTop;
},
onScroll() {
this.props.handleScroll( this.refs.elem.scrollTop );
}
});
The parent component, aka wrapper, keeps the scroll top value in its state. Its handleScroll is passed to the child components as callback. Any scroll on the child elements triggers the callback, sets the state, results in a redraw, and updates the child component.
var Wrapper = React.createClass({
getInitialState() {
return {
scrollTop: 0
}
},
render() {
return (
<div>
<Elem scrollTop={ this.state.scrollTop } handleScroll={ this.handleScroll } />
<Elem scrollTop={ this.state.scrollTop } handleScroll={ this.handleScroll } />
</div>
);
},
handleScroll( scrollTop ) {
this.setState({ scrollTop });
}
});
And render the wrapper, presuming an existing <div id="container"></div>.
ReactDOM.render(
<Wrapper />,
document.getElementById('container')
);
2019's answer
First, the fix:
const resetScrollEffect = ({ element }) => {
element.current.getScrollableNode().children[0].scrollTop = 0
}
const Table = props => {
const tableRef = useRef(null)
useEffect(() => resetScrollEffect({ element: tableRef }), [])
return (
<Component>
<FlatList
ref={someRef}
/>
</Component>
)
}
Second, a little explanation:
Idk what is your reason why you got here but I have used flex-direction: column-reverse for my FlatList (it's a list of elements). And I need this property for z-index purposes. However, browsers set their scroll position to the end for such elements (tables, chats, etc.) - this may be useful but I don't need that in my case.
Also, example is shown using React Hooks, but you can use older more traditional way of defining refs
this.refs is deprecated. use reactjs.org/docs/refs-and-the-dom.html#creating-refs
import React from 'react';
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.resultsDiv = React.createRef();
}
someFunction(){
this.resultsDiv.current.scrollIntoView({behavior: 'smooth'});
// alternative:
// this.resultsDiv.current.scrollTop = 0;
}
render() {
return (
<div ref={this.resultsDiv} />
);
}
}
export default SomeComponent;
Here's an updated version of Season's answer, including a runnable snippet. It uses the recommended method for creating refs.
class Editor extends React.Component {
constructor(props) {
super(props);
this.content = React.createRef();
this.handleScroll = this.handleScroll.bind(this);
}
componentDidUpdate() {
this.content.current.scrollTop = this.props.scrollTop;
}
handleScroll() {
this.props.onScroll( this.content.current.scrollTop );
}
render() {
let text = 'a\n\nb\n\nc\n\nd\n\ne\n\nf\n\ng';
return <textarea
ref={this.content}
value={text}
rows="10"
cols="30"
onScroll={this.handleScroll}/>;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {scrollTop: 0};
this.handleScroll = this.handleScroll.bind(this);
}
handleScroll(scrollTop) {
this.setState({scrollTop: scrollTop});
}
render() {
return <table><tbody>
<tr><th>Foo</th><th>Bar</th></tr>
<tr>
<td><Editor
scrollTop={this.state.scrollTop}
onScroll={this.handleScroll}/></td>
<td><Editor
scrollTop={this.state.scrollTop}
onScroll={this.handleScroll}/></td>
</tr>
</tbody></table>;
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Categories