Updating state in redux. Unbound function? - reactjs

I have an application that filters some test on a button click that populates and triggers a dropdown menu. My issue that I'm having is, I want to select the items within the dropdown menu via onclick() that updates the state of the selected test. It's unusual it does not work because when I console.log() the onUpdateSelectedTest I can see that state is being changed and updated but selectedTest remains undefined. Any examples, resources is greatly appreciated.
All files are just snippets.
This is my TestActions.ts
export function UpdateSelectedTest(test: ITest): ToggleTestActionTypes {
return {
type: SELECT_TEST,
payload: test
};
}
This is my Dropdown.tsx
import * as React from "react";
import {
UpdateSelectedTestType,
UpdateSelectedTest
} from "../actions/TestActions";
import { ITestState } from "../models/ITestState";
interface IProps {
onUpdatetoggleTestState: typeof UpdateSelectedTestType;
onUpdateSelectedTest: typeof UpdateSelectedTest;
toggleTestState: ITestState;
}
export class Dropdown extends React.Component<IProps> {
public render() {
let tests = null;
tests = this.props.toggleTestState.tests.filter(
test => test.testType === this.props.toggleTestState.testType
);
return (
<div>
<ul className="nav nav-pills">
<a
className="nav-link dropdown-toggle"
data-toggle="dropdown"
href="#"
role="button"
aria-haspopup="true"
aria-expanded="true"
></a>
<div
className="dropdown-menu show"
x-placement="bottom-start"
style={{
position: "relative",
willChange: "transform",
top: "0px",
left: "0px",
transform: "translate(0px, 40px, 0px"
}}
>
{tests.map(test => (
<a
onClick={() => this.props.onUpdateSelectedTest(test)}
className="dropdown-item"
href="#"
>
<div className="dropdown-divider"></div>
{test.name}: {test.description}
</a>
))}
</div>
</ul>
</div>
);
}
}
The problem occurs here in the Dropdown.tsx
{tests.map(test => (
<a
onClick={() => this.props.onUpdateSelectedTest(test)}
className="dropdown-item"
href="#"
>
<div className="dropdown-divider"></div>
{test.name}: {test.description}
</a>
))}
This is my Apps.tsx
I will past the state of the onUpdateSelectedTest to the ListGroup to be displayed.
import * as React from "react";
import { ToggleButtonGroup } from "./components/ToggleButtonGroup";
import { Dropdown } from "./components/Dropdown";
import { ITestState } from "./models/ITestState";
import { connect } from "react-redux";
import { AppState } from "./store";
import { bindActionCreators } from "redux";
import {
UpdateSelectedTestType,
UpdateSelectedTest
} from "./actions/TestActions";
import { ListGroup } from "./components/ListGroup";
interface IProps {
onUpdatetoggleTestState: typeof UpdateSelectedTestType;
onUpdateSelectedTest: typeof UpdateSelectedTest;
toggleTestState: ITestState;
}
class App extends React.Component<IProps> {
render() {
return (
<div>
<ToggleButtonGroup
toggleTestState={this.props.toggleTestState}
onUpdatetoggleTestState={this.props.onUpdatetoggleTestState}
/>
<Dropdown
toggleTestState={this.props.toggleTestState}
onUpdatetoggleTestState={this.props.onUpdatetoggleTestState}
onUpdateSelectedTest={UpdateSelectedTest}
/>
<ListGroup
toggleTestState={this.props.toggleTestState}
onUpdatetoggleTestState={this.props.onUpdatetoggleTestState}
onUpdateSelectedTest={UpdateSelectedTest}
/>
</div>
);
}
}
const mapStateToProps = (state: AppState) => ({
toggleTestState: state.toggle
});
const mapDispatchToProps = (dispatch: any) => {
return {
onUpdatetoggleTestState: bindActionCreators(
UpdateSelectedTestType,
dispatch
),
onUpdateSelectedTest: bindActionCreators(UpdateSelectedTest, dispatch)
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
This is my toggleButtonReducers.ts
const initialState: ITetsState = {
testType: TestType.test1,
test: dbTests,
question: dbQuestion,
selectedTest: undefined
//selectedQuestion: undefined
};
export function toggleButtonReducer(
state = initialState,
action: ToggleTestActionTypes
) {
switch (action.type) {
case TOGGLE_TEST_TYPE:
return {
...state,
testType: action.payload
};
case SELECT_TEST:
return {
...state,
selectedTest: action.payload
};
default:
return state;
}
}

You likely need to use the dispatch bound action creators. Currently you're calling them but they're not being dispatched.
For example, this line:
onUpdateSelectedTest={UpdateSelectedTest}
Should be:
onUpdateSelectedTest={this.props.onUpdateSelectedTest}

In the below code, from where probes value is coming? I don't see any probes variable in the state or from props.
{probes.map(test => (
<a
onClick={() => this.props.onUpdateSelectedTest(test)}
className="dropdown-item"
href="#"
>
<div className="dropdown-divider"></div>
{test.name}: {test.description}
</a>
))}
i think, instead of probes it should be tests.

Related

How manage global state using context API in React js

I am having issues managing the state of my navbar using useContext. Atm my app renders the menu items as soon as the menu toggle. I want this event to happen only onClick, also the button does not log the console.log message, it works only when I click directly on the link item ex:home.
So I have 2 questions.
How do I manage my navbar state to show how to hide the menu items without having to create a new component for it?
How do I fix my click event for it be triggered on either the menu button itself or/and menu items?
Below you will code snippets for App.js, Layout.js, ThemeContext.js, useTheme.js, useToggle.js, ToggleContext.js and the Navbar where the toggle context is used.
I would really appreciate some help here guys, I am junior and really kind of stuck here.
Thanks in advance to you all.
App.js
//import { data } from '../../SkillData';
import Header from './Header';
import Navbar from './Navbar';
import Skills from './Skills';
import Layout from './Layout';
function App () {
return (
<Layout startingTheme="light" startingToggle={"show"}>
<div>
<Navbar />
<Header />
<Skills />
</div>
</Layout>
);
}
export default App;
Layout.js
import React, { useContext } from "react";
import { ThemeContext, ThemeProvider } from "../contexts/ThemeContext";
import { ToggleContext, ToggleProvider } from "../contexts/ToggleContext";
function Layout ({startingTheme, startingToggle, children}) {
return (
<>
<ThemeProvider startingTheme={startingTheme} >
<ToggleProvider startingToggle={startingToggle}>
<LayoutNoToggleProvider>
</LayoutNoToggleProvider>
</ToggleProvider>
<LayoutNoThemeProvider >{children}</LayoutNoThemeProvider>
</ThemeProvider>
</>
);
}
function LayoutNoToggleProvider ({children}) {
const toggle = useContext(ToggleContext);
return (
<div className={
toggle === false ? "navbar navbar-collapsed" : "navbar navbar-collapse show"
}>
{children}
</div>
)
}
function LayoutNoThemeProvider ({ children }) {
const {theme} = useContext(ThemeContext);
return (
<div className={
theme === "light" ?
"container-fluid bg-white" :
"container-fluid bg-dark"
}>
{children}
</div>
);
}
export default Layout;
ThemeContext
import React, { createContext} from "react";
import useTheme from "../hooks/useTheme";
export const ThemeContext = createContext();
function ThemeProvider ({children, startingTheme}) {
const { theme, setTheme } = useTheme(startingTheme);
return (
<ThemeContext.Provider value={
{theme, setTheme}
}>
{children}
</ThemeContext.Provider>
);
}
export { ThemeProvider };
useTheme.js
import { useState } from "react";
function useTheme (startingTheme ="light") {
const [theme, setTheme] = useState(startingTheme);
function validateTheme (themeValue) {
if (themeValue === "dark") {
setTheme("dark");
} else {
setTheme("light");
}
}
return {
theme,
setTheme: validateTheme,
}
}
export default useTheme;
ToggleContext.js
import React, { createContext } from "react";
import useToggle from "../hooks/useToggle";
export const ToggleContext = createContext();
function ToggleProvider({ children, startingToggle }) {
const { toggle, setToggle } = useToggle(startingToggle);
return (
<ToggleContext.Provider value={{ toggle, setToggle }}>
{children}
</ToggleContext.Provider>
);
}
export { ToggleProvider };
useToggle.js
import { useState } from "react";
function useToggle (startingToggle = false) {
const [toggle, setToggle] = useState(startingToggle);
function validateShowSidebar (showSidebarValue) {
if (showSidebarValue === "show") {
setToggle("show");
} else {
setToggle("");
}
}
return {
toggle,
setToggle: validateShowSidebar,
}
}
export default useToggle;
Navbar.js
import Image from "next/image";
import styles from "../../styles/Home.module.scss"
import Logo from "../../public/Knowledge Memo.svg"
import { useContext } from "react";
import { ThemeContext } from "../contexts/ThemeContext";
import { ToggleContext } from "../contexts/ToggleContext";
import Link from 'next/link';
import { useState } from "react";
const navbarData = [
{ id: "1",
title: "home",
ref: "#home"
},
{ id:"2",
title: "Skills",
ref: "#skills"
},
{ id:"3",
title: "The List",
ref: "#theList"
},
{ id: "4",
title: "Team",
ref: "#team"
},
{ id: "5",
title: "Contact",
ref: "#contact"
},
];
function Navbar() {
const theme = useContext(ThemeContext);
const toggle = useContext(ToggleContext);
return (
<>
<nav className={
theme === "light" ?
"navbar navbar-expand-lg navbar-dark fixed-top":
"navbar navbar-expand-lg navbar-dark bg-dark fixed-top id= mainNav"}>
<div className="container d-flex flex justify-content-between">
<a className="navbar-brand h-50" href="#page-top">
<div className="navbar-brand">
<Image
src={Logo}
alt="..."
fill="#fff"
objectFit="contain"
className="h-50"
/>
</div>
</a>
<button
onClick={ () => toggle === !toggle, console.log("clicked")}
className="navbar-toggler collapsed"
type="button"
data-bs-toggle="collapsed"
data-bs-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false"
aria-label="Toggle navigation"
>
Menu
<i className="fa fa-bars ms-1 navbar-toggler" aria-hidden="true"></i>
</button>
{toggle ?
<div className="collapsed navbar-collapse mt-2 id=navbarResponsive">
<ul className="navbar-nav text-uppercase ms-auto py-4 py-lg-0">
{navbarData.map((link,idx) => {
return (
<li key={link.id}>
<Link href={`/${link.ref}`} className="nav-item" data-index={idx} passHref>
<a className="nav-link">
{link.title}
</a>
</Link>
</li>
);
})}
</ul>
</div>
: <div className="collapse navbar-collapse show mt-2 id=navbarResponsive">
<ul className="navbar-nav show text-uppercase ms-auto py-4 py-lg-0">
{navbarData.map((link,idx) => {
return (
<li key={link.id}>
<Link href={`/${link.ref}`} className="nav-item" data-index={idx} passHref>
<a className="nav-link">
{link.title}
</a>
</Link>
</li>
);
})}
</ul>
</div>}
</div>
</nav>
</>
);
}
export default Navbar;
You can try out this implemetation with reducers to handle for you the state change with localstorage. It is not an exact implemetation of your's but you can see the flow
In the AppContext.jsx
The AppContext holds the global state of the application so that it's easier working with a single context provider and dispatching actons to specific reducers to handle state change without providing many providers.
The combinedReducers handle reducer methods to a given state component
import { useReducer, createContext, useEffect } from "react";
import userReducer from "./reducers/userReducer";
import themeReducer from "./reducers/themeReducer";
export const APP_NAME = "test_app";
//Check the localstorage or set a default state
const initialState = JSON.parse(localStorage.getItem(APP_NAME))
? JSON.parse(localStorage.getItem(APP_NAME))
: {
user: {
username: "",
email: "",
isAdmin: false,
},
theme: { dark: false },
};
//Create your global context
const AppContext = createContext(initialState);
//Create combined reducers
const combinedReducers = ({ user, theme }, action) => ({
user: userReducer(user, action),
theme: themeReducer(theme, action),
});
const AppState = ({ children }) => {
//Making it to provider state
const [state, dispatch] = useReducer(combinedReducers, initialState);
useEffect(() => {
localStorage.setItem(APP_NAME, JSON.stringify(state));
}, [state]);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
export default AppState;
export { AppContext, AppState };
The above implementation works like redux but you destructure the given state to a specific reducer to handle the state change
In this I have used localstorage to keep a persistent state because with context API on page reload the state goes. Use the useEffect hook from react and add the state in the dependency array to ensure your state is in sync
In the UserReducer.jsx
const userReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "LOGIN":
return { ...state, ...payload };
case "LOGOUT":
return {};
default:
return state;
}
};
export default userReducer;
In the ThemeReducer.jsx
const themeReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "DARK":
return { ...payload };
default:
return state;
}
};
export default themeReducer;
Wrapping the whole app with a single provider in the index.jsx
import reactDom from "react-dom"
import React from "react"
import App from "./App"
import "./index.css"
import AppState from "./state/AppState"
reactDom.render(
<React.StrictMode>
<AppState >
<App />
</AppState>
</React.StrictMode>,
document.getElementById("root")
)
Accessing the context from App.jsx
import { useContext } from "react";
import { AppContext } from "./state/AppState";
const App = () => {
const { state, dispatch } = useContext(AppContext);
const handleLogin = () => {
dispatch({
type: "LOGIN",
payload: {
username: "Mike",
email: "mike#gmail.com",
isAdmin: false,
},
});
};
const handleLogout = () => {
dispatch({
type: "LOGOUT",
payload: {},
});
};
return (
<div className="main-container">
<div className="container">
<p>Username: {state.user.username ? state.user.username : "Unknown"}</p>
<p>Email: {state.user.email ? state.user.email : "Unknown"}</p>
</div>
<button onClick={handleLogin}>Login</button>
<button onClick={handleLogout} style={{ background: "red" }}>
Login
</button>
</div>
);
};
export default App;
Here is my code LINK if you want to see the structure Github

