MapStateToProps doesn't call componentWillRecieveProps - reactjs

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

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

How to redirect to another page in react confirmAlert?

I am developing a react shopping cart app. If the user click on 'Clear Cart' button, the entire cart will be cleared. Before that the application should confirm the action and therefore, I used 'React Confirm Alert'. If the user click on 'Yes' button, it will clear the cart and it should navigate to the root page('/'). I searched over the internet and on the StackOverflow and tried the three ways given here. But nothing worked. For both this.props.history.push('/') and this.props.router.push('/') gives me a TypeError saying undefined object and for useHistory() it says violating react hooks principles. Is there anything I can do to make this work? Thanks in advance!
Code :-
import React, { useEffect, useState, Component} from 'react';
import Titles from "../Titles";
import CartColumns from './CartColumns';
import EmptyCart from "./EmptyCart";
import CartList from "./CartList";
import {connect} from "react-redux";
import {Link, Redirect} from "react-router-dom";
import Axios from "axios";
import { confirmAlert } from 'react-confirm-alert'; // Import
import 'react-confirm-alert/src/react-confirm-alert.css' // Import css
const mapStateToProps = ({ session, history}) => ({
session, history
});
class Cart extends Component {
constructor() {
super();
this.removeItem = this.removeItem.bind(this);
}
state = {
Cart: [],
total : 0,
discount: 0
};
componentDidMount() {
if(this.props.session.userId !== null){
this.getItems();
}
}
getItems = () => {
//function to get the items
};
removeItem = (productId) => {
//method to remove an item
};
clearCart = () => {
confirmAlert({
title: 'Clear Cart',
message: 'Are you sure to clear the Cart?',
buttons: [
{
label: 'Yes',
onClick: () => {Axios.get('http://localhost:8000/api/cart/clearCart', {params:{userId: this.props.session.userId}});
} //I want to redirect from here
},
{
label: 'No'
}
]
})
};
checkout= () => {
//Checkout function
};
render() {
let total = this.state.total;
let discount = this.state.discount;
if(this.props.session.username !== null){
return (
<section>
{(this.state.Cart.length > 0) ? (
<React.Fragment>
<Titles name ="Your " title = "Cart">Cart</Titles>
<CartColumns/>
<CartList cart = {this.state.Cart} removeItem={this.removeItem}/>
<div className="container">
<div className="row">
<div className="col-10 mt-2 ml-sm-5 ml-md-auto col-sm-8 text-capitalize text-right">
<h5>
<span className="text-title">Sub Total: </span>
<strong>$ {total}</strong>
</h5>
<h5>
<span className="text-title">Discount: </span>
<strong>$ {discount}</strong>
</h5>
<h5>
<span className="text-title">Total: </span>
<strong>$ {total - discount}</strong>
</h5>
<div className="col-10 mt-2 ml-sm-5 ml-md-auto col-sm-8 text-capitalize text-right">
<button className="btn btn-outline-danger text-uppercase mb-3 px-5" type="button" onClick={this.clearCart}>Clear Cart</button>
<button className="btn btn-outline-info ml-3 text-uppercase mb-3 px-5" type="button" onClick={this.checkout}>Check Out</button>
</div>
</div>
</div>
</div>
</React.Fragment>
) : (
<EmptyCart/>
)
}
</section>
);
} else {
return(
<Redirect to="/login" />
);
}
}
}
export default connect(
mapStateToProps
)(Cart);
I think you have 2 options.
First: Just use window.location method like this
// Simulate a mouse click:
window.location.href = "http://www.example.com";
// Simulate an HTTP redirect:
window.location.replace("http://www.example.com");
Second: Use react state
import React from 'react'
import { Redirect } from 'react-router-dom'
class Cart extends Component {
constructor() {
super();
}
state = {
isRedirect: false
};
const onClick = () =>{
this.setState({isRedirect: true})
}
render() {
let isRedirect = this.state.isRedirect;
if(isRedirect == true){
return <Redirect to='/target' />
}
return (
<div>
...
</div>
)
)
}
window.location.replace("http://localhost:3000/");
This statement will solve your problem. Happy Coding!

Updating state in redux. Unbound function?

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.

Changes are not reflected in UI in react

When a user like a post, then the count is incremented in ui. Bu when a user remove the like then count ui is not changing though it is changed in server.How to solve it?
When a user like a post, then the count is incremented in ui. Bu when a user remove the like then count ui is not changing though it is changed in server.How to solve it?
import React from 'react';
import moment from 'moment';
import CommentForm from './CommentForm';
import CommentList from './CommentList';
import CommentModal from './CommentModal';
import { connect } from 'react-redux';
import { startAddComment, startAddLike, startRemoveLike } from
'../actions/post';
import { Link } from 'react-router-dom';
import UserInfo from './UserInfo';
class PostListItem extends React.Component{
constructor(props){
super(props);
this.state = {
isliked: false,
commentM: undefined,
likes: this.props.likes
}
}
componentDidMount(){
if(this.props.likes.includes(this.props.user.uid)){
this.setState(() => ({isliked:true}));
}
}
onClickedLike = () =>{
if(this.state.isliked === false){
this.props.dispatch(startAddLike(this.props._id));
this.setState(()=>{
console.log(this.props);
return{
isliked:true
}
});
} else{
this.props.dispatch(startRemoveLike(this.props._id));
this.setState(()=>({isliked:false}));
}
}
openModal = () =>{
this.setState({commentM: this.props.comments});
}
closeModal = () =>{
this.setState(({commentM: undefined}));
}
render(){
return(
<div className="post">
<div className="post__header">
<UserInfo user={this.props.author}
time={this.props.createdAt}/>
{
(this.props.user.uid === this.props.author.uid)?
<Link to={`/edit/${this.props._id}`}
className="post__edit">
Edit</Link>:''
}
{/* <p className="post__time">
{moment(this.props.createdAt).fromNow()}</p> */}
</div>
<div className="post__caption">{this.props.caption}</div>
<img src={this.props.content} className="post__content"/>
<div className="post__extra">
<div className="post__lc">
<button className="post__button"
onClick={this.onClickedLike}
>{this.state.isliked? <i className="fas fa-futbol"></i>
: <i className="far fa-futbol"></i>}
</button>
<button className="post__button"
onClick={this.openModal}><i className="far fa-
comment"></i>
</button>
</div>
{this.props.likes.length !== 0 && <p className="post__like">
{this.props.likes.length} {this.props.likes.length === 1? 'like':'likes'}
</p>} // likes count is not changing while removing the like(ui only)
<CommentModal
commentM={this.state.commentM}
closeModal={this.closeModal}/>
<CommentForm onSubmit={(comment) => {
this.props.dispatch(startAddComment(this.props._id,
comment));
}} />
{this.props.comments && <CommentList comments=
{this.props.comments}/>}
</div>
</div>
);
}
};
const mapStateToProps = (state) => {
return{
user: state.auth
}
}
export default connect(mapStateToProps)(PostListItem);

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>
})
}

Resources