REAGIR - alternar classe ao clicar

96

Estou tentando descobrir como alternar uma classe ativa onClickpara alterar as propriedades CSS.

Eu fiz muitas abordagens e li muitas respostas de SO. Usar jquery seria relativamente simples, no entanto, não consigo descobrir como fazer isso com o react. Meu código está abaixo. Alguém pode aconselhar como devo fazer isso?

Sem criar um novo componente para cada item, é possível fazer isso?

class Test extends Component(){

    constructor(props) {

    super(props);
    this.addActiveClass= this.addActiveClass.bind(this);

  }

  addActiveClass() {

    //not sure what to do here

  }

    render() {
    <div>
      <div onClick={this.addActiveClass}>
        <p>1</p>
      </div>
      <div onClick={this.addActiveClass}>
        <p>2</p>
      </div>
        <div onClick={this.addActiveClass}>
        <p>3</p>
      </div>
    </div>
  }

}
Peter Flanagan
fonte

Respostas:

86

Use o estado. Os documentos do Reacts estão aqui .

class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.addActiveClass= this.addActiveClass.bind(this);
        this.state = {
            active: false,
        };
    }
    toggleClass() {
        const currentState = this.state.active;
        this.setState({ active: !currentState });
    };

    render() {
        return (
            <div 
                className={this.state.active ? 'your_className': null} 
                onClick={this.toggleClass} 
            >
                <p>{this.props.text}</p>
            </div>
        )
  }
}

class Test extends Component {
    render() {
        return (
            <div>
                <MyComponent text={'1'} />
                <MyComponent text={'2'} />
            </div>
        );
    }
}
cssko
fonte
3
existe uma maneira de fazer isso sem criar um componente extra?
Peter Flanagan
2
Você deseja separar componentes complexos em componentes menores, consulte facebook.github.io/react/docs/…
cssko
1
Eu quebrei o componente, mas isso ainda adiciona activea cada elemento no clique. Eu só quero que um elemento tenha uma classe ativa
Peter Flanagan
1
Isso não pode ser chamado de classe 'ativa' alternada porque as classes ativas de outros elementos ainda estão lá.
Dat TT
6
você pode atualizar a resposta para usar onClickem vez de todas as letras minúsculas, já que você usou incorretamente
Peter Flanagan
24

Eu preferiria usar o operador "&&" na instrução if embutida. Na minha opinião, ele fornece uma base de código mais limpa dessa forma.

Geralmente você poderia estar fazendo algo assim

render(){
return(
  <div>
    <button className={this.state.active && 'active'}
      onClick={ () => this.setState({active: !this.state.active}) }>Click me</button>
  </div>
)
}

Apenas tenha em mente que a função de seta é um recurso ES6 e lembre-se de definir o valor 'this.state.active' no construtor de classe () {}

this.state = { active: false }

ou se você deseja injetar css em JSX você pode fazer desta forma

<button style={this.state.active && style.button} >button</button>

e você pode declarar a variável json de estilo

const style = { button: { background:'red' } }

lembre-se de usar camelCase em folhas de estilo JSX.

Jimi Pajala
fonte
2
Esta é a maneira mais inteligente de alternar nomes de classe simples em componentes.
SeaWarrior404
12

Bem, seu addActiveClass precisa saber o que foi clicado. Algo como isso pode funcionar (observe que adicionei as informações de quais divs estão ativos como uma matriz de estado, e que onClick agora passa a informação que foi clicado como um parâmetro após o qual o estado é devidamente atualizado - certamente existem maneiras mais inteligentes de faça, mas essa é a ideia).

class Test extends Component(){

  constructor(props) {
    super(props);
    this.state = {activeClasses: [false, false, false]};
    this.addActiveClass= this.addActiveClass.bind(this);
  }

  addActiveClass(index) {
    const activeClasses = [...this.state.activeClasses.slice(0, index), !this.state.activeClasses[index], this.state.activeClasses.slice(index + 1)].flat();
    this.setState({activeClasses});
  }

  render() {
    const activeClasses = this.state.activeClasses.slice();
    return (
      <div>
        <div className={activeClasses[0]? "active" : "inactive"} onClick={() => this.addActiveClass(0)}>
          <p>0</p>
        </div>
        <div className={activeClasses[1]? "active" : "inactive"} onClick={() => this.addActiveClass(1)}>
          <p>1</p>
        </div>
          <div  onClick={() => this.addActiveClass(2)}>
          <p>2</p>
        </div>
      </div>
    );
  }
}
Bane Bojanić
fonte
11

Você também pode fazer isso com ganchos .

function MyComponent (props) {
  const [isActive, setActive] = useState(false);

  const toggleClass = () => {
    setActive(!isActive);
  };

  return (
    <div 
      className={isActive ? 'your_className': null} 
      onClick={toggleClass} 
    >
      <p>{props.text}</p>
    </div>
   );
}  

fonte
4

usando React, você pode adicionar classe de alternância a qualquer id / elemento, tente

style.css

.hide-text{
    display: none !important;
    /* transition: 2s all ease-in 0.9s; */
  }
.left-menu-main-link{
    transition: all ease-in 0.4s;
}
.leftbar-open{
    width: 240px;
    min-width: 240px;
    /* transition: all ease-in 0.4s; */
}
.leftbar-close{
    width: 88px;
    min-width:88px;
    transition: all ease-in 0.4s;
}

fileName.js

......
    ToggleMenu=()=>{
             this.setState({
                isActive: !this.state.isActive
              })
              console.log(this.state.isActive)
        }
        render() {
            return (
                <div className={this.state.isActive===true ? "left-panel leftbar-open" : "left-panel leftbar-close"} id="leftPanel">
                    <div className="top-logo-container"  onClick={this.ToggleMenu}>
                            <span className={this.state.isActive===true ? "left-menu-main-link hide-from-menu" : "hide-text"}>Welcome!</span>
                    </div>

                    <div className="welcome-member">
                        <span className={this.state.isActive===true ? "left-menu-main-link hide-from-menu" : "hide-text"}>Welcome<br/>SDO Rizwan</span>
                    </div>
    )
    }
......
Rizo
fonte
Gostaria de acrescentar que você pode usar o seguinte estilo de código se tiver uma classe estática que não deseja definir para cada uma das instruções "isActive":className={`left-panel ${this.state.isActive===true ? 'leftbar-open' : 'leftbar-close'}`}
Zeliax
4

O React tem um conceito de estado de componentes, então se você quiser trocá-lo, faça setState:

constructor(props) {
  super(props);

  this.addActiveClass= this.addActiveClass.bind(this);
  this.state = {
    isActive: false
  }
}

addActiveClass() {
  this.setState({
    isActive: true
  })
}

Em seu componente, use this.state.isActivepara renderizar o que você precisa.

Isso fica mais complicado quando você deseja definir o estado no componente # 1 e usá-lo no componente # 2. Basta pesquisar mais no fluxo de dados unidirecional de reação e possivelmente reduxar para ajudá-lo a lidar com isso.

Dhorelik
fonte
Eu entendo, statemas isso ainda não explica realmente como atualizar o nome da classe quando um elemento é clicado
Peter Flanagan
em sua marcação jsx faça - <div className={this.state.isActive ? 'active' : ''}>. É sobre renderização condicional, mas ainda depende de estado.
dhorelik
mas isso adicionará a classe ativa a cada item no clique. Só quero que o item seja clicado para ter o activenome da turma
Peter Flanagan
2
por que cada item? Isso adicionará a classe ao item para o qual você adicionou a condição. Idealmente, seus itens devem ser abstraídos para o Itemcomponente. Você o incluirá 3 vezes e terá basicamente 3 itens, cada um com seu próprio isActiveestado. É isso que você quer dizer?
dhorelik
sem dividi-lo em um Itemcomponente, há uma maneira de fazer isso?
Peter Flanagan
2

Um bom exemplo ajudaria a entender melhor as coisas:

HTML

<div id="root">
</div>

CSS

.box {
  display: block;
  width: 200px;
  height: 200px;
  background-color: gray;
  color: white;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
}
.box.green {
  background-color: green; 
}

Código de reação

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {addClass: false}
  }
  toggle() {
    this.setState({addClass: !this.state.addClass});
  }
  render() {
    let boxClass = ["box"];
    if(this.state.addClass) {
      boxClass.push('green');
    }
    return(
        <div className={boxClass.join(' ')} onClick={this.toggle.bind(this)}>{this.state.addClass ? "Remove a class" : "Add a class (click the box)"}<br />Read the tutorial <a href="http://www.automationfuel.com" target="_blank">here</a>.</div>       
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));
Alex Jolig
fonte
2

você pode adicionar classe de alternância ou estado de alternância ao clicar

