简体   繁体   中英

React - parent class component function change class of child component

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

  1. this isn't bound correctly in constructor for handleClick .

Child component.

  1. openTasks is a functional component, so there is no this , or rather, this will just be undefined.
  2. 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

  1. Fix the this binding in the constructor
  2. Update handleClick to consume a task id to toggle active state of
  3. 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

  1. Remove all the this references since this is a functional component.
  2. Consume props object.
  3. Add an "active" class if props.active matches the current task id.
  4. 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>
  );
}

编辑 react-parent-class-component-function-change-class-of-child-component

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, ie "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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM