Updating redux state onClick - javascript

I have a component that displays data from the state. I'm using redux for state. I want to be able to click a button and filter the state. But I'm stuck on dispatching the action from the button.
Right now I have a button that is supposed to dispatch the action but it's not being called. I'm not sure if the mapsToDispatchProps is wrong or it's something else.
Here is the actions
import { GET_POLLS, SHOW_APPROVAL } from './types';
const URL = 'https://projects.fivethirtyeight.com/polls/polls.json';
export const getPolls = () => dispatch => {
return fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
}
export const getApproval = () => ({ type: SHOW_APPROVAL })
reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return (payload.type === "trump-approval")
default:
return state
}
}
export default pollReducer;
types
export const GET_POLLS = 'GET_POLLS';
export const POLLS_LOADING = 'POLLS_LOADING';
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_APPROVAL = 'SHOW_APPROVAL';
list that displays data
import React, { Component } from 'react'
import { PollCard } from '../Components/PollCard'
// import FilterLink from './FilterLink'
import * as moment from 'moment';
import { connect } from 'react-redux'
import { getPolls, getApproval } from '../actions/index';
class PollList extends Component {
componentDidMount() {
this.props.getPolls();
}
render() {
console.log("rendering list")
const { polls } = this.props
const range = 30
var dateRange = moment().subtract(range, 'days').calendar();
var filteredPolls = polls.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange)).reverse()
return (
<React.Fragment>
<button onClick={getApproval}>
Get Approval
</button>
{console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
polls: state.polls
});
const mapDispatchToProps = {
getApproval
};
export default connect(
mapStateToProps,
mapDispatchToProps,
{ getPolls, getApproval }
)(PollList);
// export default PollList;

Your mapDispatchToProps() appears to be configured incorrectly. You need to define a function that returns an object, defining a key-value pair for each action you want to make available as a prop in your component.
const mapDispatchToProps = (dispatch) => {
return {
getApproval: () => {
dispatch(getApproval())
},
getPolls: () => {
dispatch(getPolls())
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProp)(PollList);
Now getPolls is available as prop and you can use it in componentDidMount()
componentDidMount() {
this.props.getPolls();
}
You should also create an onClick handler for your getApproval action
handleClick = () => {
this.props.getApproval()
}
And then connect it to your onClick event-listener
<React.Fragment>
<button onClick={this.handleClick}>
Get Approval
</button>
console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
Action File
export const getPolls = () => dispatch => {
fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
.catch(errors => {
dispatch({ type: "GET_ERRORS", payload: errors.response.data })
})
}
Reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return state.filter((poll) => {
return poll.type === "trump-approval"
})
case "GET_ERRORS":
return payload
default:
return state
}
}
export default pollReducer;

You are not calling the action function.
// Either destructure it
const { polls, getApproval } = this.props;
<button onClick={getApproval}>
Get Approval
</button>
// Or use this.props.function
<button onClick={this.props.getApproval}>
Get Approval
</button>
// You don't need this
const mapDispatchToProps = {
getApproval
};
// You don't need this
const mapStateToProps = state => {
return {polls: state.polls};
};
export default connect(
mapStateToProps,
// Doing this is easier, cleaner & faster
{ getPolls, getApproval }
)(PollList);
Here you are doing it correctly;
componentDidMount() {
this.props.getPolls();
}

Related

TypeError: updateElement is not a function