class Test extends Component(){
  state={
  active:false, 
 }
  toggleClass() {
    console.log(this.state.active)
    this.setState=({
     active:true,
   })
  }

    render() {
    <div>
      <div onClick={this.toggleClass.bind(this)}>
        <p>1</p>
      </div>
    </div>
  }

}
Rizo
fonte
2

Obrigado a @cssko por fornecer a resposta correta, mas se você mesmo tentou, perceberá que não funciona. Uma sugestão foi feita por @Matei Radu, mas foi rejeitada por @cssko, então o código permanece impossível de executar (ele gerará o erro 'Não é possível ler vínculo de propriedade de indefinido'). Abaixo está a resposta correta de trabalho:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.addActiveClass = this.addActiveClass.bind(this);
    this.state = {
      active: false,
    };
  }
  addActiveClass() {
    const currentState = this.state.active;
    this.setState({
      active: !currentState
    });
  };

  render() {
    return ( <
      div className = {
        this.state.active ? 'your_className' : null
      }
      onClick = {
        this.addActiveClass
      } >
      <
      p > {
        this.props.text
      } < /p> < /
      div >
    )
  }
}

class Test extends React.Component {
  render() {
    return ( <
      div >
      <
      MyComponent text = {
        'Clicking this will toggle the opacity through css class'
      }
      /> < /
      div >
    );
  }
}

ReactDOM.render( <
  Test / > ,
  document.body
);
.your_className {
  opacity: 0.3
}
<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.12.0/umd/react-dom.production.min.js"></script>

Sansão
fonte
1

Comecei a aprender React recentemente e queria criar uma guia apenas para ver o quão longe meu conhecimento foi. Me deparei com isso e decidi implementar algo sem redux. Eu meio que sinto que as respostas não refletem o que op quer alcançar. Ele deseja apenas um componente ativo, mas as respostas aqui definirão todos os componentes ativos. Eu dei uma chance.

Abaixo está um arquivo de guia

import React, { Component } from 'react';


class Tab extends Component {

    render(){
        const tabClassName = "col-xs-3 tab-bar";
        const activeTab = this.props.activeKey === this.props.keyNumber ? "active-tab" : null;
        return (
                <div 
                    className = {`${tabClassName} ${activeTab}`}
                    onClick={()=>this.props.onClick(this.props.keyNumber)}
                >
                    I am here
                </div>
        );
    }
}


export default Tab;

O arquivo de guias ...

import React, { Component } from 'react';
import Tab from './tab';

class Tabs extends Component {

    constructor(props){
        super(props);

        this.state = {
            currentActiveKey: 0,
            tabNumber: 2
        };

        this.setActive = this.setActive.bind(this);
        this.setTabNumber = this.setTabNumber.bind(this);
    }

    setTabNumber(number){
        this.setState({
            tabNumber: number
        });
    }

    setActive (key){
        this.setState({
            currentActiveKey: key 
        });
    }

    render(){
        let tabs = [];
        for(let i = 0; i <= this.state.tabNumber; i++){
            let tab = <Tab key={i} keyNumber={i} onClick={this.setActive} activeKey={this.state.currentActiveKey}/>;
            tabs.push(tab);
        }
        return (
            <div className="row">
                {tabs}
            </div>
        );
    }
}


export default Tabs;

seu arquivo de índice ...

import React from 'react';
import ReactDOM from 'react-dom';
import Tabs from './components/tabs';


ReactDOM.render(
    <Tabs />
  , document.querySelector('.container'));

e o css

.tab-bar {
    margin: 10px 10px;
    border: 1px solid grey;
}

.active-tab {
    border-top: 1px solid red;
}

Este é o esqueleto de algo que desejo melhorar, então aumentar o tabNumber além de 4 quebrará o css.

J.Ewa
fonte
1

Aqui está um código que criei:

import React, {Component} from "react";
import './header.css'

export default class Header extends Component{
    state = {
        active : false
    };


    toggleMenuSwitch = () => {
        this.setState((state)=>{
            return{
                active: !state.active
            }
        })
    };
    render() {
        //destructuring
        const {active} = this.state;

        let className = 'toggle__sidebar';

        if(active){
            className += ' active';
        }

        return(
            <header className="header">
                <div className="header__wrapper">
                    <div className="header__cell header__cell--logo opened">
                        <a href="#" className="logo">
                            <img src="https://www.nrgcrm.olezzek.id.lv/images/logo.svg" alt=""/>
                        </a>
                        <a href="#" className={className}
                           onClick={ this.toggleMenuSwitch }
                           data-toggle="sidebar">
                            <i></i>
                        </a>
                    </div>
                    <div className="header__cell">

                    </div>
                </div>
            </header>
        );
    };
};
Vadim Plisko
fonte
1