React - Redux dispatcher not mapping

I have a several react components in react-redux application. Most of them are working find, but the below "TagItemWidget" does not appear to bind the state or dispatchers to props. I have confirmed that the dispatch function works and fires the reducer. The same function and state can be bound on other components. I have done a trace and observed that the bind function is firing. However, in both "console.log" outputs, props is empty. componentDidMount and render appear to be called only once, when the page loads - never again. What gives?
UPDATE: If I move my <TagItemWidget /> into the spot where <TagItemButton /> is, it populates the dispatchers. Is there a problem with my TagItemButton?
TagItemWidget:
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { tags_list } from "../../actions/tags";
export class TagItemWidget extends React.Component {
static propTypes = {
cases: PropTypes.array.isRequired,
activeCase: PropTypes.string
};
constructor(props) {
super(props);
}
componentDidMount() {
console.log(this)
}
render() {
console.log(this)
return (
<Fragment>
<div key={Math.random} >
{this.props.activeCase}
</div>
</Fragment>
)
}
}
const mapStateToProps = (state) => ({
cases: state.tags.tags,
activeCase: state.cases.activeCase
});
export default connect(mapStateToProps, { tags_list })(TagItemWidget);
The including component, TagItemButton:
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { TagItemWidget } from './TagItemWidget';
export class TagItemButton extends Component {
render() {
return (
<Fragment>
<a href="#" className="list-group-item list-group-item-action" id="controls_tagitem"
data-toggle="modal" data-target="#tagItemModal">
Tag Item
</a>
<div className="modal fade" id="tagItemModal" tabIndex="-1"
role="dialog" aria-labelledby="tagItemModalLabel"
aria-hidden="true">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title"
id="tagItemModalLabel">
Tag Item
</h5>
<button type="button" className="close"
data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<TagItemWidget />
</div>
</div>
</div>
</div>
</Fragment>
)
}
}
export default TagItemButton;
actions/tags.js
import { TAGS_LIST } from "./types";
import { createMessage, returnErrors } from "./messages";
export const tags_list = ( case_id ) => dispatch => {
if ( case_id != null ) {
console.log("dispatging TAGS_LIST")
axios
.get("/OMNI_api/api/tag/listbycase/?case_id="+case_id)
.then(response => {
dispatch
({
type: TAGS_LIST,
payload: response.data
})
})
}
}
If you are using redux-thunk. Which I think you are.
dispatch needs to be spread from the thunk like so
export const tags_list = ( case_id ) => ({ dispatch }) => {
You need to bindActionCreators
either before passing them to the connect function
const mapDispatch = (dispatch) => bindActionCreators({ tag_list }, dispatch);
export default connect(mapStateToProps, mapDispatch)(TagItemWidget);
inside the constructor
https://redux.js.org/api/bindactioncreators

React / Redux: Actions automatically get called whenever page change

I'm making a simple shopping cart app using Redux. Right now every time I change page, actions are automatically get called for three times which is equal to the number of items. If I go to Cart page, removeItems action gets called three times so there's no way I can add items to cart so far. It might be a problem about router, but I can't spot the problem. Could anyone explain me what is the problem?
Home.js
import React from 'react';
import { connect } from 'react-redux';
import { addToCart } from '../actions';
class Home extends React.Component {
handleClick = id => {
this.props.addToCart(id)
}
renderList = () => {
return this.props.cart.slice(0, 3).map(item => {
return (
<div className="card" key={item.id} style={{width: "200px", float: "left", marginRight: "20px"}}>
<div className="card-image">
<img src={item.imageUrl} alt={item.name} />
<span className="card-title">{item.name}</span>
<span to="/"
className="btn-floating halfway-fab waves-effect waves-light red"
onClick={this.handleClick(item.id)}
>
<i className="material-icons">add</i>
</span>
</div>
<div className="card-content">
<p>{item.desc}</p>
<p><b>${item.price}</b></p>
</div>
</div>
)
})
}
render() {
console.log(this.props.cart)
return (
<div className="container">
<h3>Home</h3>
<div className="box">
{this.renderList()}
</div>
</div>
)
}
}
const mapStateToProps = state => {
return { cart: state.cart.items }
}
const mapStateToDispatch = dispatch => {
return {
addToCart: (id) => { dispatch(addToCart(id)) }
}
}
export default connect(mapStateToProps, mapStateToDispatch)(Home);
Cart.js
import React from 'react';
import { connect } from 'react-redux';
import { removeItem } from '../actions';
class Cart extends React.Component {
handleClick = (id) => {
this.props.removeItem(id);
}
renderList = () => {
if (this.props.addedItems.length !== 0) {
return this.props.addedItems.map(item => {
return (
<li className="collection-item avatar" key={item.id}>
<div className="item-img">
<img src={item.imageUrl} alt={item.name} style={{width: "120px"}} />
</div>
<div className="item-desc">
<span className="title">{item.name}</span>
<p>{item.content}</p>
<p><b>${item.price}</b></p>
</div>
<button
className="waves-effect waves-light btn pink remove"
onClick={this.handleClick(item.id)}
>Remove</button>
</li>
)
})
}
else {
return <p>Nothing is in cart.</p>
}
}
render() {
return (
<div className="container">
<div className="cart">
<ul className="collection">
{this.renderList()}
</ul>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return { addedItems: state.cart.addedItems }
}
const mapDispatchToProps = dispatch => {
return {
removeItem: (id) => {dispatch(removeItem(id))}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart);
Header.js
import React from 'react';
import { Link } from 'react-router-dom';
const Header = () => {
return (
<nav className="nav-wrapper">
<div className="container">
<Link to="/" className="brand-logo">Shopping</Link>
<ul className="right">
<li><Link to="/">Shop</Link></li>
<li><Link to="/cart">Cart</Link></li>
<li><Link to="/cart"><i className="material-icons">shopping_cart</i></Link></li>
</ul>
</div>
</nav>
)
}
export default Header;
Reducers
import data from '../data.json';
import { ADD_TO_CART, REMOVE_FROM_CART } from "../actions/types";
const INITIAL_DATA = {
items: data,
addedItems: [],
total: 0
}
const cartReducer = (state = INITIAL_DATA, action) => {
switch(action.type) {
case ADD_TO_CART:
let addedItem = state.items.find(item => item.id === action.id);
let existedItem = state.addedItems.find(item => action.id ===item.id);
if (existedItem) {
addedItem.quantity += 1;
return {
...state,
total: state.total + addedItem.price
}
}
else {
addedItem.quantity = 1;
let newTotal = state.total + addedItem.price;
return {
...state,
addedItems: [...state.addedItems, addedItem],
total: newTotal
}
}
case REMOVE_FROM_CART:
let itemToRemove = state.addedItems.find(item => action.id === item.id);
let newItems = state.addedItems.filter(item => action.id !== item.id);
let newTotal = state.total - itemToRemove.price;
return {
...state,
addedItems: newItems,
total: newTotal
}
default:
return state;
}
}
export default cartReducer;
Actions
import { ADD_TO_CART, REMOVE_FROM_CART } from "./types";
export const addToCart = (id) => {
return {
type: ADD_TO_CART,
id
}
}
export const removeItem = (id) => {
return {
type: REMOVE_FROM_CART,
id
}
}
App.js
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Header from './Header';
import Home from './Home';
import Cart from './Cart';
const App = () => {
return (
<BrowserRouter>
<div className="app">
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/cart" component={Cart} />
</Switch>
</div>
</BrowserRouter>
)
}
export default App;
Seems like you're actually calling your click handlers whenever you render your components, instead of just passing the handler function, so that's why actions are being triggered multiple times.
For instance, in your Home.js component change the code below from:
<span to="/" className="btn-floating halfway-fab waves-effect waves-light red" onClick={this.handleClick(item.id)} >
to:
<span to="/" className="btn-floating halfway-fab waves-effect waves-light red" onClick={() => { this.handleClick(item.id); }} >
And the same thing on Cart.js, change from:
<button className="waves-effect waves-light btn pink remove" onClick={this.handleClick(item.id)}>Remove</button>
to:
<button className="waves-effect waves-light btn pink remove" onClick={() => {this.handleClick(item.id); }}>Remove</button>

How to add 'Delete Post' functionality on list items with React & Redux?

I'm currently taking the Modern React with Redux course from Udemy.
On posts_show component I can get the id from react-router's params object from the uri as shown below.
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {fetchPost, deletePost } from "../actions/index";
import { Link } from 'react-router-dom';
class PostsShow extends Component {
componentDidMount() {
// if (!this.props.post) { if we don't have it go and grab that thing}
const { id } = this.props.match.params;
this.props.fetchPost(id);
}
onDeleteClick() {
const { id } = this.props.match.params;
this.props.deletePost(id, () => {
this.props.history.push('/');
});
}
render() {
const { post } = this.props;
if(!post) { // BEFORE THE DATA IS LOADED, WE SHOULD RETURN (RENDER) ANOTHER THING.
return <div>Loading...</div>
}
return (
<div>
<Link to="/" className="btn btn-primary">Back to Index</Link>
<button
className="btn btn-danger pull-xs-right"
onClick={this.onDeleteClick.bind(this)}
>
Delete Post
</button>
<h3>{post.title}</h3>
<h6>Categories: {post.categories}</h6>
<p>{post.content}</p>
</div>
);
}
}
function mapStateToProps({ posts }, ownProps) { // (application state, ownProps)
return { post: posts[ownProps.match.params.id] };
}
export default connect(mapStateToProps, { fetchPost, deletePost })(PostsShow);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Here is the action creator:
export function deletePost(id, callback) {
const request = axios.delete(`${ROOT_URL}/posts/${id}${API_KEY}`)
.then(() => callback());
return {
type: DELETE_POST,
payload: id
}
}
and here is the reducer:
export default function(state = {}, action) {
switch (action.type) {
case DELETE_POST:
return _.omit(state, action.payload);
Now I would like to add the same functionality for the posts_index component.
I'd like to add Delete Post buttons for each individual list item. I assume that it's ok to use the same action_creator and reducer for the task, however I can't reach the id property of the individual list item and pass it to the onDeleteClick function on the posts_index component.
I would appreciate if anyone can help me to resolve this issue.
Thanks in advance.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {fetchPosts, deletePost} from "../actions/index";
import _ from 'lodash';
import { Link } from 'react-router-dom';
class PostsIndex extends React.Component {
componentDidMount() {
this.props.fetchPosts();
}
onDeleteClick() {
console.log(this.props.posts);
alert('clicked');
}
renderPosts() {
return _.map(this.props.posts, post => {
return <li className="list-group-item" key={post.id}>
<Link to={`/posts/${post.id}`}>
{post.title}
</Link>
<button
className="btn btn-danger pull-xs-right"
onClick={this.onDeleteClick.bind(this)}
>Delete {post.id}
</button>
</li>
})
}
render() {
// console.log(this.props.posts);
return (
<div>
<div className="text-xs-right">
<Link className="btn btn-primary" to="/posts/new">
Add a Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
return { posts: state.posts };
}
export default connect(mapStateToProps, { fetchPosts, deletePost })(PostsIndex);
Change the onClick to
onClick={() => this.onDeleteClick(post.id)}
Ok, I got it, thank you for your help!
onDeleteClick(id) {
this.props.deletePost(id, () => {
this.props.history.push('/');
});
}
renderPosts() {
return _.map(this.props.posts, post => {
return <li className="list-group-item" key={post.id}>
<Link to={`/posts/${post.id}`}>
{post.title}
</Link>
<button
className="btn btn-danger pull-xs-right"
onClick={() => this.onDeleteClick(post.id)}
>Delete {post.id}
</button>
</li>
})
}

MapStateToProps doesn't call componentWillRecieveProps

I am using react-redux to manage states between 3-4 Components. I have 4 Components which act as Pages, whenever one is selected an action calls the reducer to update the selected Tab on the navbar. Simple enough.
I have done everything correctly, it works fine till the point where I can access the state in the mapsStateToProps const in the NavbarComponent but it doesn't re-render the Component. I have tried calling componentWillRecieveProps lifecycle method, but it isn't being called.
SideNavbarComponent
import React, { Component } from 'react';
import { Nav, NavItem } from 'react-bootstrap';
import { connect } from 'react-redux';
class SideNavComponent extends Component {
// Is not called
componentWillReceiveProps(nextProps) {
console.log(nextProps);
}
render() {
// logs undefined
console.log(this.props.key);
return (
<Nav bsStyle="pills" stacked activeKey={this.props.key}>
<NavItem disabled eventKey={1}>Home</NavItem>
<NavItem disabled eventKey={2}>Device</NavItem>
<NavItem disabled eventKey={3}>Transformations</NavItem>
<NavItem disabled eventKey={4}>Graphs</NavItem>
</Nav>
);
}
}
const mapStateToProps = (state) => {
const { key } = state.sideNav;
// I can access via key here
return { key };
};
export default connect(mapStateToProps)(SideNavComponent);
Reducer
const INITIAL_STATE = {
key: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'changed_link':
return { ...state, key: action.payload };
default:
return state;
}
};
Action
export const linkChange = (key) => {
return {
type: 'changed_link',
payload: key
};
};
Edit
I call the action (linkChange) in each of the 4 Components which act as Pages. One of them is below:
DeviceComponent
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Col, Well } from 'react-bootstrap';
import axios from 'axios';
import _ from 'lodash';
import moment from 'moment';
import { linkChange } from '../../actions';
class DeviceComponent extends Component {
state = {
device: {}
};
componentDidMount() {
axios.get(`http://localhost:3000/api/device/${this.props.match.params.id}`)
.then(res => {
this.setState({ device: res.data });
})
.catch(err => {
console.log(err);
});
this.props.linkChange(2);
}
render() {
const { device } = this.state;
const battery = _.values(device.battery);
const generator = _.values(device.generator);
const Timestamp = moment(device.timestamp).format('dddd, MMMM Do YYYY, h:mm:ss a');
return (
<div>
<Col md={7} sm={12}>
<h3>Device ID: {device.id} ({device.location})</h3>
<hr />
<div style={{ marginTop: 40 }}>
<Link className="btn btn-lg btn-success btn-block" to="/transform">Transformations</Link>
<Link className="btn btn-lg btn-warning btn-block" to="/graph">Graphs</Link>
</div>
<div style={{ marginTop: 70 }}>
<Link className="btn btn-lg btn-primary btn-block" to="/">Home</Link>
</div>
</Col>
<Col md={5} sm={12}>
<Well>
<ul>
<li>ID: {device.id}</li>
<li>Location: {device.location}</li>
<li>Timestamp: {Timestamp}</li>
<li>
Battery:
<ul>
<li>Temperature: {battery[0]}</li>
<li>Current: {battery[1]}</li>
<li>Voltage: {battery[2]}</li>
</ul>
</li>
<li>
Generator:
<ul>
<li>Temperature: {generator[0]}</li>
<li>Current: {generator[1]}</li>
<li>Voltage: {generator[2]}</li>
</ul>
</li>
</ul>
</Well>
</Col>
</div>
);
}
}
export default connect(null, { linkChange })(DeviceComponent);
key is a reserved word in React - renaming it will resolve the issue

Resources