I have two components, a parent component and its children components.
In the parent component I store a state for 'active' which holds the id of the active child component. What I'd like to do is have a handleClick function that compares the id of the child component which is being clicked to the value of 'active' and if it's the same, or different, i'd like it to update html className of the child component to achieve a certain style effect (which may include some animation).
Questions:
Is there a way to target the className of a particular child element and update it?
Is it instead better to handle this function in the child itself while storing the id of the 'active' child in the state of the parent component?
If i'm looking to achieve css animations based on the change in className of the child component from one className to another, are there additional considerations, such as including that the animation run on render of the component, if i'm hoping to animate the change this way?
I'm sure there are other ways to approach this and i'm totally open to suggestions on the best approach to achieve the above, but I'll also include that I am just getting started with react and haven't learned about how to use hooks yet. I'm still working with basic functional and class based components.
Thanks in advance and example code with pseudo code below.
example parent component:
import React, {Component} from "react";
import Task from './openTasks';
import TasksData from './tasks-data';
class openTaskAccordion extends Component{
constructor(){
super()
this.state = {
//holds the id of the currently opened/active item but initialized to -1 since there is no item with an id of -1 at initialization.
active: -1
}
this.handleClick() = this.handleClick.bind(this);
}
handleClick(){
//if (the id of the clicked task === this.state.active){
// change the className of the clicked child component to "closed"
// } else {
// change the className of the child component with id == this.state.active to "closed", change the className of the clicked child component to "open" and update this.state.active to the id of the now open child component with setState.
//
}
render(){
const Tasks = TasksData.map(task=> <Task key={task.id} task ={task}/>)
return(
Tasks
)
}
}
export default openTaskAccordion
example child component
import React from "react";
import "./OpenTasks.css"
function openTasks(){
return (
<div id = {props.task.id} className="tasks" value = {props.task.id}>
<h1 >{props.task.clientName}</h1>
<div className="accordion-item accordion-item-closed" >
<h2>{props.task.matter}</h2>
<p> - {props.task.matterStatus}</p>
</div>
</div>
);
}
export default openTasks
Issues
Parent component
this isn't bound correctly in constructor for handleClick.
Child component.
openTasks is a functional component, so there is no this, or rather, this will just be undefined.
openTasks doesn't consume any of the props passed to it.
To answer questions
Is there a way to target the className of a particular child element and update it?
You could do this, but direct DOM manipulations and reaching into other components to change things is anti-pattern in React. The react way is to pass data as props (data including what is or isn't "active") and letting children components handle it locally.
Is it instead better to handle this function in the child itself while
storing the id of the 'active' child in the state of the parent
component?
No, I don't think so, the parent should store the single source of truth about the current "active" child. Pass props to the child, including any callbacks the child could/should call to update state in the parent.
Solution
The parent component should store the active child, as you've done, but you should pass the active id and a callback to the children in order for them to be "clickable" and allow the parent to update what the active child is.
OpenTaskAccordion
Fix the this binding in the constructor
Update handleClick to consume a task id to toggle active state of
Pass active state and onClick callback to Task
code
class OpenTaskAccordion extends Component {
constructor() {
super();
this.state = {
active: -1
};
this.handleClick = this.handleClick.bind(this); // <-- fix this binding
}
handleClick(id) { // <-- consume task id
this.setState((prevState) => ({
...prevState,
active: id === prevState.active ? -1 : id
}));
}
render() {
const { active } = this.state;
const { tasks = [] } = this.props;
return tasks.map((task) => (
<OpenTask
key={task.id}
active={active}
task={task}
onClick={this.handleClick}
/>
));
}
}
OpenTasks
Remove all the this references since this is a functional component.
Consume props object.
Add an "active" class if props.active matches the current task id.
Attach props.onClick to something clickable to toggle the active state in parent.
code
function OpenTask(props) { // <-- consume `props`!!
return (
<div
id={props.task.id}
className={["tasks", props.active === props.task.id && "active"].join(" ")}
value={props.task.id}
onClick={() => props.onClick(props.task.id)} // <-- attach onClick callback
>
<h1>{props.task.clientName}</h1>
<div className="accordion-item accordion-item-closed">
<h2>{props.task.matter}</h2>
<p> - {props.task.matterStatus}</p>
</div>
</div>
);
}
CSS used to apply an "animation" for active item toggling. Uses a simple CSS transition on the background color.
.tasks {
transition: background-color ease-in-out 0.5s;
}
.active {
background-color: lightblue;
}
Comment Questions
In the functional child component, where can I read about what this
is doing? className={["tasks", props.active === props.task.id && "active"].join(" ")}
It is simply a way to create a list of space-separated class names, i.e. "tasks" or "tasks active" from ["tasks"] or ["tasks", "active"].
Some alternative methods include
className={`tasks ${props.active === active ? "active" : ""}`}
className={"tasks" + `${props.active === active ? " active" : ""}`}
In the parent class component, what is this doing and why isn't
{tasks =[] } overridden by the data set you created in your
example?
const { active } = this.state;
const { tasks = [] } = this.props;
const { tasks = [] } = this.props; is just a way to provide a defined value for the mapping in the case that this.props.tasks is undefined (or falsey), as would be the case if a tasks prop was not passed to the component. So long as this.prop.tasks is a defined truth value then that is what is used. Consider this a fallback value.
Related
I'm using following React Native module:
https://github.com/shariqahmed1/react-native-animated-tree-view
I like to get clicked item from a TreeView.
Documentation says I should be able to get clicked item by onClick prop.
My attempt was like that:
<TreeView
onPress={() => console.log(props.onClick)} //Cannot get clicked item
data={data} //Works Okay
/>
I'm able to give source data successfully but I can not get the clicked item from tree view.
How can I get the child component value from parent component?
You can use Ref/createRef to give it a unique reference (just like ID) and then you have the access to it:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
render() {
// tell React that we want to associate the <input> ref
// with the `textInput` that we created in the constructor
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
In the onclick event handler, use this.onClick() instead of props.onClick() and in this.onClick, you can access child component using this.textInput.current to access the child component.
You should use onClick inplace of onPress as per the doc to get clicked item:
onClick={props.onClick} // <-- will call parent component's onClick
// OR
onClick={(item) => props.onClick(item)} // <-- will call parent component's onClick
To check console, try below code :
onClick={(item) => { console.log(item); }}
I have a parent Component which sends a list of data to a List component which in turn sends it to a Label Component to display each data as a label.
I want to be able to focus on the label element when i click on it so that the appropriate style is applied ..
Below is the gist :-
class ContainerComp extends React.Component {
constructor(props) {
super(props);
this.state = {
group: [1, 2, 3]
};
clickHandler = (name, ref) = > {
// I am able to get the DIV as a html element here but calling .focus() on it dosent change the style where as when i explictly add focus using chrome debugger for the element it works.
ref.focus() // not working
}
render() {
return ( <
ListComp group = {
group
}
onClick = {
clickHandler
} >
)
}
}
function ListComp(props) {
const data = props.group.map(... < label onClick = {} > )
return ( <
Label.. >
)
}
function Label(props) {
let ref = createref();
// on focus style for the component is defined in this component
// i am making use of css modules
return ( <
div ref = {
ref
}
onClick = (name, ref) >
)
}
<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>
How can we achieve such a functionality without having to pass a selected prop to the label component ? By default i would select the first element and keep the focus .. or we can make it configurable.
Usually for this I would use Redux and fire off an action which therefore sets the property of the component that needs change, and make a listener that will listen for that specific prop and change style accordingly.
In this situation, id just pass down the event handler to the child component (remember to not call it when you pass it down, so do:
{() => {eventHandler()}}
and then in the child component do:
onClick={this.props.eventHandler(e)}
You will use the event to identify which element triggered it and then apply the class/style/prop to it.
There was some problem with the Ref , I am not quite sure why but i changed it to use the useRef() hook.
Label Component
const elementRef = useRef(null);
return (
<div className={[externalStyle, styles.container].join(' ')} onClick={() => onClickEvent(itemName, elementRef)} ref = {elementRef} tabIndex={1}> // added tabIndex and also changed to useRef
Container Component
clickHandler = (name: string, ref) => {
ref.current.focus(); // volla it worked
}
I tried using the old form of Ref and also useRef() without null previously (el) => (const = el).
It works if some one has some explanation where i went wrong i will be happy to listen as i am able to wrap my head around. may be a nights sleep helped fix it :P
I've created a React component which takes any component and renders it as a Pop-up. A parent component receives the component to be rendered (popped up). The rendered component is here the child component which using react-sizeme to get its size and pass back to parent component. The parent component must take the dimensions of child component, so adjusts' its height and width. This is the code:
class Popup extends React.Component<IPopupProps,IComponent>{
constructor(props:IPopupProps){
super(props);
this.state={
childComponent:this.props.children,
style:{
height:0,
width:0
}
}
}
// This function runs two times, before and after rendering child component
// & so have an improper visualization as the size is changed twice here
public OnSize = (size:any) =>{
const width = size.width +20;
const height =size.height+20;
this.setState({
style:{height,
width }
})
}
public render(){
return(
<div className='popup'>
<div style={this.state.style} className='popup-content'>
<a className="close" onClick={this.props.onExit}>
×
</a>
<this.state.childComponent onSize={this.OnSize}/>
</div>
</div>
)
}
}
The initial width and height is set to 0. So it doesn't renders properly. So is there any way so that to hide the child component or avoid its rendering before parent component gets the size?
EDIT: We can't get the size until the child component is rendered. So is there any trick to get this done. Just a component needs to be popped-up properly.
EDIT 2: Here's the PropsBuilder.tsx which calls the Popup.tsx and sends the component to display as children
class PopupBuilder extends React.Component<IPopupBuilderProps, IPopup>{
constructor(props:IPopupBuilderProps){
super(props);
this.state = {
showPopup:false
}
}
public togglePopup = () =>{
this.setState({
showPopup:!this.state.showPopup
})
}
public render (){
return(
<React.Fragment>
<button onClick={this.togglePopup}>{this.props.trigger}</button>
<React.Fragment>
{this.state.showPopup?<Popup onExit={this.togglePopup} >{this.props.component}</Popup>:null}
</React.Fragment>
</React.Fragment>
)
}
}
export default PopupBuilder;
Actually, this looks like more general DOM/JavaScript question.
Consider such case:
const span = document.createElement('span');
span.innerText = 'hello';
span.getBoundingClientRect() // -> { width: 0, height: 0, top: 0, … }
This is an indicator that you don't know the dimensions of the element until it is in DOM (Rendered in react);
document.body.appendChild(span);
span.getBoundingClientRect(); // -> {width: 50, height: 16, …}
My recommendation to you in this case are:
Child component should accept a property (function) from Parent one
Use React "ref" feature to find actual dimensions of Child element
Call the function in 'componentDidMount' (use componentDidUpdate if child can change dynamically), passing it child component dimensions.
If you don't have access to child component. You may wrap it like this:
// Popup.tsx
class Popup .... {
...
render() {
<Measurer>{this.props.children}</Measurer>
}
}
and implement the logic of fetching dimensions in it. Measurer is a direct child of Popup and their communication can be controlled by you.
I have started an application which I want to work same as weather.com next 36 hours section. The idea is when you click on each weatherCard which has a seperate component in my app you will update the below section which is my weatherDetails component based on the selected weatherCard /weather box. So I made the entire component clickable by giving it the click event via props from my stateful component which is my weatherLocation component. This is my WeatherCard component:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={props.clicked}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
And here in render method in WeatherLocation component I loop through data coming from state and give props the WeatherCard component:
const WeatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
{...report}
clicked={() => this.handleCardClick(event)}
/>
);
});
And this is the handleCardClick that I added for it just for testing:
handleCardClick = event => {
// const { reports , selectedCardInfo , activeCard } = this.state;
const selectedDate = document.getElementById(event.target.id);
console.log(event.target.id);
}
I don't want to use anchor tag as I don't need href. The click works fine by itself. But because I need to get the id of the parent which is the div with the class of weatherCard. At the moment when I click on other elements inside the card I cannot get the id because they are not the parent. The reason I need its id is when I get data with from the API I need a unique value for each card so that when you click on the card the data for that card will be shown in the other component which is the WeatherDetails component. But for now I need to be able to somehow choose that selected card and pull out the state for that unique card. Could someone help me out? Thanks.
You just need to pass the Parent component ID to your onClick function in Weather Card.
Here is your WeatherCard - Component
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
You can see that I have added props.id to your onClick function and with help of event now you can access that id from the parent component.
Now here is your Parent Component- WeatherCards
const WeatherCards = this.state.reports.map( (report, i) => {
return(
<WeatherCard
key={report.id}
id={i}
{...report}
clicked={this.handleCardClick}
/>
);
});
You can see in the code I am passing index number as id to your child component.
So this will give you an id (for now it's an index number) of the card in your onClick handler.
and Finally, here is your on click handler.
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
As of now, I am using the index as id if you want to use a unique identifier, you can change that easily.
General JavaScript solution is to differentiate the elements and .stopPropogation after you've captured the event you are targeting. A nested unordered list, <ul>would be an example. Tag the containing <li> with an .opened class upon rendering/displaying each level of nesting, tag those <li> elements accordingly, e.g. a dataset attribute such as data-make, then data-model, then data-option. You then attach and fire event listeners on the different level <li>'s.
Thank you #RutulPatel. I made your answer as the answer. But I changed your code a bit as I got your point so I wrote an answer as it is long. I think we might not need to change the WeatherCard at all and I don't pass event or any logic there. so it will be intact:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
But I use your tip changing my weatherCards array to look like this:
const weatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
id={report.date}
{...report}
clicked={() => this.handleCardClick(event, report.date)}
/>
);
});
So I use the report.date which is a unique value as my id. Also I don't pass event as a parameter to the arrow function I just pass it with the report.date to the handler:
clicked={() => this.handleCardClick(event, report.date)}
And the handler will be the same as you did:
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
I might even remove event later on from both if there was no need fo that.
Thank you again.
I am starting my adventure with React so it is a hard time for me, however I prepared such pen for you to test. Here is a portion of code:
class App extends React.Component {
constructor() {
super();
this.state = {
settings: true,
next: false,
};
}
toggler(abc) {
console.log(">>", abc)
this.setState({
next: !this.state.next
/* {abc}: this.state.{abc} */
})
console.log(this.state.next)
}
render() {
return (
<div className="kalreg">
<MyButton name='settings' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={this.state.next} type="next" toggle={this.toggler.bind(this)}/>
</div>)
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
}
onChangeName(){
console.log(this.props.type)
if ( this.props.isActive ) { console.log("this one is active"); } else { console.log("ouch! it is not active, ignoring!"); return;}
this.props.toggle(this.props.type);
}
render () {
if ( this.props.isActive ) {
return ( <div className="button notVisible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
} else {
return ( <div className="button visible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
}
}
}
ReactDOM.render(<App />, document.getElementById("app"));
What I am trying to achieve is that when i press one of "settings" buttons (yellow) the "next" button becomes unclickable (green). There is a toggle function that every time I click settings button it turns on and off "next" button.
It works quite good, however it is just a draft of bigger project and i want to automate it a little bit.
As you can see I create my <MyButton> with both "isActive" and "type" props. But isActive holds what's inside this.state.settings while type is "settings". Instead of using two variables it would be great to pass only type of button to its component and component, depending on its type would check its parent's this.state.{type}. I used {type} because i would like to check it dynamically. Is that possible?
If so - how to do it?
My first attempt is to pass type from <MyButton> to <App> via toggler function. I named the variable "abc". I commented the way I wanted to do it because it doesn't work:
{abc}: !this.state.{abc}
Any idea to solve this problem would be more than appreciated.
Kalreg.
It is somewhat unclear what you are trying to achieve here. If you want to wire the state dynamically based on type, as you wrote in code: {abc}: !this.state.{abc} each button would toggle itself, not the next button. In this case your syntax is a little incorrect, it will work if you write it like:
[abc]: !this.state[abc]
However as I said, in your example, this makes the settings button change the state for this.state.settings disabling itself instead of the next button.
Another note would be, that if it is not necessary for the MyButton component to know its own type for other reasons, it is unnecessary to pass it as a prop and than make the component pass it back as an argument (this.props.toggle(this.props.type);). You can simply define the toggle function in the parent as:
toggle={() => this.toggler("settings")}
without passing type as a prop.
So basically we want to have the settings and settings2 buttons, and when we click on them, they toggle the state of the next button by making it un-clickable (green).
So if that is our goal, then
we don't need an isActive prop for the settings button. (Because it's always going to be active no matter what)
We also don't need to have a toggle prop on the Next button. (Because clicking the next button isn't supposed to toggle anything)
Instead of having two variables in the state why not just have one and then use that to determine the isActive prop of the next button?
The component would look like this:
constructor() {
super();
this.state = {
nextIsActive: false,
};
}
toggler() {
this.setState({
nextIsActive: !this.state.nextIsActive
})
console.log(this.state);
}
render() {
const {nextIsActive} = this.state
return (
<div className="kalreg">
<MyButton name='settings' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={nextIsActive}/>
</div>
)
}
That way you don't have to have 2 state properties that you have to dynamically update because it adds more complexity to your application.
You can see the finished product here: Codepen