O React tem um conceito de estado dos componentes, então se você quiser Alternar, use setState:

  1. App.js
import React from 'react';

import TestState from './components/TestState';

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <h1>React State Example</h1>
        <TestState/>
      </div>
    );
  }
}

export default App;
  1. componentes / TestState.js
import React from 'react';

class TestState extends React.Component
{

    constructor()
    {
        super();
        this.state = {
            message: 'Please subscribe',
            status: "Subscribe"
        }
    }

    changeMessage()
    {
        if (this.state.status === 'Subscribe')
        {
            this.setState({message : 'Thank You For Scubscribing.', status: 'Unsubscribe'})
        }
        else
        {
            this.setState({ message: 'Please subscribe', status: 'Subscribe' })
        }
    }
    
    render()
    {
        return (
            <div>
                <h1>{this.state.message}</h1>
        <button onClick={()=> this.changeMessage() } >{this.state.status}</button>
            </div>
        )
    }
}

export default TestState;
  1. Resultado

insira a descrição da imagem aqui

Rahul Shukla
fonte
0

Só queria adicionar minha abordagem. Usando ganchos e provedor de contexto.

Nav.js

function NavBar() {
  const filterDispatch = useDispatchFilter()
  const {filter} = useStateFilter()
  const activeRef = useRef(null)
  const completeRef = useRef(null)
  const cancelRef = useRef(null)

  useEffect(() => {
    let activeClass = '';
    let completeClass = '';
    let cancelClass = '';
    if(filter === ACTIVE_ORDERS){
      activeClass='is-active'
    }else if ( filter === COMPLETE_ORDERS ){
      completeClass='is-active'
    }else if(filter === CANCEL_ORDERS ) {
      cancelClass='is-active'
    }
    activeRef.current.className = activeClass
    completeRef.current.className = completeClass
    cancelRef.current.className = cancelClass
  }, [filter])

  return (
    <div className="tabs is-centered">
      <ul>
        <li ref={activeRef}>
          <button
            className="button-base"
            onClick={() => filterDispatch({type: 'FILTER_ACTIVE'})}
          >
            Active
          </button>
        </li>

        <li ref={completeRef}>
          <button
            className="button-base"
            onClick={() => filterDispatch({type: 'FILTER_COMPLETE'})}
          >
            Complete
          </button>
        </li>
        <li ref={cancelRef}>
          <button
            className={'button-base'}
            onClick={() => filterDispatch({type: 'FILTER_CANCEL'})}
          >
            Cancel
          </button>
        </li>
      </ul>
    </div>
  )
}

export default NavBar

filterContext.js

export const ACTIVE_ORDERS = [
  "pending",
  "assigned",
  "pickup",
  "warning",
  "arrived",
]
export const COMPLETE_ORDERS = ["complete"]
export const CANCEL_ORDERS = ["cancel"]

const FilterStateContext = createContext()
const FilterDispatchContext = createContext()

export const FilterProvider = ({ children }) => {
  const [state, dispatch] = useReducer(FilterReducer, { filter: ACTIVE_ORDERS })
  return (
    <FilterStateContext.Provider value={state}>
      <FilterDispatchContext.Provider value={dispatch}>
        {children}
      </FilterDispatchContext.Provider>
    </FilterStateContext.Provider>
  )
}
export const useStateFilter = () => {
  const context = useContext(FilterStateContext)
  if (context === undefined) {
    throw new Error("place useStateMap within FilterProvider")
  }
  return context
}
export const useDispatchFilter = () => {
  const context = useContext(FilterDispatchContext)
  if (context === undefined) {
    throw new Error("place useDispatchMap within FilterProvider")
  }
  return context
}


export const FilterReducer = (state, action) => {
  switch (action.type) {
    case "FILTER_ACTIVE":
      return {
        ...state,
        filter: ACTIVE_ORDERS,
      }
    case "FILTER_COMPLETE":
      return {
        ...state,
        filter: COMPLETE_ORDERS,
      }
    case "FILTER_CANCEL":
      return {
        ...state,
        filter: CANCEL_ORDERS,
      }
  }
  return state
}

Funciona rápido e substitui redux.

Aaron Tomlinson
fonte