DaneTheory
DaneTheory

Reputation: 292

Simple state update event with Redux using ReactJS?

I've gone through many of the Redux and ReactJS tuts. I understand setting actions => action creators => dispatch => store => render view (uni-directional flow) with more data substantial events. My problem is dealing with very simple events that change state. I know not all state always needs to be handled in Redux, and that local state events (set on React components) is an acceptable practice. However, technically Redux can handle all state events and this is what I am trying to do.

Here is the issue. I have a React component that renders a Button. This Button has an onClick event that fires a handleClick function. I set the state of the Button via the constructor method to isActive: false. When handleClick fires, setState sets isActive: true. The handleClick method also runs two if statements that, when either evaluate to true, run a function that either changes the background color of the body or the color of paragraph text. Clicking the same button again sets state back to false and will change back the body color or text color to the original value. This Button component is created twice within a separate component, Header. So long story short, I've got two buttons. One changes body color, the other changes p tag color after a click event.

Here's the code for the Button component:

import React, {Component} from 'react';
import {dimLights, invertColor} from '../../../actions/headerButtons';
import { connect } from 'react-redux';
import { Actions } from '../../../reducers/reducer';

const headerButtonWrapper = 'headerButton';
const headerButtonContext = 'hb--ctrls ';
const dimmedLight = '#333333';
const invertedTextColor = '#FFFFFF';

export default class Button extends Component {
  constructor (props) {
    super(props)
    this.state = {
      isActive: false
    };
  }

  handleClick (e) {
    e.preventDefault();
    let active = !this.state.isActive;
    this.setState({ isActive: active });

      if(this.props.label === "Dim The Lights"){
        dimLights('body', dimmedLight);
      }
      if(this.props.label === "Invert Text Color"){
        invertColor('p', invertedTextColor)
      }
  }

  render() {
    let hbClasses = headerButtonContext + this.state.isActive;
    return (
      <div className={headerButtonWrapper}>
        <button className={hbClasses} onClick={this.handleClick.bind(this)}>{this.props.label}</button>
      </div>
    );
  }
}

Here's the code for the imported functions that handle changing the colors:

export function dimLights(elem, color) {
  let property = document.querySelector(elem);
    if (property.className !== 'lightsOn') {
        property.style.backgroundColor = color;
        property.className = 'lightsOn'
    }
    else {
        property.style.backgroundColor = '#FFFFFF';
        property.className = 'lightsOff';
    }
}

export function invertColor(elem, textColor) {
  let property = document.querySelectorAll(elem), i;
    for (i = 0; i < property.length; ++i) {
      if (property[i].className !== 'inverted') {
          property[i].style.color = textColor;
          property[i].className = 'inverted'
        } else {
          property[i].style.color = '#3B3B3B';
          property[i].className = 'notInverted';
      }
    }
}

Here's the code for the reducers:

import * as types from '../constants/ActionTypes';

const initialState = {
  isActive: false
};

export default function Actions(state = initialState, action) {
  switch (action.type) {

      case types.TOGGLE_LIGHTS:
      return [
        ...state,
        {
          isActive: true
        }
      ]

      default:
        return state
  }
}

Here's the code for the actions:

import EasyActions from 'redux-easy-actions';

export default EasyActions({
   TOGGLE_LIGHTS(type, isActive){
       return {type, isActive}
   }
})

If it helps, here's the Header component that renders two Button components:

import React, {Component} from 'react';
import Button from './components/Button';

const dimmer = 'titleBar--button__dimmer';
const invert = 'titleBar--button__invert';

export default class Header extends Component {
  render() {
    return (
      <div id="titleBar">
        <div className="titleBar--contents">
          <div className="titleBar--title">Organizer</div>
            <Button className={dimmer} label="Dim The Lights" />
            <Button className={invert} label="Invert Text Color" />
        </div>
      </div>
    );
  }
}

Finally, here's the code containing the store and connection to Redux (NOTE: Layout contains three main components Header, Hero, and Info. The Buttons are created only within the Header component)

import React, { Component } from 'react';
import { combineReducers } from 'redux';
import { createStore } from 'redux'
import { Provider } from 'react-redux';

import Layout from '../components/Layout';
import * as reducers from '../reducers/reducer';

const reducer = combineReducers(reducers);
const store = createStore(reducer);

// This is dispatch was just a test to try and figure this problem out
store.dispatch({
  type: 'TOGGLE_LIGHTS',
  isActive: true
})
console.log(store.getState())

export default class Organizer extends Component {
  render() {
    return (
        <Provider store={store}>
          <div>
            <Layout />
          </div>
        </Provider>
    );
  }
}

What I am looking to do is remove the state logic from the local React component and into Redux. I feel like the functions I have imported need to act as dispatchers. I also feel like I am setting up my initial actions incorrectly. This is such an incredibly simple event that finding an answer anywhere online is difficult. Anyone have any thoughts on what I can do to fix this?

Upvotes: 2

Views: 4698

Answers (1)

bosgood
bosgood

Reputation: 2042

You're almost there. It looks like you've left out the code for Layout component, which I assume is the component that's rendering your Button. The critical piece here is going to be your container, which is the component that's wrapped with Redux's connect to link it to the store. Docs for this. More details here.

What you did:

// components/Button.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';

handleClick() {
  dimLights();
}

What Redux wants you to do instead:

// containers/App.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';

class App extends Component {
  render() {
    // Pass in your button state from the store, as well as 
    // your connected/dispatch-ified actions.
    return (
      <Button
        state={this.props.buttonState}
        onClick={this.props.buttonState ? dimLights : invertColor}
      />
    );
  }
}

function mapStateToProps(state, ownProps) {
  return {
    buttonState: state.buttonState
  };
}
export default connect(mapStateToProps, {
  // Your action functions passed in here get "dispatch-ified"
  // and will dispatch Redux actions instead of returning 
  // { type, payload }-style objects.
  dimLights, invertColor
})(App);

Hope that helps! Redux has a lot of boilerplate for simple stuff like this, however, because most of the pieces can be expressed as pure functions, you gain a lot in unit testing flexibility, and get to use the devtools debugger.

Upvotes: 3

Related Questions