i'm new and i'm trying to learn react and redux, I was trying to do this simple user list / todo list, I can add list items but I can't remove them with the remove in the reducer, if i click on the button it deletes all the array and the log of payload returns undefined, can someone help me? thanks all for the help!
STORE
import { combineReducers } from "#reduxjs/toolkit";
import { userState } from "./UsersState";
import { createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
const rootReducer = combineReducers({
users: userState.reducer,
});
export const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
REDUCER
import { createSlice, current } from "#reduxjs/toolkit";
export const userState = createSlice({
name: "users",
initialState: [],
reducers: {
add: (state, action) => [...state, action.payload],
remove: (state, action) =>
state.filter((user, index) => user.id !== action.payload),
clear: (state, action) => [],
log: (state, action) => console.log(state),
},
});
RENDER
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { store } from "./Store";
import { add, remove, clear, userState } from "./UsersState";
export function UsersList() {
const users = useSelector((state) => state.users);
const dispatch = useDispatch();
function addUser(event) {
event.preventDefault();
dispatch(userState.actions.add(event.target.elements.username.value));
dispatch(userState.actions.log());
}
function removeUser() {
dispatch(userState.actions.remove());
}
function clearList() {
dispatch(userState.actions.clear());
console.log(store.getState());
}
function logState() {
dispatch(userState.actions.log());
}
return (
<div>
<ul className="ul">
{users.map((user, index) => (
<div className="list-element" key={index + 1}>
<li key={index}>{user}</li>
<button onClick={removeUser} type="submit">
remove
</button>
</div>
))}
</ul>
<form onSubmit={addUser}>
<input name="username" type="text" />
Insert name
<button type="submit">Add user</button>
</form>
<button onClick={clearList}>Clear list</button>
<button onClick={logState}>Log state</button>
</div>
);
}
First of all, update the component like following (I commented on the place of change)
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { store } from "./Store";
import { add, remove, clear, userState } from "./UsersState";
// make sure to export these actions from the reducer file
export function UsersList() {
const users = useSelector((state) => state.users);
const dispatch = useDispatch();
function addUser(event) {
event.preventDefault();
dispatch(userState.actions.add(event.target.elements.username.value));
dispatch(userState.actions.log());
}
function removeUser() {
dispatch(userState.actions.remove());
}
function clearList() {
dispatch(userState.actions.clear());
console.log(store.getState());
}
function logState() {
dispatch(userState.actions.log());
}
return (
<div>
<ul className="ul">
{users.map((user, index) => (
<div className="list-element" key={index + 1}>
<li key={index}>{user}</li>
{/* you can directly dispatch the remove function*/}
{/* no need this button to be type="button"*/}
{/* change here */}
<button onClick={() => dispatch(remove(user))}>
remove
</button>
</div>
))}
</ul>
<form onSubmit={addUser}>
<input name="username" type="text" />
Insert name
<button>Add user</button>
</form>
<button onClick={clearList}>Clear list</button>
<button onClick={logState}>Log state</button>
</div>
);
}
You needed to pass user in the action and dispatch it, hope it helps!
For the reducer file, you have to export the actions also
import { createSlice, current } from "#reduxjs/toolkit";
export const userState = createSlice({
name: "users",
initialState: [],
reducers: {
add: (state, action) => [...state, action.payload],
remove: (state, action) =>
// need to redefine the state, forgot this part
// change here
state = state.filter((user, index) => user !== action.payload),
clear: (state, action) => [],
log: (state, action) => console.log(state),
},
});
export const {add, remove, clear, log} = userState.actions;
export default userState.reducer;
Related
I build a MERN stack ecommerce using redux. In the part of cart components I add product to the cart and also to localStorage. When I refresh the page the items disappear from the page but it is still in localStorage and I can't find the problem.
This is my cart reducer code:
import { ADD_TO_CART } from "../constants/cartConstants";
export const cartReducer = (state = { cartItems: [] }, action) => {
switch (action.type) {
case ADD_TO_CART:
const item = action.payload;
const isItemExist = state.cartItems.find(
(i) => i.product === item.product
);
if (isItemExist) {
return {
...state,
cartItems: state.cartItems.forEach((i) =>
i.product === isItemExist.product ? item : i
),
};
} else {
return {
...state,
cartItems: [...state.cartItems, item],
};
}
default:
return state;
}
};
and this is my store initialState code:
const initialState = {
cart: {
cartItems: localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [],
},
};
const store = configureStore(
{ reducer },
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
cart.jsx
import React, { Fragment, useEffect, useState } from "react";
import "./Cart.css";
import { CartItems } from "../";
import { useDispatch, useSelector } from "react-redux";
import { addItemsToCart } from "../../actions/cartActions";
const Cart = () => {
const dispatch = useDispatch();
const { cartItems } = useSelector((state) => state.cart);
const increaseQuantity = (id, quantity, stock) => {
const newQty = quantity + 1;
if (stock < quantity) {
return;
}
dispatch(addItemsToCart(id, newQty));
};
return (
<Fragment>
<div className="cart__page">
<div className="cart__header">
<p>Product</p>
<p>Quantity</p>
<p>Subtotal</p>
</div>
{cartItems &&
cartItems?.map((item) => (
<div key={item?.product} className="cartContainer">
<CartItems item={item} />
<div className="cart__Input">
<button>+</button>
<input type="number" readOnly value={item?.quantity} />
<button>-</button>
</div>
<p className="Cart__subtotal">
{`$${item?.price * item?.quantity}`}
</p>
</div>
))
}
I'm trying to use useEffect hook but the data come by redux doesn't save in localStorage.
The configureStore function takes only a single configuration object that takes reducer, middleware, devTools, preloadedState, and enhancers properties.
See configureStore.
It appears you are correctly accessing the persisted state from localStorage, but then not passing the initial state correctly to the store configurator.
import { configureStore } from '#reduxjs/toolkit';
const initialState = {
cart: {
cartItems: JSON.parse(localStorage.getItem("cartItems")) ?? [],
},
};
const store = configureStore({
reducer,
preloadedState: initialState,
});
export default store;
If your redux state persistence needs change or grow then I'd suggest taking a look at redux-persist. If you are already familiar with Redux then this is about a 5-15 minute integration the first time.
Language used : javascript with react / redux
My project : I have a multiple step form. At every step,when a user write something or check someting i'm using redux to store the state. I have one reducer but I'm creating an action for every step of the form
What i would like to do : I would like to have only one action to update the state step by step.
What i'm doing now (working fine) :
my page who contain each step
const Form = () => {
return (
<div className="page">
<form>
{
{
1: <StepOne />,
2: <StepTwo />,
3: <StepThree />,
}[buttonDatas.pageNumber]
}
</form>
</div>
);
};
export default Form;
here one example of a step component (stepOne)
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addName } from '../../../actions/form.action.js';
import { isEmpty } from '../../../middlewares/verification.js';
export const StepOne = () => {
const dispatch = useDispatch();
const usersList = useSelector((state) => state.userReducer);
const [userName, setUserName] = useState();
useEffect(() => {
dispatch(addName(userName));
}, [dispatch, userName]);
return (
<div>
<label>Select the user name</label>
<select
name="name"
onChange={(e) => {
const userSelected = e.target.value;
setUserName(userSelected);
}}
defaultValue={'default'}
>
<option value="default" hidden disabled>
Select a user
</option>
{!isEmpty(usersList[0]) &&
usersList.map((user) => {
return (
<option key={user.id}>
{user.fullName}
</option>
);
})}
</select>
</div>
);
};
here my reducer :
import {
ADD_NAME,
ADD_PHONE,
ADD_ADDRESS,
} from '../actions/form.action';
const initialState = {
userInfo: {
name: '',
phone : '',
},
address: ''
};
export default function formReducer(state = initialState, actions) {
switch (actions.type) {
case ADD_NAME:
state = {
...state,
userInfo: {
name: actions.payload,
},
};
return state;
case ADD_PHONE:
state = {
...state,
userInfo: {
phone: actions.payload,
},
};
return state;
case ADD_ADDRESS:
state = {
...state,
address: actions.payload,
},
};
return state;
default:
return state;
}
}
Is there a better way to write it ?
You can create one object that includes all the necessary property values throughout your multi-step form wizard layout and maintain only one action to save the data in the Redux store. Instead of making each action item for a single property of the identical form.
hereby am giving you a reference which will help you to organise your code based on your requirement.
I will recommend you to go through below two links:
Redux Form multi-step wizard form
Video Tutorial of creating a multi-step form using react Hooks.
With the indications of nimish, i've used redux toolkit and react-hook-form and it's working fine.
What i've change :
Create a slice file (for my reducer and action ) with redux toolkit
import { createSlice } from '#reduxjs/toolkit';
const formSlice = createSlice({
name: 'form',
initialState: {
userInfo: {
name: '',
phone: ''
},
address: ''
},
reducers: {
selectUserName: (state, action) => {
state.userInfo.name = action.payload;
},
addUserPhone: (state, action) => {
state.userInfo.phone = action.payload;
},
addAddress: (state, action) => {
state.address = action.payload;
},
},
});
export const reducer = formSlice.reducer;
export const {
selectUserName,
addUserPhone,
addAddress,
} = formSlice.actions;
use it in my userInfo component
import React from 'react';
import { userName } from './userName';
import { userPhone } from './userPhone';
import { useDispatch, useSelector } from 'react-redux';
import {
selectUserName,
addUserPhone,
} from '../../../reducer/form.slice';
import { useForm } from 'react-hook-form';
export const UserInfo = () => {
const dispatch = useDispatch();
const state = useSelector((state) => state.reducer);
const { register, handleSubmit } = useForm({
defaultValues: {},
});
const onChange = (data) => {
dispatch(selectUserName(data.name);
dispatch(addUserPhone(data.phone));
};
return (
<div>
<h2 className="title"> Step One : Information User</h2>
<form onChange={handleSubmit(onChange)} className="form">
<userName register={register} />
<userPhone register={register} />
</form>
//to see your result
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
);
};
in the child comp
import React from 'react';
import { useSelector } from 'react-redux';
import { isEmpty } from '../../../middlewares/verification.js';
export const UserName = ({ register }) => {
const userList = useSelector((state) => state.userReducer);
return (
<div className="form_group">
<label>Select the user name</label>
<select
className="select"
name="name"
{...register('userName')}
defaultValue={'default'}
>
<option value="default" hidden disabled>
Select a user
</option>
{!isEmpty(userList[0]) &&
userList.map((user) => {
return (
<option value={user.fullName} key={user.mail}>
{user.fullName}
</option>
);
})}
</select>
</div>
);
};
thank you.
I'm developing a CRUD and, in one of the components, when the user creates the post, a div is rendered with the title and content values of his post. I need his posts to be saved even when he navigates between pages (without refreshing the page). Currently I can create as many posts as I want and they will be lined up one below the other, but when I go back to the previous page they are deleted. I tried to implement redux in this part the way I implemented it to save the user input (creating a slice to save the posts) but it didn't work. I know normally posts wouldn't be deleted, so I'd like to know where I'm going wrong.
signup screen:
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { useDispatch } from 'react-redux';
import userSlice from '../redux/userslice';
import { useNavigate } from "react-router-dom";
function Signup() {
const navigate = useNavigate();
const dispatch = useDispatch();
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if (name!== '') {
setButtonGrey("black")
}
else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
dispatch(userSlice.actions.saveUser(name))
navigate("/main")
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input type="text" name="name" value={name} onChange = {e => handleChangeName(e.target.value)} placeholder="Jane Doe" />
<div className="button">
<button type="submit" style={{backgroundColor: buttonGrey}} disabled={!name} >
ENTER
</button>
</div>
</form>
</div>
</div>
);
}
export default Signup;
CRUD screen:
import React, { useState, useEffect } from "react";
import "../_assets/App.css";
import "../_assets/mainscreen.css";
import { MdDeleteForever } from "react-icons/md";
import { FiEdit } from "react-icons/fi";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { Navigate } from 'react-router-dom';
import postsSlice from '../redux/postsslice'
import Modal from "../components/modal.jsx";
function MainScreen() {
const dispatch = useDispatch();
const user = useSelector((state) => state.user)
const loadPosts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [newPosts, setNewPosts] = useState([])
const [buttonGreyOut, setButtonGreyOut] = useState("#cccccc");
useEffect(() => {
if (title && content !== "") {
setButtonGreyOut("black");
} else {
setButtonGreyOut("#cccccc");
}
},[title, content]);
const handleSubmitSendPost = (e) => {
e.preventDefault();
setNewPosts(newPosts.concat({title, content}))
dispatch(postsSlice.actions.savePosts(newPosts))
setTitle('')
setContent('')
};
const handleChangeTitle = (text) => {
setTitle(text);
};
const handleChangeContent = (text) => {
setContent(text);
};
const [openModal, setOpenModal] = useState();
if (user === '') {
return <Navigate to="/" />
} else {
return (
<div className="containerMainScreen">
{openModal && <Modal closeModal={setOpenModal} />}
<div className="bar">
<h1>Codeleap</h1>
</div>
<div className="boxPost">
<h2 style={{ fontWeight: 700 }}>What's on your mind?</h2>
<h2>Title</h2>
<form onSubmit={handleSubmitSendPost}>
<input
type="text"
placeholder="Hello World"
name="name"
value={title}
onChange={(e) => handleChangeTitle(e.target.value)}
></input>
<h2>Content</h2>
<textarea
placeholder="Content"
name="content"
value={content}
onChange={(e) => handleChangeContent(e.target.value)}
></textarea>
<button
className="createButton"
type="submit"
style={{ backgroundColor: buttonGreyOut }}
disabled={!title || !content}
>
CREATE
</button>
</form>
</div>
{newPosts.map((post) => (
<div className="boxPost">
<div className="bar">
<h1>{post.title}</h1>
<MdDeleteForever
className="icon"
onClick={() => {
setOpenModal(true);
}}
/>
<FiEdit
style={{ color: "white", fontSize: "45px", paddingLeft: "23px" }}
/>
</div>
<div id="postowner">
<h3>#{user}</h3>
<h3>25 minutes ago</h3>
<br></br>
<textarea style={{ border: "none" }}>{post.content}</textarea>
</div>
</div>
))}
</div>
);
}
}export default MainScreen;
store.js:
import { configureStore } from '#reduxjs/toolkit';
import userSlice from './userslice';
import postsSlice from './postsslice'
export const store = configureStore({
reducer: {
user: userSlice.reducer,
loadPosts: postsSlice.reducer
},
})
postsSlice:
import { createSlice } from "#reduxjs/toolkit";
const postsSlice = createSlice({
name: "posts",
initialState: "",
reducers: {
savePosts: (state, action) => action.payload
}
});
export default postsSlice
CRUD screenshot:
https://i.stack.imgur.com/YoCJz.png
You are dispatching the savePosts action with the value of newPosts from before the current posts are added to it. The problem is in these lines:
setNewPosts(newPosts.concat({title, content}))
dispatch(postsSlice.actions.savePosts(newPosts))
Try something like this instead:
const posts = newPosts.concat({title, content})
setNewPosts(posts)
dispatch(postsSlice.actions.savePosts(posts))
It does not make sense to store the posts array in Redux and also store it in local component state. In my opinion you should ditch the component newPosts state and access the posts via Redux with useSelector.
I would also recommend dispatching an addPost action which requires just the current post. Let the reducer handle adding it to the array.
The state is an array of posts, so your initialState should be an empty array rather than an empty string.
const postsSlice = createSlice({
name: "posts",
initialState: [],
reducers: {
// add one post to the array.
addPost: (state, action) => {
state.push(action.payload); // modifies the draft state.
},
// replace the entire array.
replacePosts: (state, action) => action.payload
}
});
function MainScreen() {
const dispatch = useDispatch();
const user = useSelector((state) => state.user)
const posts = useSelector((state) => state.loadPosts)
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
/* ... */
const handleSubmitSendPost = (e) => {
e.preventDefault();
dispatch(postsSlice.actions.addPost({title, content}))
setTitle('')
setContent('')
};
|I have the following component based on this:
**WarningModal.js**
import React from 'react';
import ReactDOM from 'react-dom';
import {connect, Provider} from 'react-redux';
import PropTypes from 'prop-types';
import {Alert, No} from './pure/Icons/Icons';
import Button from './pure/Button/Button';
import Modal from './pure/Modal/Modal';
import {setWarning} from '../actions/app/appActions';
import configureStore from '../store/configureStore';
const store = configureStore();
export const WarningModal = (props) => {
const {message, withCleanup} = props;
const [
title,
text,
leave,
cancel
] = message.split('|');
const handleOnClick = () => {
props.setWarning(false);
withCleanup(true);
}
return(
<Modal>
<header>{title}</header>
<p>{text}</p>
<Alert />
<div className="modal__buttons-wrapper modal__buttons-wrapper--center">
<button
onClick={() => withCleanup(false)}
className="button modal__close-button button--icon button--icon-only button--text-link"
>
<No />
</button>
<Button id="leave-warning-button" className="button--transparent-bg" onClick={() => handleOnClick()}>{leave}</Button>
<Button id="cancel-warning-button" onClick={() => withCleanup(false)}>{cancel}</Button>
</div>
</Modal>
);
}
WarningModal.propTypes = {
withCleanup: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
setWarning: PropTypes.func.isRequired
};
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const WarningModalContainer = connect(mapStateToProps, {
setWarning
})(WarningModal);
export default (message, callback) => {
const modal = document.createElement('div');
document.body.appendChild(modal);
const withCleanup = (answer) => {
ReactDOM.unmountComponentAtNode(modal);
document.body.removeChild(modal);
callback(answer);
};
ReactDOM.render(
<Provider store={store}>
<WarningModalContainer
message={message}
withCleanup={withCleanup}
/>
</Provider>,
modal
);
};
the issue I have is that 'setWarning' doesn't update the state, it does get called as I have a debugger inside the action and the reducer but the actual property doesn't not change to 'false' when:
props.setWarning(false);
gets called.
I use the following to trigger the custom modal:
const togglePromptCondition =
location.hash === '#access-templates' || location.hash === '#security-groups'
? promptCondition
: isFormDirty || isWarning;
<Prompt message={promptMessage} when={togglePromptCondition} />
To test this even further I have added 2 buttons in the application to toggle 'isWarning' (the state property I am talking about) and it works as expected.
I think that although WarningModal is connected in actual fact it isn't.
REDUCER
...
case SET_WARNING:
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
ACTION
...
export const setWarning = status => {
console.log('action called')
return {
type: SET_WARNING,
payload: status
}
};
...
UPDATE
After having to incorporates the following:
const mapStateToProps = state => {
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
I am now getting:
Maybe this could help?
You have to dispatch the actions in the action creator and the type of the action to dispatch should be always string.
Try this
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
console.log(dispatch)
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
const WarningModalContainer = connect(mapStateToProps, mapDispatchToProps)(WarningModal);
REDUCER
...
case 'SET_WARNING':
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
I'm working on a react-redux app and for some reason the action I call does not reach the reducer (in which I currently only have a log statement). I have attached the code I feel is relevant and any contributions would be highly appreciated.
Action called within function in component:
onSearchPressed() {
console.log('search pressed');
this.props.addToSaved();
}
actions/index.js:
var actions = exports = module.exports
exports.ADD_SAVED = "ADD_SAVED";
exports.addToSaved = function addToSaved() {
console.log('got to ADD_SAVED step 2');
return {
type: actions.ADD_SAVED
}
}
reducers/items.js:
const {
ADD_SAVED
} = require('../actions/index')
const initialState = {
savedList: []
}
module.exports = function items(state = initialState, action) {
let list
switch (action.type) {
case ADD_SAVED:
console.log('GOT to Step 3');
return state;
default:
console.log('got to default');
return state;
}
}
reducers/index.js:
const { combineReducers } = require('redux')
const items = require('./items')
const rootReducer = combineReducers({
items: items
})
module.exports = rootReducer
store/configure-store.js:
import { createStore } from 'redux'
import rootReducer from '../reducers'
let store = createStore(rootReducer)
EDIT: Entire component for onSearchPressed:
class MainView extends Component {
onSearchPressed() {
this.props.addToSaved();
}
render() {
console.log('MainView clicked');
var property = this.props.property;
return (
<View style={styles.container}>
<Image style={styles.image}
source={{uri: property.img_url}} />
<Text style={styles.description}>{property.summary}</Text>
<TouchableHighlight style = {styles.button}
onPress={this.onSearchPressed.bind(this)}
underlayColor='#99d9f4'>
<Text style = {styles.buttonText}>Save</Text>
</TouchableHighlight>
</View>
);
}
}
module.exports = MainView;
As Rick Jolly mentioned in the comments on your question, your onSearchPressed() function isn't actually dispatching that action, because addToSaved() simply returns an action object - it doesn't dispatch anything.
If you want to dispatch actions from a component, you should use react-redux to connect your component(s) to redux. For example:
const { connect } = require('react-redux')
class MainView extends Component {
onSearchPressed() {
this.props.dispatchAddToSaved();
}
render() {...}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatchAddToSaved: () => dispatch(addToSaved())
}
}
module.exports = connect(null, mapDispatchToProps)(MainView)
See the 'Usage With React' section of the Redux docs for more information.
Recently I faced issue like this and found that I had used action import but it has to come from props. Check out all uses of toggleAddContactModal. In my case I had missed toggleAddContactModal from destructuring statement which caused this issue.
import React from 'react'
import ReactDOM from 'react-dom'
import Modal from 'react-modal'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import {
fetchContacts,
addContact,
toggleAddContactModal
} from '../../modules/contacts'
import ContactList from "../../components/contactList";
Modal.setAppElement('#root')
class Contacts extends React.Component {
componentDidMount(){
this.props.fetchContacts();
}
render(){
const {fetchContacts, isFetching, contacts,
error, isAdding, addContact, isRegisterModalOpen,
toggleAddContactModal} = this.props;
let firstName;
let lastName;
const handleAddContact = (e) => {
e.preventDefault();
if (!firstName.value.trim() || !lastName.value.trim()) {
return
}
addContact({ firstName : firstName.value, lastName: lastName.value});
};
return (
<div>
<h1>Contacts</h1>
<div>
<button onClick={fetchContacts} disabled={isFetching}>
Get contacts
</button>
<button onClick={toggleAddContactModal}>
Add contact
</button>
</div>
<Modal isOpen={isRegisterModalOpen} onRequestClose={toggleAddContactModal}>
<input type="text" name="firstName" placeholder="First name" ref={node =>
(firstName = node)} ></input>
<input type="text" name="lastName" placeholder="Last name" ref={node =>
(lastName = node)} ></input>
<button onClick={handleAddContact} disabled={isAdding}>
Save
</button>
</Modal>
<p>{error}</p>
<p>Total {contacts.length} contacts</p>
<div>
<ContactList contacts={contacts} />
</div>
</div>
);
}
}
const mapStateToProps = ({ contactInfo }) => {
console.log(contactInfo)
return ({
isAdding: contactInfo.isAdding,
error: contactInfo.error,
contacts: contactInfo.contacts,
isFetching: contactInfo.isFetching,
isRegisterModalOpen: contactInfo.isRegisterModalOpen
});
}
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
fetchContacts,
addContact,
toggleAddContactModal
},
dispatch
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(Contacts)