m00saca m00saca - 2 months ago 11
React JSX Question

Redux dispatcher not working

Good Evening and get ready for a lot of code.

I am learning how to use Redux with React in an effort to share state across components and I've been trying to make a navigation bar that changes colors based on which div you click. At a high level I want Redux to allow me to do the following...

<Navigation>
<NavEndpt /> <-- click an endpoint and you set this.props.active to true
<NavEndpt /> <-- resets the rest and set this.props.active to false
</Navigation>


So basically the components change color when you click them and that is REALLY HARD TO DO in React I have come to find out. I have my dispatcher wired up correctly according to the tutorial I am watching and I'm still having no success changing state. Maybe I don't totally understand Redux but I would sure appreciate some help. I am going to provide snippets in the order best suited for understanding of my issue and maybe you can spot where I've gone wrong.

client.js
that gets attached to index.html nothing special.

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";

import Layout from "./components/Layout"
import store from "./store"

const app = document.getElementById("app");

ReactDOM.render(<Provider store={store}><Layout /></Provider>, app);


store.js
:

import { applyMiddleware, createStore } from "redux";

import reducer from "./reducers/mainReducer"

export default createStore(reducer)


Here is
navReducer.js
that sets the initial state:

export default function reducer(state = {
about: {
id: "about",
title: "Who I am",
active: false
},
projects: {
id: "projects",
title: "What I do",
active: false
}

}, action) {

switch (action.type) {
case "CLICK": {
return {...state, active: true }
break
}
}

return state
}


Here is
navActions.js
that has one action...

export function activeBox(){
return{
type: "CLICK",
}
}


Here are the components that make use of the above snippets. One note, I am not receiving any errors in the console or terminal which makes me believe that my issue is simply a bad path to a file.

Layout.js


import React from "react";

import Nav from "./views/Nav"

import { connect } from "react-redux";

@connect((store) => {
return{

about: store.navControls.about,
projects: store.navControls.projects

};
})
export default class Layout extends React.Component {


render(){


return(
<div id="wrapper">
<Nav about={this.props.about} projects={this.props.projects} dispatch={this.props.dispatch} />
<div id="content"></div>
</div>
)

}
}


Nav.js
(featured in
Layout
)

import React from "react";

import NavEndpt from "./NavEndpt"

const Nav = React.createClass({


render(){

let about = this.props.about;
let projects = this.props.projects;


return(
<div id="navigation">
<NavEndpt data={about} dispatch={this.props.dispatch} />
<NavEndpt data={projects} dispatch={this.props.dispatch} />
</div>
)
}
})


export default Nav;


and finally
NavEndpt.js


import React from "react";

import { activeBox } from "../../actions/navActions.js"

const NavEndpt = React.createClass({
handleClick(){
let data = this.props.data;
console.log("clicked " + data.active)

this.props.dispatch(activeBox()); // this does nothing, checking out React in the chrome dev tools shows no change to about.active or projects.active
},

styleUpdate(){
let data = this.props.data;

if (data.active === false){
return{
background: "violet"
}
} else if (data.active === true){
return {
background: "yellow"
}
}
},

render(){
let data = this.props.data;
return(

<div className="navButton" onClick={this.handleClick} style={this.styleUpdate()}>
<span>{data.title}</span>
</div>

)
}
})

export default NavEndpt;


Thanks in advance stack overflow community, I look forward to your insights because I've been trying to get this to work all day.

cheers

Answer

this.props.dispatch(activeBox()); dispatches the following action to your redux store:

{
  type: "CLICK",
}

That action does not identify which nav item was clicked, it just says that a click occurred. Similarly, your reducer will go from

{
  about: {
    id: "about",
    title: "Who I am",
    active: false
  },
  projects: {
    id: "projects",
  title: "What I do",
  active: false
  }
}

to

{
  about: {
    id: "about",
    title: "Who I am",
    active: false
  },
  projects: {
    id: "projects",
  title: "What I do",
  active: false
  },
  active: true
}

You're just adding an active flag adjacent to about and projects, not in them. Check out redux's todomvc example to see how to best identify which item got clicked and have your store update appropriately. One solution is

//navActions.js
export function activeBox(nav){
  return{
    type: "CLICK",
    nav: nav
  }
}

// navReducer.js
export default function reducer(state = {
  about: {
    id: "about",
    title: "Who I am",
    active: false
  },
  projects: {
    id: "projects",
    title: "What I do",
    active: false
  }
}, action) {

switch (action.type) {
  case "CLICK": {
    // Copy state over to prevent altering previous state
    let newState = { 
      about: ...state.about,
      projects: ...state.projects
    }
    // Set all nav items to inactive, when you have more navItems you'll want to loop
    newState.about.active = false
    newState.projects.active = false

    // if action.nav is a proper nav item, set it to active
    if (newState[action.nav]) {
      newState[action.nav].active = true
    }
    return newState
    break
  }
}

return state
}