Trying to figure out how to transfer data between siblings components. The idea is this: you need to make sure that only one child component has an "active" class (Only one div could be selected). Here is the code:
https://codepen.io/slava4ka/pen/rNBoJGp
import React, {useState, useEffect} from 'react';
import styleFromCss from './Garbage.module.css';
const ChildComponent = (props) => {
const [style, setStyle] = useState(`${styleFromCss.childComponent}`);
const [active, setActive] = useState(false)
const setActiveStyle = () => {
console.log("setActiveStyle");
if (!active) {
setStyle(`${styleFromCss.childComponent} ${styleFromCss.active}`)
setActive(true)
} else {
setStyle(`${styleFromCss.childComponent}`);
setActive(false)
}
};
//console.log(props);
console.log(`id ${props.id} style ${style}`);
return (
<div className={style} onClick={() => {
props.updateData(props.id, () => setActiveStyle())
}}>
<h3>{props.name}</h3>
</div>
)
};
const ParentComponent = (props) => {
const state = [{'id': 0, 'name': 'один'}, {'id': 1, 'name': 'два'}, {'id': 2, 'name': 'три'}];
const [clicked, setClicked] = useState(null);
const highlight = (id, makeChildActive) => {
//console.log("click! " + id);
setClicked(id);
makeChildActive();
};
return (
<div>
{state.map(entity => <ChildComponent updateData={highlight}
key={entity.id}
id={entity.id}
name={entity.name}
isActive={(entity.id ===
clicked) ? styleFromCss.active : null}
/>)}
</div>
)
};
export default ParentComponent;
styleFromCss.module.css:
.childComponent{
width: 200px;
height: 200px;
border: 1px solid black;
margin: 10px;
float: left;
text-align: center;
}
.active{
background-color: blueviolet;
}
I tried to implement this through hooks, not classes. As a result, when you click on the selected component, its classes change, and nothing happens on its siblings. As I understand it, they simply do not redraw. The question is how to fix it? And is such an approach correct for the realization of a given goal? Or my idea is fundamentally wrong, I will be grateful for the advice)))
You cannot use the Child's onClick method to set the style as when one Child is clicked, the other Children don't know that :((
Instead, when you click on one Child, it tells the Parent it is clicked (you do this correctly already with setClicked()), then the Parent can tell each Child whether they are active or not (by passing IsActive boolean), and each Child uses its props.isActive boolean to set its style :D
const ChildComponent = (props) => {
let style = 'childComponent'
if (props.isActive) style = style + ' active'
return (
<div className={style} onClick={() => {
props.updateData(props.id)
}}>
<h3>{props.name}</h3>
</div>
)
};
const ParentComponent = (props) => {
const state = [{'id': 0, 'name': 'один'}, {'id': 1, 'name': 'два'}, {'id': 2, 'name': 'три'}];
const [clicked, setClicked] = React.useState(null);
const highlight = (id) => {
setClicked(id);
};
return (
<div>
{state.map(entity =>
<ChildComponent updateData={highlight}
key={entity.id}
id={entity.id}
name={entity.name}
isActive={entity.id === clicked}
/>
)}
</div>
)
};
ReactDOM.render(
<ParentComponent/>,
document.getElementById('root')
);
You need to pass a function down into the child from the parent that handles state change in the parent component for a piece of state that determines which id is "active", lets call that activeId. Then pass activeId into the child as a prop. In the child, compare the id to the activeId and apply the class accordingly.
Correct, this is the wrong way to be thinking about it. If you only want one child to be selected at a time, you should keep the "which child is selected" data in the parent. Each child should not also manage its own version of its highlighted state; instead it should be a prop given to each child (each child is a pure function.) Try this:
const ChildComponent = props => {
return (
<div
className={'childComponent' + (props.isActive ? ' active' : '')}
onClick={() => props.setHighlighted(props.id)}
>
<h3>{props.name}</h3>
</div>
);
};
const ParentComponent = props => {
const state = [
{ id: 0, name: 'один' },
{ id: 1, name: 'два' },
{ id: 2, name: 'три' }
];
const [clicked, setClicked] = React.useState(null);
return (
<div>
{state.map(entity => (
<ChildComponent
setHighlighted={setClicked}
key={entity.id}
id={entity.id}
name={entity.name}
isActive={entity.id === clicked ? 'active' : null}
/>
))}
</div>
);
};
ReactDOM.render(<ParentComponent />, document.getElementById('root'));
Related
The SelectedColumn value doesn't come in the CustomHeader component. However, setSelectedColumn works! Why🧐 ?
Also, I'm passing CustomHeader to constant components that use useMemo. Without useMemo CustomHeader doesn't work.
const [selectedColumn, setSelectedColumn] = useState(null);
console.log("selected Column Outside:", selectedColumn); // It works!
const CustomHeader = (props) => {
const colId = props.column.colId;
console.log("selected Column In CustomHeader:", selectedColumn); // Doesn't work
return (
<div>
<div style={{float: "left", margin: "0 0 0 3px"}} onClick={() => setSelectedColumn(props.column.colId)}>{props.displayName}</div>
{ selectedColumn === colId ? <FontAwesomeIcon icon={faPlus} /> : null}
</div>
)
}
const components = useMemo(() => {
return {
agColumnHeader: CustomHeader
}
}, []);
UPDATE: If I use the useState hook inside the CustomHeader component, it adds a "+" sign to each column and does not remove from the previous one. Here is a picture:
After reading your comment, your issue is clearly about where you want to place your useState.
First of all, you should always place useState inside a component. But in your case, apparently what you're trying to achieve is that when you select a column, the other columns get deselected.
Therefore, you need to pass both selectedColumn and setSelectedColumn as props to your component, and create the useState on the parent component.
Assuming all your CustomHeader components share the same parent component, in which my example I'll call CustomHeadersParent, you should do something like this:
// added mock headers to have a working example
const headers = [
{
displayName: "Game Name",
column: {
colId: 1,
},
},
{
displayName: "School",
column: {
colId: 2,
},
},
];
const CustomHeadersParent = (props) => {
const [selectedColumn, setSelectedColumn] = useState(null);
return headers.map((h) => (
<CustomHeader
column={h.column}
displayName={h.displayName}
setSelectedColumn={setSelectedColumn}
selectedColumn={selectedColumn}
/>
));
};
const CustomHeader = (props) => {
const colId = props.column.colId;
return (
<div>
<div
style={{ float: "left", margin: "0 0 0 3px" }}
onClick={() => props.setSelectedColumn(props.column.colId)}
>
{props.displayName}
</div>
{props.selectedColumn === colId ? <FontAwesomeIcon icon={faPlus} /> : null}
</div>
);
};
const components = useMemo(() => {
return {
agColumnHeader: CustomHeader,
};
}, []);
You should use hooks inside your component
const CustomHeader = (props) => {
const colId = props.column.colId;
const [selectedColumn, setSelectedColumn] = useState(null);
console.log("selected Column In CustomHeader:", selectedColumn); // Should work
return (
<div>
<div style={{float: "left", margin: "0 0 0 3px"}} onClick={() => setSelectedColumn(props.column.colId)}>{props.displayName}</div>
{ selectedColumn === colId ? <FontAwesomeIcon icon={faPlus} /> : null}
</div>
)
}
https://jsfiddle.net/wg4uL2dk/
here is old example of Tabs component.
I tried to make it with function components and typescript.
interface ITabBarProps {
children: React.ReactElement[]
}
export const TabBar = ({ children }: ITabBarProps) => {
const [activeTab, setActiveTab] = useState(0)
const handleTabClick = (index: number) => {
setActiveTab(index)
}
function renderTabNav () {
return React.Children.map(children, (child, index) => {
return React.cloneElement(child, {
onClick: handleTabClick,
index: index,
isActive: index === activeTab
})
})
}
const renderActiveTabContent = () => {
if (children[activeTab]) {
return children[activeTab].props.children
}
}
return (
<div>
<div>
{renderTabNav()}
</div>
<div>
{renderActiveTabContent()}
</div>
</div>
)
}
interface ITabBarItemProps {
label?: string
children: React.ReactNode
}
export const Tab = ({ label }: ITabBarItemProps) => {
return (
<button
className={cn(classes.tabBarNav)}
>
Button
</button>
)
}
onClick props is kinda doesn't work, and i don't get the point why.
in example i see how he get that prop 'onClick' and pass it to onClick of child element, but i don't know the approach to this problem.
quick note:
there is not exactly error. I expected the when i click on Tab Component it should do the handleTabClick arrow function from parrent component, but there is no result
I'm wiriting a kibana plugin and I have some problems with a flyout component. That's my starting code:
export const InputPipelineDebugger = ({queryParams, setQueryParams, setConnectionType, setMessage}) => {
const onChangeTest = (e) => {
setMessage(e.target.value);
}
const onTabConnectionTypeClicked = (tab) => {
setConnectionType(tab.id);
}
var tabsConnection = [
{
id: 'http',
name: 'HTTP',
content: <HttpInput onChangeTest = {onChangeTest} queryParams = {queryParams} setQueryParams={setQueryParams} />
},
{
id: 'syslog',
name: 'SYSLOG',
content: <SyslogInput onChangeTest = {onChangeTest} />
},
{
id: 'beats',
name: 'BEAT',
content: <BeatsInput onChangeTest = {onChangeTest} />
}
];
return (
<EuiFlexItem>
<h3>Input</h3>
<EuiTabbedContent
tabs={tabsConnection}
initialSelectedTab={tabsConnection[0]}
autoFocus="selected"
onTabClick={tab => {
onTabConnectionTypeClicked(tab);
}} />
</EuiFlexItem>
);
}
And what I want is to dynamically build the tabs array according to the response from a rest call. So I was trying to use the useEffect method and for that I change the tabsConnection with a state (and a default value, that works WITHOUT the useEffect method) but is not working at all. Console saids to me that the 'content' value from the tabs array is undefined, like if it's not recognizing the imports.
How can I achieve my goal? Thanks for the support
export const InputPipelineDebugger = ({queryParams, setQueryParams, setConnectionType, setMessage}) => {
//initialized with a default value
const [tabs, setTabs] = useState([{
id: 'syslog',
name: 'SYSLOG',
content: <SyslogInput onChangeTest = {onChangeTest} />
}]);
const onChangeTest = (e) => {
setMessage(e.target.value);
}
const onTabConnectionTypeClicked = (tab) => {
setConnectionType(tab.id);
}
useEffect(()=>{
//rest call here;
//some logics
var x = [{
id: 'beats',
name: 'BEATS',
content: <BeatsInput onChangeTest = {onChangeTest} />
}];
setTabs(x);
}, []);
return (
<EuiFlexItem>
<h3>Input</h3>
<EuiTabbedContent
tabs={tabs}
initialSelectedTab={tabs[0]}
autoFocus="selected"
onTabClick={tab => {
onTabConnectionTypeClicked(tab);
}} />
</EuiFlexItem>
);
}
Errors from the console:
Uncaught TypeError: Cannot read property 'content' of undefined
at EuiTabbedContent.render
EDIT 1
Here the code of BeatsInput and SyslogInput:
import {
EuiText,
EuiTextArea,
EuiSpacer,
EuiFlexItem,
} from '#elastic/eui';
import React, { Fragment, useState } from 'react';
export const SyslogInput = ({onChangeTest}) => {
return (
<EuiFlexItem>
<EuiFlexItem >
<EuiSpacer />
<EuiText >
<EuiTextArea fullWidth={true}
style={{ height: "450px" }}
onChange={e => onChangeTest(e)}
placeholder="Scrivi l'input"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexItem>
)
}
import {
EuiText,
EuiTextArea,
EuiSpacer,
EuiFlexItem,
} from '#elastic/eui';
import React, { Fragment, useState } from 'react';
export const BeatsInput = ({onChangeTest}) => {
return (
<EuiFlexItem>
<EuiFlexItem >
<EuiSpacer />
<EuiText >
<EuiTextArea fullWidth={true}
style={{ height: "450px" }}
onChange={e => onChangeTest(e)}
placeholder="Scrivi l'input"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexItem>
)
}
Change initialSelectedTab to selectedTab [or just add it in addition to]
https://elastic.github.io/eui/#/navigation/tabs
You can also use the selectedTab and
onTabClick props to take complete control over tab selection. This can
be useful if you want to change tabs based on user interaction with
another part of the UI.
Or work around:
give tabs an empty default value
const [tabs, setTabs] = useState();
render the component conditionally around tabs
{tabs && (
<EuiTabbedContent
tabs={tabs}
initialSelectedTab={tabs[0]}
autoFocus="selected"
onTabClick={tab => {
onTabConnectionTypeClicked(tab);
}}
/>
)}
I do sorting on reactjs, I can’t understand how to redraw all child components so that only one selected remains active, I can update the current one, but the others do not change. Here is the code for an example. Can anyone help / explain how to do it right?
nodejs, webpack, last reactjs
App.js
import React, { Component } from "react";
import Parent from "./Parent";
class App extends Component {
render() {
return(
<Parent />
)
}
}
export default App;
Parent.js
import React, { Component } from "react";
import Child from "./Child";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
popularity: {"sorting": "desc", "active": true},
rating: {"sorting": "desc", "active": false},
reviews_count: {"sorting": "desc", "active": false},
};
}
updateFilters = () => {
// ??
};
render() {
return (
<div>
<Child type="popularity" sorting={this.state.popularity.sorting} active={this.state.popularity.active} updateFilters={this.updateFilters} />
<Child type="rating" sorting={this.state.rating.sorting} active={this.state.rating.active} updateFilters={this.updateFilters} />
<Child type="reviews_count" sorting={this.state.reviews_count.sorting} active={this.state.reviews_count.active} updateFilters={this.updateFilters} />
</div>
)
}
}
export default Parent;
Child.js
import React, { Component } from "react";
class Child extends Component {
handleClick = () => {
this.props.updateFilters();
};
render() {
let activeStr = "";
if (this.props.active) {
activeStr = "active"
} else {
activeStr = "inactive";
}
return(
<div onClick={() => this.handleClick}>
{this.props.type} {activeStr} {this.props.sorting}
</div>
);
}
}
export default Child;
Assuming you are trying to set the active flag for a clicked Type to true and also set all the other types to false.
<div onClick={() => this.handleClick}> this isn't correct, as you aren't invoking the function. This could be corrected to:
<div onClick={() => this.handleClick()}>
Then you can update handleClick to pass the Type:
handleClick = () => {
this.props.updateFilters(this.props.type);
};
OR
You could ignore that handleClick and call the prop function:
<div onClick={() => this.props.updateFilters(this.props.type)}>
Once you have passed the Type back into the updateFilters, we can simply iterate over the previous State Properties, setting all Types' Active Flag to false. However, if the Key matches the Type which was clicked, we set it to true.
updateFilters = type => {
this.setState(prevState => {
return Object.keys(prevState).reduce(
(result, key) => ({
...result,
[key]: { ...prevState[key], active: key === type }
}),
{}
);
});
};
Your Child component could be heavily refactored into a Pure Functional Component, making it a lot simpler:
const Child = ({ type, active, updateFilters, sorting }) => (
<div onClick={() => updateFilters(type)}>
{type} {active ? "active" : "inactive"} {sorting}
</div>
);
Work solution:
https://codesandbox.io/s/4j83nry569
I am trying to make each individual list item clickable.
Here I have a color change state so that the color changes to red. But everytime I click one list item, it makes all of the boxes red. Again I want to select which ones to turn red, not turn all of them red. a hint in the right direction or a link would do fine.
import React, { Component } from 'react';
import './App.css';
import MainTool from './components/Layout/MainTool/MainTool.js';
import Aux from './hoc/Aux.js';
class App extends Component {
state = {
MainList: [
"GamePlay",
"Visuals",
"Audio",
"Story"
],
color: "white"
}
changeEle = () =>{
this.setState({color : "red"});
}
render() {
return (
<Aux>
<MainTool MainList = {this.state.MainList}
change = {this.changeEle}
color = {this.state.color}/>
</Aux>
);
}
}
export default App;
This MainTool just shoots my state arguments to Checkblock. Just for reference. Its unlikely the error is here although I have been wrong plenty times before.
import React from 'react';
import './MainTool.css';
import CheckBlock from '../../CheckBlock/CheckBlock';
const MainTool = props => {
return (
<div className = "mtborder">
<CheckBlock MainList = {props.MainList}
change = {props.change}
color = {props.color}/>
</div>
);
};
export default MainTool;
And here is my best guess at where the problem is. I used a loop to iterate through my state object array and print out the list and divs next to each list item. the divs being the elements I want to make clickable individually.
import React from 'react';
import './CheckBlock.css';
import Aux from '../../hoc/Aux';
const CheckBlock = props => {
console.log(props.change);
console.log(props.color);
let mainList = [];
for(let i = 0; i <= 3; i++)
{
mainList[i] = <li key = {i}
className = "nameBox">{props.MainList[i]}
<div onClick = {props.change}
style = {{backgroundColor: props.color}}
className = "clickBox"></div></li>
}
//console.log(dubi());
return (
<Aux>
<ul className = "mainList">{mainList}</ul>
<button>Enter</button>
</Aux>
);
};
export default CheckBlock;
You need a state based ListItem component with internal color state. No need to pass function as prop to change color. Use internal method
class ListItem extends Component {
state = { color : 'white' };
onClick = () => {
this.setState({ color: 'red' });
}
render () {
return (
<li key={i} className="nameBox">
{this.props.value}
<div onClick={this.onClick} style={{backgroundColor: props.color}}
className="clickBox">
</div>
</li>
);
}
}
const CheckBlock = props => {
console.log(props.change);
console.log(props.color);
let mainList = [];
for(let i = 0; i <= 3; i++)
{
mainList[i] = <ListItem key={i} value={props.MainList[i]} />
}
return (
<Aux>
<ul className = "mainList">{mainList}</ul>
<button>Enter</button>
</Aux>
);
};
I put jsfiddle together:
Let me know if there is a better practice to toggle element in React;
Cheers!
List item toggle jsfiddle
// ----------------APP-------------------
class App extends React.Component {
state = {
mainList: [
{
label: 'GamePlay',
id: 1,
},
{
label: 'Visuals',
id: 2,
},
{
label: 'Audio',
id: 3,
},
{
label: 'Story',
id: 4,
},
],
}
render() {
const { mainList } = this.state;
return (
<div>
<List mainList={mainList} />
</div>
);
}
}
// ----------------LIST-------------------
const List = ({ mainList }) => (
<div>
{mainList.map((listItem) => {
const { label, id } = listItem;
return (
<ListItem key={id} id={id} label={label} />
);
})}
</div>
);
// ----------------LIST-ITEM-------------------
class ListItem extends React.Component{
state = {
selected: false,
}
changeColor = () => {
const { selected } = this.state;
this.setState({selected: !selected})
}
render(){
const { label, id } = this.props;
const { selected } = this.state;
return console.log(selected ,' id - ', id ) || (
<button
className={selected ? 'green' : 'red'}
onClick= {() => this.changeColor()}
>
{label}
</button>
)
}
}