I am trying to update an element from an array by adding an object as a property like shown in this picture
When a user clicks on a single node button, a modal appears the user fills the form and then it is addes as a property for this node.
But for some reason I get this type error that says that the updateElement is not a function.
BTW, I am using Redux & react-flow-renderer libraries.
Reducer
import * as types from '../actions/types';
const initialState = {
elements: []
};
const flow = (state = initialState, action) => {
switch (action.type) {
case types.UPDATE_ELEMENT:
return {
...state,
elements: state.elements.map((e) => {
if (e.id === action.payload.id) {
e = {
...e,
options: action.payload.options,
};
}
return e;
}),
};
default:
return state;
}
};
export default flow;
Action
import { UPDATE_ELEMENT } from './types';
export const updateElement = (data) => (dispatch) => {
dispatch({
type: UPDATE_ELEMENT,
payload: data,
});
};
Node modal
import React, { useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { updateElement } from '../../../../redux/actions/flow';
const VPCNodeModal = (props, { updateElement }) => {
const [formData, setFormData] = useState({
instance: '',
});
// options
const { instance } = formData;
const onFormChange = (e) =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmitForm = () => {
const update = {
id: selectedElement.id,
options: formData,
};
updateElement(update);
};
return (
<>
<Modal {...props}>
<form
onSubmit={(e) => {
e.preventDefault();
onSubmitForm();
}}
>
<label>
<span> Instance name:</span>
<input
type='text'
name='instance'
value={instance}
onChange={onFormChange}
/>
</label>
<button type='submit'>Submit</button>
</form>
</Modal>
</>
);
};
VPCNodeModal.propTypes = {
updateElement: PropTypes.func.isRequired
};
export default connect(null, { updateElement })(VPCNodeModal);
Issue is while receiving the props.
change
const VPCNodeModal = (props, { updateElement }) => {
to
const VPCNodeModal = (props) => {
const { updateElement } = props;
updateElement is a props was passes in VPCNodeModal. So you should update like this with spread operator
const VPCNodeModal = ({ updateElement, ...props }) => {

React-Redux: Action is Dispatched but Reducer is not updating the state

I'm trying to check if my store is onboarded or not. for that, I'm making an API call through the redux to check it in the BE and if it's true I'll redirect it to the dashboard. I'm able to get the data successfully from BE, and on success checkIsStoreOnboardedSuccess() is called but in the reducer, the state is not updated with the CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS state in the reducer.
action.js
import * as actionTypes from './index';
import API from '../../api';
export const clearCheckIsStoreOnboarded = () => {
return {
type: actionTypes.CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING,
};
};
export const checkIsStoreOnboarded = (payload) => {
return (dispatch) => {
dispatch(checkIsStoreOnboardedInitiate());
API.getAccountSettings(payload)
.then((response) => {
checkIsStoreOnboardedSuccess(response.data);
})
.catch((err) => {
checkIsStoreOnboardedFailure(err);
});
};
};
const checkIsStoreOnboardedInitiate = () => {
return {
type: actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START,
};
};
const checkIsStoreOnboardedSuccess = (data) => {
return {
type: actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS,
data: data,
};
};
const checkIsStoreOnboardedFailure = (err) => {
return {
type: actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL,
data: err,
};
};
reducer.js
import * as actionTypes from '../actions';
const initialState = {
isLoading: true,
isError: false,
isDone: false,
data: [],
error: null,
};
const clearCheckIsStoreOnboarded = () => {
return initialState;
};
const checkIsStoreOnboardedStart = (state) => {
return { ...state, isLoading: true, error: null, isError: false };
};
const checkIsStoreOnboardedSuccess = (state, action) => {
return { ...state, data: action.data, isDone: true, isLoading: false };
};
const checkIsStoreOnboardedFailure = (state, action) => {
return { ...state, error: action.data, isLoading: false, isError: true };
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING:
return clearCheckIsStoreOnboarded();
case actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START:
return checkIsStoreOnboardedStart(state);
case actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS:
return checkIsStoreOnboardedSuccess(state, action);
case actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL:
return checkIsStoreOnboardedFailure(state, action);
default:
return state;
}
};
export default reducer;
actionTypes.js
export const CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING = 'CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING';
export const CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START = 'CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START';
export const CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS = 'CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS';
export const CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL = 'CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL';
onboard.js
import React, { useState, useEffect } from 'react';
import { withCookies } from 'react-cookie';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import Crew from './Crew';
import Service from './Services';
import Address from './Address';
import { useStyles } from './css/index.css';
import Header from './header';
import Stepper from './stepper';
import { getStoreID } from '../../../utils';
import {
clearCheckIsStoreOnboarded,
checkIsStoreOnboarded,
} from '../../../store/actions/check-is-store-onboarded-for-onboarding'
import Loader from '../../../components/CircularProgressLoader';
const OnboardScreen = ({
cookies,
clearCheckIsStoreOnboarded,
checkIsStoreOnboarded,
checkIsStoreOnboardedData,
}) => {
const [step, setStep] = useState(0);
// eslint-disable-next-line no-unused-vars
const [width, isDesktop] = useWindowWitdh();
const classes = useStyles(isDesktop);
const store_id = getStoreID(cookies);
useEffect(() => {
checkIsStoreOnboarded({
store_id,
});
}, []);
useEffect(() => () => clearCheckIsStoreOnboarded(), []);
if(checkIsStoreOnboarded.isDone){
<Redirect to='/dashboard'>
}
const updateStep = () => {
const updatedStep = step + 1;
setStep(updatedStep);
};
const onboardingScreenToRender = () => {
switch (step) {
case 0:
return (
<Crew />
);
case 1:
return (
<Service />
);
case 2:
return <Address />;
}
};
return (
<div className={classes.container}>
<Header isDesktop={isDesktop} />
<div className={classes.contentOfContainer}>
<div className={classes.titleHeader}>
Onboarding
</div>
<Stepper stepNumber={step} setStepNumber={setStep} />
{checkIsStoreOnboardedData.isLoading && <Loader />}
</div>
</div>
// <OnboardLoader />
);
};
const mapStateToProps = (state, ownProps) => {
return {
...ownProps,
checkIsStoreOnboardedData: state.checkIsStoreOnboardedForOnboardingReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
checkIsStoreOnboarded: (payload) => dispatch(checkIsStoreOnboarded(payload)),
clearCheckIsStoreOnboarded: () => dispatch(clearCheckIsStoreOnboarded()),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withCookies(OnboardScreen));
You need to dispatch your actions:
export const checkIsStoreOnboarded = (payload) => {
return (dispatch) => {
dispatch(checkIsStoreOnboardedInitiate());
API.getAccountSettings(payload)
.then((response) => {
// here
dispatch(checkIsStoreOnboardedSuccess(response.data));
})
.catch((err) => {
// and here
dispatch(checkIsStoreOnboardedFailure(err)(;
});
};
};
That said: you are writing a very outdated style of Redux here - in modern Redux, all of that would probably be possible with 1/4 of the code. If you are just learning Redux, you are probably following a very outdated tutorial. Modern Redux does not require you to write action type strings or action creators and your reducers can contain mutable logic. Also, it does not use connect unless you are working with legacy class components (which you don't seem to be doing).
I really recommend you to read the official Redux tutorial at https://redux.js.org/tutorials/essentials/part-1-overview-concepts

Passing multiple actions to mapDispatchToProps in Redux

I have 3 actions I am trying to use in this component. One logs me out of Firebase Google Auth (working correctly), and the other two are simply changing a piece of state to a certain string which I am going to use later to determine what component to render.
The commented out mapDispatchToProps works fine and it's how I'm used to writing it, the one using the logout method is the syntax I can't figure out. How can I refactor the below so that setRoutines and setExercises work?
The component:
import React from 'react';
import { connect } from "react-redux";
import { firebaseLogout } from '../Auth/Auth.actions';
import { setRoutines, setExercises } from './Profile.actions';
const Profile = ({logout, setRoutines, setExercises}) => (
<React.Fragment>
<button onClick={setRoutines}>My Routines</button>
<button onClick={setExercises}>My Exercises</button>
<button onClick={logout}>Logout</button>
</React.Fragment>
);
const mapDispatchToProps = (dispatch) => ({
logout: () => dispatch(firebaseLogout()),
setRoutines,
setExercises,
});
// const mapDispatchToProps = {
// setRoutines,
// setExercises
// };
export default connect(
undefined,
mapDispatchToProps
)(Profile);
My actions file:
export const setRoutines = () => ({
type: "SET_ROUTINES",
payload: "routines"
});
export const setExercises = () => ({
type: "SET_EXERCISES",
payload: "exercises"
});
export const logout = () => ({
type: 'LOGOUT'
});
export const firebaseLogout = () => {
return () => {
return firebase.auth().signOut();
}
};
My reducer file:
export default (state = {view:'routines'}, action) => {
switch (action.type) {
case 'SET_ROUTINES':
return {
...state,
view: action.payload
};
case 'SET_EXERCISES':
return {
...state,
view: action.payload
};
case 'LOGOUT':
return {};
default:
return state;
}
};
By modifying the mapDispatchToProps to the below mention format should help in creating a bound action creator that automatically dispatches.
const mapDispatchToProps = (dispatch) => ({
logout: () => dispatch(firebaseLogout()),
boundRoutines: () => dispatch(setRoutines()),
boundExercises: () => dispatch(setExercises()),
});
After creating a bound action creator we can call the creator as follows.
const Profile = ({logout, boundRoutines, boundExercises}) => (
<React.Fragment>
<button onClick={boundRoutines}>My Routines</button>
<button onClick={boundExercises}>My Exercises</button>
<button onClick={logout}>Logout</button>
</React.Fragment>
);

Action doesn't update the store

|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
};
...

mapDispatchToProps dispatch action not working to update State

In my index.js the addCoin action is working.
import { addCoin } from './reducer/portfolio/actions'
const element = document.getElementById('coinhover');
const store = createStore(reducer, compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
store.dispatch(addCoin('bitcoin'));
When store.dispatch is called I can see the updated state here.
However I do not want to call dispatch actions from my index.js, but from within my components.
My SearchCoin component:
import React from 'react'
import { connect } from 'react-redux'
import * as R from 'ramda'
import * as api from '../../services/api'
import { addToPortfolio, findCoins } from '../../services/coinFactory'
import { addCoin } from '../../reducer/portfolio/actions'
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) {
return () => {
dispatch(addCoin(coin))
}
}
});
class SearchCoin extends React.Component {
constructor(props) {
super(props)
this.state = {
searched: []
};
// console.log('props', props);
this.close = this.close.bind(this);
}
componentDidMount() {
this.coinInput.focus();
this.handleChange = this.handleChange.bind(this);
this.clickCoin = this.clickCoin.bind(this);
}
handleChange() {
const text = document.getElementById('coin-search').value;
const search = (text) => this.setState({ searched: findCoins(text) });
const clearSearch = () => this.setState({ searched: [] });
text.length > 1 ? search(text) : clearSearch();
}
clickCoin(coin) {
console.log('clickCoin', coin);
// api.getCoin(coin.id).then((res) => {
// const apiCoin = R.head(res.data);
// addToPortfolio(apiCoin);
// });
this.props.selectCoin(coin);
this.props.closeSearch();
}
close() {
this.props.closeSearch();
}
render() {
const searched = this.state.searched.map((coin) => {
return (
<li key={ coin.id } onClick={ ()=> this.clickCoin(coin) }>
<div className="coin-logo">
<img src={ coin.logo }/>
</div>
<span>{ coin.name }</span>
</li>
);
});
return (
<div id="search-coin-component">
<input type="text"
id="coin-search"
className="coin-search-input fl"
placeholder="Search"
onChange={ ()=> this.handleChange() }
ref={ (input) => { this.coinInput = input; } } />
<div className="icon-cancel-outline fl" onClick={ this.close }></div>
<div className="coin-select">
<ul>
{ searched }
</ul>
</div>
</div>
)
}
}
export default connect(null, mapDispatchToProps)(SearchCoin)
This is the onClick:
<li key={ coin.id } onClick={ ()=> this.clickCoin(coin) }>
At the bottom of the file I am using connect to add mapDispatchToProps
export default connect(null, mapDispatchToProps)(SearchCoin)
Here is the class method clickCoin which calls this.props.selectCoin
clickCoin(coin) {
console.log('clickCoin', coin);
this.props.selectCoin(coin);
this.props.closeSearch();
}
Finally selectCoin
import { addCoin } from '../../reducer/portfolio/actions'
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) {
return () => {
dispatch(addCoin(coin))
}
}
});
However when I click the button it seems like the dispatch is not fired as nothing happens to the redux state.
import * as R from 'ramda'
import * as api from '../../services/api'
import { addToPortfolio } from '../../services/coinFactory'
export const ADD_COIN = 'ADD_COIN'
export function addCoin(coin) {
console.log('addCoin', coin);
return dispatch =>
api.getCoin(coin)
.then((res) => addToPortfolio(R.head(res.data)))
.then((portfolio) => dispatch(add(portfolio)));
}
// action creator
export function add(portfolio) {
return {
type: ADD_COIN,
portfolio
}
}
The reducer
import { ADD_COIN } from './actions'
const initialState = [];
export default (state = initialState, action) => {
switch(action.type) {
case ADD_COIN:
return action.portfolio;
default:
return state;
}
}
the reducer/index.js
import { combineReducers } from 'redux'
import portfolio from './portfolio'
export default combineReducers({
portfolio
});
Apart from azium answer, you can use actions like this. It saves you some writing,
export default connect(null, {addCoin})(SearchCoin)
and you can use it like this,
clickCoin(coin) {
console.log('clickCoin', coin);
this.props.addCoin(coin);
this.props.closeSearch();
}
The problem is that you are wrapping your function with an extra function.
Change:
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) {
return () => { <--- returning extra function
dispatch(addCoin(coin))
}
}
})
to:
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) { dispatch(addCoin(coin)) }
})

Categories

Resources