I need a little help with this redux problem that I am encountering. Here, I have an APP.js code that called the action from a file called duck.js.
import {
selectBaseCurrency,
selectExchangeCurrency,
selectExhangeRate,
setBaseCurrency, //calling this action
initialState,
} from "./configureStore/duck";
In this code, I have specified the mapDispatchToProp to dispatch the action.
const mapDispatchToProps = (dispatch,ownProps)=> ({
onClick: () => dispatch(setBaseCurrency(ownProps.baseCurrency)),
});
I've also connected it to the connect().
export default connect(
state => ({
exchangeRate: selectExhangeRate(state),
exchangeCurrency: selectExchangeCurrency(state),
baseCurrency: selectBaseCurrency(state)
}), mapDispatchToProps
)(App);
However, for some reason, when I click on the button, the value is not updated accordingly to the input. The button code looks like following:
<button onClick={() => onClick("USD")}>
Change Currency Value
</button>
Have I missed out a code to dispatch this correctly? What could be the problem with it.
Here below, I attach the full duck and also the App.js for more reference.
App.js:
import React, { useEffect, useState } from "react";
import { PropTypes } from "prop-types";
import { connect } from "react-redux";
import {
selectBaseCurrency,
selectExchangeCurrency,
selectExhangeRate,
setBaseCurrency, //calling this action
// setExchangeCurrency,
// setExchangeRate,
initialState,
} from "./configureStore/duck";
const App = ({
exchangeRate,
exchangeCurrency,
baseCurrency,
onClick
}) => {
return (
<div>
<div>
<b>Exchange Rate</b>: {exchangeRate}
</div>
<div>
<b>Exchange Currency</b>: {exchangeCurrency}
</div>
<div>
<b>Base Currency</b>: {baseCurrency}
</div>
<button onClick={() => onClick("USD")}>
Change Currency Value
</button>
</div>
);
};
App.propTypes = {
exchangeRate: PropTypes.number,
exchangeCurrency: PropTypes.string,
baseCurrency: PropTypes.string,
setBaseCurrency: PropTypes.func.isRequired,
// setExchangeCurrency: PropTypes.func.isRequired,
// setExchangeRate: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired
};
App.defaultProps = {
exchangeRate: initialState.exchangeRate,
exchangeCurrency: initialState.exchangeCurrency,
baseCurrency: initialState.baseCurrency
};
const mapDispatchToProps = (dispatch,ownProps)=> ({
onClick: () => dispatch(setBaseCurrency(ownProps.baseCurrency)),
// on: setExchangeCurrency,
// setExchangeRate: setExchangeRate
});
export default connect(
state => ({
exchangeRate: selectExhangeRate(state),
exchangeCurrency: selectExchangeCurrency(state),
baseCurrency: selectBaseCurrency(state)
}), mapDispatchToProps
)(App);
duck.js
import { defineAction } from "redux-define";
import { createAction, handleActions } from "redux-actions";
export const initialState = {
exchangeRate: 3.06,
baseCurrency: "SGD",
exchangeCurrency: "MYR"
};
//Action-types
export const SET_EXCHANGE_RATE = defineAction("SET_EXCHANGE_RATE");
export const SET_BASE_CURRENCY = defineAction("SET_BASE_CURRENCY");
export const SET_EXCHANGE_CURRENCY = defineAction("SET_EXCHANGE_CURRENCY");
//Action-creators
export const setExchangeRate = createAction(
SET_EXCHANGE_RATE,
params => params
);
export const setExchangeCurrency = createAction(
SET_EXCHANGE_CURRENCY,
params => params
);
export const setBaseCurrency = createAction(
SET_BASE_CURRENCY,
params => params
);
//reducer
const reducer = handleActions(
{
[setExchangeRate]: (state, { exchangeRate }) => ({
...state,
exchangeRate
}),
[setExchangeCurrency]: (state, { exchangeCurrency }) => ({
...state,
exchangeCurrency
}),
[setBaseCurrency]: (state, { baseCurrency }) => ({
...state,
baseCurrency
})
},
initialState
);
export default reducer;
//Selector
export const selectExhangeRate = state => state.exchangeRate;
export const selectExchangeCurrency = state => state.exchangeCurrency;
export const selectBaseCurrency = state => state.baseCurrency;
Edit : As additional info, here is my sandbox link: https://codesandbox.io/s/todoapp-with-redux-and-normalized-store-jkp8z
and here is my github link:
https://github.com/sc90/test-sandbox
So there are at least two issues here, I'll try to explain them one by one, I'm not sure how these frameworks you're using interact but here are a few points that will at least fix your issue.
Your reducer is trying to extract { baseCurrency } but this is not a property of your action. You instead need to extract the payload here, like this: { payload }, this payload value will contain your baseCurrency, and to properly save it in the reducer you should return { ...state, baseCurrency: payload }
Your selectors are trying to read directly from the state variable, but this one contains your reducers under the keys you sent to combineReducers, in your case you called your reducer reducer, thus you need to select state like this state => state.reducer.baseCurrency
See my fork of your Sandbox where I've fixed the baseCurrency case for you:
https://codesandbox.io/s/todoapp-with-redux-and-normalized-store-ih79q
Related
I am trying to trying to get my textbox to update the word count when the user types something in the box. But the setWordCount action is not getting passed to the reducer. I am at a loss for why this isn't working.
In troubleshooting, I confirmed that the component is pulling the initial word count off state the way it's supposed to. I also confirmed that setWordCount is getting called when the user types something. This should trigger off the action which passes the updated word count to state, but it's not firing. I am not getting any errors in the console and none of my middleware loggers is firing.
This is my component.
import React from 'react';
import { connect } from 'react-redux';
import { setWordCount } from '../../redux/writing/writing.actions';
import { UpdateWordCount, UpdatePercentage } from '../../redux/writing/writing.utils';
import './writing-text-box.styles.scss';
const WritingTextBox = ({wordCount, goal}) => {
var updatedPercentage = UpdatePercentage(wordCount, goal);
var percent = updatedPercentage + '%';
return (
<div className='writing-box-container'>
<textarea className='writing-box' type='text' onChange={
(e) => {
setWordCount(UpdateWordCount(e.target.value));
}
}
/>
<div id='Progress'>
<div id='Bar' style={{width: percent}}></div>
</div>
<p key='writing-box-word-count' className='wordcount' >
{wordCount} / {goal}</p>
</div>
)}
const mapStateToProps = state => ({
wordCount: state.writing.wordCount,
goal: state.writing.goal,
percentage: state.writing.percentage
});
const mapDispatchToProps = dispatch => ({
setWordCount: ({wordCount}) => dispatch(setWordCount(wordCount)),
// setPercentage: percentage => dispatch(setPercentage(percentage)),
});
export default connect(mapStateToProps, mapDispatchToProps)(WritingTextBox);
This is my actions file, which is nearly copy-pasted from another app that works:
import WritingTypes from "./writing.types";
export const setWordCount = wordCount => ({
type: WritingTypes.SET_WORD_COUNT,
payload: wordCount,
});
and my reducer:
import WritingTypes from "./writing.types";
const INITIAL_STATE = {
wordCount: 0,
goal: 124,
percentage: 0
}
const writingReducer = (currentState = INITIAL_STATE, action) => {
switch(action.type) {
case WritingTypes.SET_WORD_COUNT:
return {
...currentState,
wordCount: action.payload
};
default:
return currentState;
}
}
export default writingReducer;
and my root Reducer:
import { combineReducers } from "redux";
import writingReducer from "./writing/writing.reducer";
const rootReducer = combineReducers ({
writing: writingReducer
})
export default rootReducer;
You need to be a little more careful with the namings. currently, setWordCount is the name of:
the action creator, which simply creates the action object.
export const setWordCount = wordCount => ({
type: WritingTypes.SET_WORD_COUNT,
payload: wordCount,
});
the prop that dispatches the action.
setWordCount: ({wordCount}) => dispatch(setWordCount(wordCount)),
in here it should be the second one:
<textarea className='writing-box' type='text' onChange={
(e) => {
setWordCount(UpdateWordCount(e.target.value));
}
}
/>
to make it work, destructure it from props:
const WritingTextBox = ({wordCount, goal,setWordCount}) => {
now it points to the prop, and works as expected.
I have this action:
export const setActive = (index,payload) => ({
type:actionTypes.SET_ACTIVE,
payload,
id
})
This is reducer:
case actionTypes.SET_ACTIVE:
return {
...state,
accounts:[...state.accounts,{active:action.payload,id:action.id}]
}
this is how I import it in my component:
import { connect } from "react-redux";
import { bindActionCreators } from 'redux';
import { setActive } from '../../redux/user/actions'
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(
{
setActive
},
dispatch,
),
});
const mapStateToProps = createStructuredSelector({
GetAccounts: getAccounts,
});
export default connect(mapStateToProps, null)(UserMenuAccounts);
This is how I use it in the function:
setActiveFunc = (item) => {
const {actions} = this.props
console.log("Info",item.active,item.id)
actions.setActive(!item.active,item.id)
};
But when this function is executed,I get this error TypeError: undefined is not an object (evaluating 'actions.setActive')
Any suggestions on why this is happening and how can I fix it?
You don't actually pass your mapDispatchToProps to the connect HOC.
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(
{
setActive
},
dispatch,
),
});
const mapStateToProps = createStructuredSelector({
GetAccounts: getAccounts,
});
export default connect(mapStateToProps, null)(UserMenuAccounts); // passed null!!
Solution
Pass mapDispatchToProps to connect HOC.
export default connect(mapStateToProps, mapDispatchToProps)(UserMenuAccounts);
I am trying to identify a Student or a Counselor based on the button that is clicked.
What I am trying to achieve is that, if the user clicks on the Counselor button, isCounselor will become true, if the other button is clicked, then isStudent will become true.
I have tried both onClick and onSubmit but the state remains the same.
Therefore, I believe I have done something wrong but not sure where. I have just started using react-redux and cannot get my head around it!
The reducer is included in the combineReducers and I have created the types in the code below:
reducer
import { IS_COUNSELOR, IS_STUDENT } from "../actions/types";
const initialState = {
isCounselor: false,
isStudent: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case IS_COUNSELOR:
return {
...state,
isCounselor: true,
isStudent: false,
};
case IS_STUDENT:
return {
...state,
isCounselor: false,
isStudent: true,
};
default:
return state;
}
}
action
import { IS_COUNSELOR, IS_STUDENT } from "./types";
// IS COUNSELOR
export const toCounselor = () => {
return {
type: IS_COUNSELOR,
payload: { isCounselor, isStudent },
};
};
// IS STUDENT
export const toStudent = () => {
return {
type: IS_STUDENT,
payload: { isCounselor, isStudent },
};
};
component
import React, { Component } from "react";
import { Link } from "react-router-dom";
import PropTypes from "prop-types";
import { toCounselor, toStudent } from "../../actions/clients";
export class Welcome extends Component {
static propTypes = {
isCounselor: PropTypes.bool,
isStudent: PropTypes.bool,
toCounselor: PropTypes.func.isRequired,
toStudent: PropTypes.func.isRequired,
};
onSubmitCounselor = (e) => {
e.preventDefault();
this.props.toCounselor();
};
onSubmitStudent = (e) => {
e.preventDefault();
this.props.toStudent();
};
render() {
return (
{/* some divs */}
<div className="col-md-6 mt-3">
<Link to="/register" className="nav-link">
<button
type="submit"
className="btn btn-success"
// name="isStudent"
onSubmit={this.onSubmitStudent}
>
I am a Student
</button>
</Link>
</div>
<div className="col-md-6 mt-3">
<Link to="/register" className="nav-link">
<button
type="submit"
className="btn btn-info"
// name="isCounselor"
onSubmit={this.onSubmitCounselor}
>
I am a Counselor
</button>
</Link>
</div>
{/* some other divs */}
);
}
}
export default Welcome;
Could you please advise? Thank you!
Your Welcome component does not seem to be connected to the redux state, thus it is not receiving data from the store and the actions are not dispatched in the redux loop.
The following code is not ready as is but should give you a working path
// Connect the Redux Store (state) to the component props
const mapStateToProps = (state) => ({
isCounselor: state.isCounselor,
isStudent: state.isStudent,
});
// Updated thanks to the comment of Drew Reese below
const mapDispatchToProps = { toCounselor, toStudent };
// Previous version
// const mapDispatchToProps = dispatch => bindActionCreators({ toCounselor, toStudent }, dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Welcome)
You should have a look at this section of the documentation and here about the bindActionCreators
In your action file, do this...
import { IS_COUNSELOR, IS_STUDENT } from "./types";
// IS COUNSELOR
export const toCounselor = () => ({ type: IS_COUNSELOR });
// IS STUDENT
export const toStudent = () => ({ type: IS_STUDENT });
Inside the render, pass the function to the onClick prop of the Link component
Then at the end of your component, do this...
const mapStateToProps = (state) => ({
isCounselor: state.isCounselor,
isStudent: state. isStudent,
});
const mapDispatchToProps = { toCounselor, toStudent };
export default connect(
mapStateToProps,
mapDispatchToProps
)(Welcome)
I'm a bit confused because this method of updating my store has worked for me in the past - Though the setup is a little different.
In its simplest form, my action gets called, but my dispatch seems to be getting neglected
(reduxMulti and thunk middleware are applied to my store which enables async and multiple actions to be dispatched. )
export function modalDocDeleteAction() {
// This logs as expected
console.log("MODAL/ACTIONS.JS // Document Delete Action Fired");
return dispatch => {
// We don't make it here :(
console.log("MODAL/ACTIONS.JS // Document Delete Action Fired In Return");
dispatch([{ type: MODAL_DOC_DELETE }, modalOpenAction()]);
};
}
Some random JS file:
I can successfully fire my action if I dispatch as below, so I don't think it's anything to do with my setup:
import { modalDocDeleteAction } from "../presentation/modal/actions";
import store from "../container/store";
export function someFunction() {
store.dispatch(modalDocDeleteAction());
});
From component: (I've left comments in as I think I may need to use mapStateToProps in the future)
When I try to dispatch from a component is where I seem to be having issues. I'm confused as this method has worked for me previously... But this is the first time I've tried to update my store to modify a component within another component (Hopefully that makes sense)
Imports:
import { connect } from "react-redux";
import { modalDocDeleteAction } from "./modal/actions"; // This is the correct location
Component:
const ProdRow = ({ document, index, edit, view, promoteRevert, remove }) => {
// I've removed a bunch of unnecessary stuff out of here.
return (
<TableRow>
<TableCell colSpan="2">
<ActionsIcon
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
onClick={modalDocDeleteAction} // This fires my console log (above), but never makes it to the actual dispatch
>
<path
id="cross"
d="M21,6.611,19.389,5,13,11.389,6.611,5,5,6.611,11.389,13,5,19.389,6.611,21,13,14.611,19.389,21,21,19.389,14.611,13Z"
transform="translate(-5 -5)"
fill="#3c3c3c"
/>
<title>Delete</title>
</ActionsIcon>
)}
</TableCell>
</TableRow>
);
};
Redux / Export:
// const mapStateToProps = state => ({
// modalTitle: state.modalReducer.modalTitle,
// modalText: state.modalReducer.modalText,
// closeText: state.modalReducer.closeText,
// continueText: state.modalReducer.continueText,
// completeAction: state.modalReducer.completeAction,
// visible: state.modalReducer.visible
// });
const mapDispatchToProps = dispatch => ({
modalDocDeleteAction: () => dispatch(modalDocDeleteAction())
});
export default connect(
// mapStateToProps,
null,
mapDispatchToProps
)(Results);
This is the exact setup I use to modify my modal component within itself - I think because I'm trying to update the store "externally" is where the problems are coming from. Any help would be great as I'm very stuck! Thanks!
Redux bits:
ACTIONS.js Removed excess code
import {
MODAL_OPEN,
MODAL_CLOSE,
MODAL_COMPLETE_USER_ACTION,
MODAL_DOC_DELETE,
MODAL_DOC_PUBLISH,
MODAL_DOC_REVERT,
MODAL_DOC_CREATE_EXIT,
MODAL_DOC_EDIT_EXIT
} from "./actionTypes";
import store from "../../container/store";
export function modalOpenAction() {
console.log("MODAL/ACTIONS.JS // Modal Open Action Fired");
return dispatch => {
dispatch({ type: MODAL_OPEN });
};
}
export function modalDocDeleteAction() {
console.log("MODAL/ACTIONS.JS // Document Delete Action Fired");
console.log(store.getState());
return dispatch => {
console.log(
"MODAL/ACTIONS.JS // Document Delete Action Fired In Return"
);
dispatch([{ type: MODAL_DOC_DELETE }, modalOpenAction()]);
};
}
REDUCER.js Removed excess code
const initialState = {
modalTitle: "Placeholder Title",
modalText: "Placeholder Text",
closeText: "Placeholder Close",
continueText: "Placeholder Continue",
visible: false,
completeAction: false
};
function modalReducer(state = initialState, action) {
switch (action.type) {
case "MODAL_OPEN":
return {
...state,
visible: true,
completeAction: false
};
case "MODAL_DOC_DELETE":
return {
...state,
modalTitle: "Delete Document?",
modalText:
"Deleting a document will permanently remove it from S3",
closeText: "No, Keep Document",
continueText: "Yes, Delete Document"
};
STORE.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reduxMulti from "redux-multi";
import notificationReducer from "../presentation/notification/reducer";
import modalReducer from "../presentation/modal/reducer";
const reducers = combineReducers({
notificationReducer,
modalReducer
});
const store = createStore(reducers, applyMiddleware(thunk, reduxMulti));
console.log("STORE.JS // Default State:");
console.log(store.getState());
export default store;
Working example:
Following the exact same process as above in my Modal component works as expected:
Imports:
import { connect } from "react-redux";
import Button from "../../form/button";
import { modalCloseAction, modalCompleteUserAction } from "./actions";
Component (Styled Components):
function Component({ ...props }) {
const {
modalTitle,
modalText,
closeText,
continueText,
visible,
modalCloseAction,
modalCompleteUserAction
} = props;
console.log("MODAL.JSX // Modal State:");
console.log(props);
return (
<div>
<ModalContainer visible={visible}>
<Overlay visible={visible} />
<Modal visible={visible}>
<ModalTitle>{modalTitle}</ModalTitle>
<ModalText>{modalText}</ModalText>
<ModalButtons>
<ModalButton
buttonStyle="Back"
onClick={modalCloseAction}
>
{closeText}
</ModalButton>
<ModalButton
buttonStyle="Primary"
onClick={modalCompleteUserAction}
>
{continueText}
</ModalButton>
</ModalButtons>
</Modal>
</ModalContainer>
</div>
);
}
Redux/Export
const mapStateToProps = state => ({
modalTitle: state.modalReducer.modalTitle,
modalText: state.modalReducer.modalText,
closeText: state.modalReducer.closeText,
continueText: state.modalReducer.continueText,
completeAction: state.modalReducer.completeAction,
visible: state.modalReducer.visible
});
const mapDispatchToProps = dispatch => ({
modalCloseAction: () => dispatch(modalCloseAction()),
modalCompleteUserAction: () => dispatch(modalCompleteUserAction())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Component);
Dispatch Actions:
export function modalCloseAction() {
return dispatch => {
dispatch({ type: MODAL_CLOSE });
};
}
export function modalCompleteUserAction() {
return dispatch => {
dispatch([{ type: MODAL_COMPLETE_USER_ACTION }, modalCloseAction()]);
};
}
I am trying to learn the react and for that I am trying to create a sample todo app. I have a python flask backend which servers as REST server and react as web server.
Everything works find an I am able to show todos and delete particular todo as well. However now I have started learning Redux, and that seems really confusing.
I am not sure how to make call to my rest server. Following just returns promise, not sure how to get the data, rather than promise.
store.js
import axios from 'axios'
import { createStore } from 'redux'
export const ADD_TODO = 'ADD_TODO'
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const listTodo = todos => ({
type: 'LIST_TODO',
todos
})
const add_todo = (id, text) => {
return axios.post("http://localhost:5001/todos", {id:id, data:text})
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(listTodo(Response.data))
})
}
const initialState ={
todos: {},
new_todo: ''
}
function todoApp(state = initialState, action) {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
new_todo: action.text
})
default:
return state
}
}
const store = createStore(todoApp)
export default store
app.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo('testing')}>fetch_Data</button>
</div>
);
}
}
export default connect() (App)
index.js
ReactDOM.render(<Provider store={store}> <App /> </Provider>,
document.getElementById('root'));
Firstly, you should export the actions you have created which will then be imported and used in the components using the connect HOC.
You can dispatch the 'fetch_data' action to get the data in your component. Also, you can dispatch 'addTodo' action to add new todo in the list.
export const ADD_TODO = 'ADD_TODO';
export const GET_TODO = 'GET_TODO';
export const fetch_data = () => {
return (dispatch) => axios.get("http://localhost:5001/todos")
.then(response => {
dispatch({type: GET_TODO, todos: response.data});
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
});
Use the actions constants like ADD_TODO, GET_TODO to save or to update the redux state in reducers
const todoApp = (state = initialState, action) => {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
const todos = {...state.todos};
todos[action.id] = action.text;
return Object.assign({}, state, {
todos: todos
});
case GET_TODO:
return Object.assign({}, state, {
todos: action.todos
});
default:
return state
}
}
Importing the actions and then call the function you have added in the 'mapDispatchToProps' to dispatch the actions.
import React, {Component} from 'react'
import {connect} from 'react-redux';
import { addTodo, fetch_data } from "../store";
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo(todoId, 'testing')}>fetch_Data</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
todos: state.todoApp.todos
});
const mapDispatchToProps = (dispatch) => ({
addTodo: (id, text) => dispatch(addTodo(id, text)),
fetch_data: () => dispatch(fetch_data())
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
redux is based on actions and reducers, basically reducers are pure functions which means no side effects as for example api calls, I'd advice you read more about redux and how to use redux with redux-chunk for making api calls
You make this work like this. You need to dispatch action when you have response.
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
})