I'm working on building my portfolio using React.js. In one section, I have four components laid out in a grid. What I want to do achieve is when one component is clicked, a css class is added to the siblings of this component so that their opacity is reduced and only the clicked component remains. In jQuery, it would be something like $('.component').on('click', function(){ $(this).siblings.addClass('fadeAway')}). How can I achieve this effect? Here is my code, thanks in advance for any and all help!
class Parent extends Component{
constructor(){
super();
this.state = {fadeAway: false}
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
//Add class to siblings
}
render(){
const array = ["Hello", "Hi", "How's it going", "Good Times"]
return(
array.map(function(obj, index){
<Child text={obj} key={index} onClick={() => this.handleClick} />
})
)
}
}
A working example for this problem could look something like this, with a marginally more complex initialization array:
class Parent extends Component{
constructor(){
super();
this.state = {
elements: [
{
id: "hello",
text: "Hello",
reduced: false,
},
{
id: "hi",
text: "Hi",
reduced: false,
}
{
id: "howsItGoing"
text: "How's it going",
reduced: false,
}
{
id: "goodTimes",
text: "Good Times",
reduced: false,
}
],
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(e){
// copy elements from state
const elements = JSON.parse(JSON.stringify(elements));
const newElements = elements.map(element => {
if (element.id === e.target.id) {
element.reduced = false;
} else {
element.reduced = true;
}
});
this.setState({
elements: newElements,
});
}
render(){
return(
this.state.elements.map(function(obj, index){
<Child
id={obj.id}
text={obj.text}
reduced={obj.reduced}
key={index}
onClick={() => this.handleClick} />
});
);
}
}
Then you would just add a ternary, like so, to the Child component:
<Child
id={this.props.id}
className={this.props.reduced ? "reduced" : ""} />
This adds a bit more boilerplate than other examples, but it's extremely brittle to tie business logic to the text inside a component, and a stronger solution requires a stronger piece of identification, like an ID or class on the rendered DOM element. This solution also, if you so wish, easily allows you to expand your logic so that more than one element can remain at maximum opacity at once.
I would simply store in state index of selected item, and then pass fadeAway prop into Child component defined as
fadeAway={this.state.selectedIndex !== index}
After that you only need to set a fade-away class in Child based on this.prop.fadeAway and define necessary CSS rules.
Here is how it could look in your case:
class Parent extends React.Component{
constructor () {
super();
this.state = {selectedIndex: null}
}
handleClick (selectedIndex) {
this.setState({ selectedIndex })
}
render () {
const array = ["Hello", "Hi", "How's it going", "Good Times"]
return (
<div>
{array.map((obj, index) => {
const faded = this.state.selectedIndex && this.state.selectedIndex !== index
return <Child
text={obj}
fadeAway={faded}
key={index}
onClick={() => this.handleClick(index)} />
})}
</div>
)
}
}
class Child extends React.Component {
render () {
return (
<h2
onClick={this.props.onClick}
className={this.props.fadeAway ? 'fade-away' : ''}>
{this.props.text}
</h2>
)
}
}
ReactDOM.render(
<Parent />,
document.body
);
.fade-away {
opacity: 0.3;
}
<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>
You can achieve that using using a toggle variable :
handleClick(){
this.setState({fadeAway} => ({
fadeAway: ! fadeAway
)};
}
...
<Child
text={obj}
key={index}
onClick={() => this.handleClick}
className={this.state.fadeAway? 'class1' : 'class2'}/>
I perfer use state like currentWord to save the word was clicked in Parent component, presudo code is like below:
class Parent extends Component {
constructor(){
super();
this.state = {
fadeAway: false,
currentWord: ''
};
this.handleClick = this.handleClick.bind(this)
}
handleClick(currentWord){
this.setState({
currentWord: currentWord,
});
}
render(){
const array = ["Hello", "Hi", "How's it going", "Good Times"]
const currentWord = this.state.currentWord;
return(
array.map(function(obj, index){
<Child currentWord={currentWord} text={obj} key={index} onClick={() => this.handleClick} />
})
)
}
}
And in Child component
class Child extends Component {
// some other code
handleClick(e) {
this.props.handleClick(e.target.value);
}
render() {
const isSelected = this.props.text === this.props.currentWord;
// use isSelected to toggle className
<div
onClick={this.handleClick.bind(this)}
>{this.props.text}
</div>
}
}
Related
I'm running into the issue where I have created a functional component to render a dropdown menu, however I cannot update the initial state in the main App.JS. I'm not really sure how to update the state unless it is in the same component.
Here is a snippet of my App.js where I initialize the items array and call the functional component.
const items = [
{
id: 1,
value:'item1'
},
{
id: 2,
value:'item2'
},
{
id: 3,
value:'item3'
}
]
class App extends Component{
state = {
item: ''
}
...
render(){
return{
<ItemList title = "Select Item items= {items} />
And here is my functional componenet. Essentially a dropdown menu from a YouTube tutorial I watched (https://www.youtube.com/watch?v=t8JK5bVoVBw).
function ItemList ({title, items, multiSelect}) {
const [open, setOpen] = useState (false);
const [selection, setSelection] = useState([]);
const toggle =() =>setOpen(!open);
ItemList.handleClickOutside = ()=> setOpen(false);
function handleOnClick(item) {
if (!selection.some(current => current.id == item.id)){
if (!multiSelect){
setSelection([item])
}
else if (multiSelect) {
setSelection([...selection, item])
}
}
else{
let selectionAfterRemoval = selection;
selectionAfterRemoval = selectionAfterRemoval.filter(
current =>current.id == item.id
)
setSelection([...selectionAfterRemoval])
}
}
function itemSelected(item){
if (selection.find(current =>current.id == item.id)){
return true;
}
return false;
}
return (
<div className="dd-wraper">
<div tabIndex={0}
className="dd-header"
role="button"
onKeyPress={() => toggle(!open)}
onClick={() =>toggle(!open)}
onChange={(e) => this.setState({robot: e.target.value})}
>
<div className="dd-header_title">
<p className = "dd-header_title--bold">{title}</p>
</div>
<div className="dd-header_action">
<p>{open ? 'Close' : 'Open'}</p>
</div>
</div>
{open && (
<ul className ="dd-list">
{item.map(item =>(
<li className="dd-list-item" key={item.id}>
<button type ="button"
onClick={() => handleOnClick(item)}>
<span>{item.value}</span>
<span>{itemSelected(item) && 'Selected'}</span>
</button>
</li>
))}
</ul>
)}
</div>
)
}
const clickOutsideConfig ={
handleClickOutside: () => RobotList.handleClickOutside
}
I tried passing props and mutating the state in the functional component, but nothing gets changed. I suspect that it needs to be changed in the itemSelected function, but I'm not sure how. Any help would be greatly appreciated!
In a function component, you have the setters of the state variables. In your example, you can directly use setOpen(...) or setSelection(...). In case of a boolean state variable, you could just toggle by using setOpen(!open). See https://reactjs.org/docs/hooks-state.html (Chapter "Updating State") for further details.
So you need to do something like below . Here we are passing handleChange in parent Component as props to the child component and in Child Component we are calling the method as props.onChange
Parent Component:
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
value :''
}
}
handleChange = (newValue) => {
this.setState({ value: newValue });
}
render() {
return <Child value={this.state.value} onChange = {this.handleChange} />
}
}
Child Component:
function Child(props) {
function handleChange(event) {
// Here, we invoke the callback with the new value
props.onChange(event.target.value);
}
return <input value={props.value} onChange={handleChange} />
}
I want to have 3 buttons and 3 related components . So that when button click show its related component . Here is my code :
Main Component :
class Main extends Component { constructor(props) {
super(props);
this.state = {
show: false,
}}
onClick(e) {
e.preventDefault()
console.log(e.target.id)
this.setState({ show: !this.state.show }); }
renderTabs() {
var child = this.props.child;
var items = [];
__.forEach(child, (item, index) => {
items.push(
<div>
<Button id={index} onClick={(e) => this.onClick(e)}>{item.data}</Button>
{this.state.show ? (
<CropComponent title={item.data} opts={item.opts} data={item.child} />
) : null}
</div>
);
});
return items;
}
render() {
var opts = this.props.opts;
return (
<div>
{this.renderTabs()}
</div>
)}
}
export default Main;
And here is my CropComponent :
class CropComponent extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>hello {this.props.data}</div>
);
}
}
export default CropComponent;
Here are my buttons how can i show its related component when click on a button and hide it on re-click the same button ?
Maintain a show state with initial value as -1. Supply event and index to onClick. In onClick do setState of show as index.
Check if show === index and render the corresponding component.
Like this
this.state = {
show: -1
}
onClick(e,index) {
e.preventDefault()
this.setState({ show: show===index? -1 : index});
}
__.forEach(child, (item, index) => {
items.push(
<div>
<Button id={index} onClick={(e) => this.onClick(e,index)}>{item.data}</Button>
{this.state.show === index ? (
<CropComponent title={item.data} opts={item.opts} data={item.child} />
) : null}
</div>
);
});
Edit:
updated the answer based on helpful comment by #Tony Nguyen
Expecting effect: click <li> --> take index --> send this index to component Watch.
When I click <li>, I grab the index and move it to theWatch component. However, when I click the second li it returns the index of the one I clicked for the first time. I think this is because it updates this index via componentDidMount. How can I reference this index after componentDidMount?
Todo
class Todo extends Component {
render () {
return (
<div className = "itemTodos" onClick={()=> this.props.selectTodo(this.props.index)}>
</div>
)
}
}
export default Todo;
App
class App extends Component {
constructor(){
super();
this.state {
selectedTodoIndex: index
}
}
selectTodo = (index) => {
this.setState({
selectedTodoIndex: index
})
}
render () {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
selectTodo ={this.selectTodo}
/>
)
}
</ul>
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
</div>
)
}
}
export default App;
Watch
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
First of all you you use selectedTodoIndex in
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
but it not specified in your render code. Add
const {selectedTodoIndex} = this.state;
in render function.
Second, use componentDidUpdate in Watch for update inner state on props update:
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
componentDidUpdate (prevProps) {
if (prevProps.selectedTodo !== this.props.selectedTodo)
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
If i am not wrong your Todo component is in watch??. So Watch component should be like this :
render () {
return (
<div>
<Todo index={this.state.selectedIndex} selectedTodo={this.props.selectedTodoIndex}/>
</div>
)
}
Here i made codesandbox of this code . Feel free to checkout and let me know if you any doubt. Code link : https://codesandbox.io/s/frosty-chaplygin-ws1zz
There are lot of improvements to be made. But I believe what you are looking for is getDerivedStateFromProps lifeCycle method in Watch Component. So the code will be:
getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.selectedTodoIndex !== prevState.selectedTodoIndex) {
return { selectIndex: nextProps.selectedTodoIndex }
}
}
This will check if the selected index has changed in App Component, if yes it will update the state in Watch Component.
I'm new to ReactJS and I would like to communicate between my components.
When I click an image in my "ChildA" I want to update the correct item image in my "ChildB" (type attribute in ChildA can only be "itemone", "itemtwo", "itemthree"
Here is what it looks like
Parent.js
export default class Parent extends Component {
render() {
return (
<div className="mainapp" id="app">
<ChildA/>
<ChildB/>
</div>
);
}
}
if (document.getElementById('page')) {
ReactDOM.render(<Builder />, document.getElementById('page'));
}
ChildA.js
render() {
return _.map(this.state.eq, ecu => {
return (
<img src="../images/misc/ec.png" type={ecu.type_eq} onClick={() => this.changeImage(ecu.img)}/>
);
});
}
ChildB.js
export default class CharacterForm extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ name: "itemone" image: "defaultone.png"},
{ name: "itemtwo" image: "defaulttwo.png"},
{ name: "itemthree" image: "defaultthree.png"},
]
};
}
render() {
return (
<div className="items-column">
{this.state.items.map(item => (<FrameCharacter key={item.name} item={item} />))}
</div>
);
}
}
I can retrieve the image on my onClick handler in my ChildA but I don't know how to give it to my ChildB. Any hints are welcomed, thanks you!
What you need is for Parent to pass an event handler down to ChildA which ChildA will call when one of the images is clicked. The event handler will call setState in Parent to update its state with the given value, and then Parent will pass the value down to ChildB in its render method.
You can see this working in the below example. Since I don't have any actual images to work with—and to keep it simple—I've used <button>s instead, but the principle is the same.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
clickedItem: 'none',
};
}
render() {
return (
<div>
<ChildA onClick={this.handleChildClick}/>
<ChildB clickedItem={this.state.clickedItem}/>
</div>
);
}
handleChildClick = clickedItem => {
this.setState({ clickedItem });
}
}
const items = ['item1', 'item2', 'item3'];
const ChildA = ({ onClick }) => (
<div>
{items.map(name => (
<button key={name} type="button" onClick={() => onClick(name)}>
{name}
</button>
))}
</div>
);
const ChildB = ({clickedItem}) => (
<p>Clicked item: {clickedItem}</p>
);
ReactDOM.render(<Parent/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
<div></div>
I have an onclick handler:
render() {
return (
<div>
<div className={className} onClick={this.target.bind(this,id)}>{name}</div>
</div>
)
}
and here is the function:
target(test, event) {
event.target.className="addClasss";
}
I was trying the above way to addClass on event target, however is there a better way to do this?
Thanks
Instead of adding a class manually to a DOM element, you can e.g. add an additional state variable that keeps track of the element that has been clicked. You can then use this in the render method to choose which element that should get the class added to it.
Example
class App extends React.Component {
state = {
arr: [{ id: 1, name: "foo" }, { id: 2, name: "bar" }],
clicked: null
};
target(id) {
this.setState({ clicked: id });
}
render() {
const { arr, clicked } = this.state;
return (
<div>
{arr.map(element => (
<div
className={clicked === element.id && "addClass"}
onClick={this.target.bind(this, element.id)}
>
{element.name} {clicked === element.id && "clicked!"}
</div>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>
class App extends React.Component {
state = {
clicked: false,
};
handleClick = () => {
this.setState({ clicked: true });
}
render() {
const { clicked } = this.state;
const className = clicked ? "newClass" : "";
const name = "add newClass";
return (
<div>
<div className={className} onClick={this.handleClick}>{name}</div>
</div>
);
}
}
A little late but for future searchers: use jquery:
target(test, event) {
var element = $(event.target)
element.addClass('your class');
}
OBS: I agree partially with the answers above, react was made to work with states but there are some cases that we don't want to care about controlling every single aspect of our code with states, specially when there are a repetitive class, so I found easier to target these elements with jquery.