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.
Related
I'm doing a small project with Redux and I'm running into the issue of an undefined action.type and I can't seem to find out what is causing it. I already cut out a lot of code to simply try to dispatch the 'LIKE' action.
My reducer looks like this:
import blogService from '../services/blogs'
const blogReducer = (state = [], action) => {
switch (action.type) {
case 'LIKE': {
// const newState = state.filter(x => x)
// const likedBlogIndex = state.findIndex(x => x.id === action.data.id)
// newState[likedBlogIndex] = { ...state[likedBlogIndex], likes: newState[likedBlogIndex].likes + 1 }
return action.type
}
case 'INIT':
return action.data
default: return state
}
}
export const initBlogs = () => {
return async dispatch => {
const blogs = await blogService.getAll()
dispatch({
type: 'INIT',
data: blogs.sort((a, b) => (b.likes - a.likes))
})
}
}
export const likeBlog = () => {
return async dispatch => {
// const likedBlog = await blogService.update(blog)
dispatch({
type: 'LIKE',
// data: { id: likedBlog.id, votes: likedBlog.votes }
data: ''
})
}
}
export default blogReducer
And the component from where the function is called that dispatches the action looks like this:
/* eslint-disable linebreak-style */
import React from 'react'
import Togglable from './Togglable'
import { useSelector, useDispatch } from 'react-redux'
import likeBlog from '../reducers/blogReducer'
const BlogList = ({ handleDelete }) => {
const blogs = useSelector(state => state.blogs)
const dispatch = useDispatch()
return(
blogs.map(blog =>
<div key={blog.id}>
<div className='blog'>
{blog.title} {blog.author}
</div>
<Togglable showLabel='show' hideLabel='hide'>
<p>
<b>url:</b> {blog.url}
</p>
<p>
<b>likes:</b> {blog.likes} <button onClick={() => dispatch(likeBlog())}>Like</button>
</p>
<p>
<b>id:</b> {blog.id}
</p>
<button onClick={(e) => handleDelete(e, blog)}>remove</button>
</Togglable>
</div>
)
)
}
export default BlogList
For the record: the error I'm facing is that action.type at line 4 of my reducer is undefined. I really hope someone spots the mistake. Thanks.
In this line you take the default export from the blogReducer file and name it likeBlog
import likeBlog from '../reducers/blogReducer'
But the default export is the reducer, so you are renaming the reducer and then using it as an action creator.
What you really want is import the named export from that file, like
import { likeBlog } from '../reducers/blogReducer'
that should work.
PSA: you are writing a pretty outdated style of redux here, it is very possible that you are following an outdated tutorial and will learn a style of redux that required multiple times the amount of code modern redux would requre. Please follow the official redux tutorials found at https://redux.js.org/tutorials/index
New to React. I am trying out react redux for the first time (on my own). I have a state for a gameboard called force_hidden that I want to set in App.js and then use in a child component ( a few levels down). I used redux to create forceGameBoardHidden that should set force_hidden to whatever value is inside the (). so, forceGameBoardHidden(true) should set the state of force_hidden to true. However, that doesn't happen. I can click on the item and it logs "before change" and then the state. In between it should have set the state to true, but the state is still false. I don't know what's going wrong here. I tried console.logging the gameBoardReducer. It fires when I start the page, but doesn't fire when I click the button.
gameboard.types.js
const GameBoardActionTypes = {
FORCE_GAMEBOARD_HIDDEN: 'FORCE_GAMEBOARD_HIDDEN'
}
export default GameBoardActionTypes;
gameboard.action.js
import GameBoardActionTypes from './game-board.types';
export const forceGameBoardHidden = value => ({
type: GameBoardActionTypes.FORCE_GAMEBOARD_HIDDEN,
payload: value
});
gameboard.reducer.js
import GameBoardActionTypes from './game-board.types'
const INITIAL_STATE = {
force_hidden: false
}
const gameBoardReducer = ( state = INITIAL_STATE, action) => {
switch (action.type) {
case GameBoardActionTypes.FORCE_GAMEBOARD_HIDDEN:
return {
...state,
force_hidden: action.payload
}
default:
return state;
}
}
export default gameBoardReducer;
root-reducer
import { combineReducers } from 'redux';
import gameBoardReducer from './game-board/game-board.reducer'
export default combineReducers ({
gameboard: gameBoardReducer
})
store.js
const middlewares = [];
const store = createStore(rootReducer, applyMiddleware(...middlewares))
export default store;
index.js
<Provider store={store}>
App.js -- this is where the magic should happen in forceGameBoardHidden
const App = () => {
const handleKeyChange = event => {
setKey(event.target.value);
console.log("before change")
forceGameBoardHidden(true)
console.log(store.getState().gameboard)
}
return (
<SearchBox
onChange={handleKeyChange}
placeholder="Enter your game Key"/>
</div>
);
}
const mapDispatchToProps = dispatch => ({
forceGameBoardHidden: item => dispatch(forceGameBoardHidden(item))
})
export default connect(null,mapDispatchToProps)(App);
I think you need to dispatch the action, there are 2 methods , one is to connect the component to the actions and bind them to dispatch. The other one is much easier since you use functional components, is by using the useDispatch hook
Example here:
import { useDispatch } from 'react-redux' // <-- add this
const App = () => {
const dispatch = useDispatch() // <-- add this
const handleKeyChange = event => {
setKey(event.target.value);
console.log("before change")
dispatch(forceGameBoardHidden(true)) // <-- change this
console.log(store.getState().gameboard)
}
return (
<SearchBox
onChange={handleKeyChange}
placeholder="Enter your game Key"/>
</div>
);
}
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
import React from 'react';
import './search.styles.scss';
import { connect } from 'react-redux';
import { setSearchField } from '../../redux/search/search.actions';
import { combineReducers } from 'redux';
class Search extends React.Component{
render(){
const { onSetSearchField, search } = this.props;
return (
<div className="search-container">
<p>user: {search}</p>
<input className="search-box" type="text" onChange={onSetSearchField} />
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSetSearchField: (event) => dispatch(setSearchField(event.target.value))
}
}
const mapStateToProps = (state) => {
console.log(state.search, "-----------")
if(state.search === "user:"){
alert(state.search)
}
return {
search: state.search
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Search);
reducer:
const initialStateSearch = {
search: null
}
export const getSearchType = (state=initialStateSearch, action={}) => {
switch (action.payload) {
case 'user':
return Object.assign({}, state, {search: action.payload})
case 'post':
return Object.assign({}, state, {search: action.payload})
default:
return state
}
}
action:
import { SearchActionTypes } from './search.types';
export const setSearchField = (text) => ({
type: SearchActionTypes.SEARCH_START,
payload: text
})
Here I'm adding search functionality using react and redux
But when I'm checking my payload in the reducer. It is coming but it is not coming in my search component.
I am alerting and trying to alert and display the text on a p tag
Please have a look
As I mentioned in a comment to your question, the code you showed for your component and redux looks fine, and the fact that you can see your redux store being populated makes me believe the problem lies with the configuration.
Have you tried making sure the Provider is a wrapper at the root level, so the components will know about the redux store https://react-redux.js.org/api/provider? If that exists, then I would advise looking at the redux example https://codesandbox.io/s/9on71rvnyo online and try to follow there logic and see what potentially you could be missing.
Can you confirm if you have used the same constant in Action Creator setSearchField for which you are checking in reducer. Is SearchActionTypes.SEARCH_START === 'user' or 'post'.
I get my action called in Redux Dev Tools and even the new state, but in the actual Component props is undefined.
The component:
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { getPromos } from '../../actions/promo';
import PropTypes from 'prop-types';
const Landing = ({ getPromos, data }) => {
useEffect(() => {
getPromos();
console.log(data) // ==>> "UNDEFINED"
}, []);
return (
<div>
<section className='landing'>
<div className='dark-overlay'>
<div className='landing-inner'>
<h1 className='x-large'> Developer Connector </h1>
<p className='lead'>
Create a developer profile/portfolio, share posts and get help
from other developers
</p>
<div className='buttons'>
<Link to='/register' className='btn btn-primary'>
Sign Up
</Link>
<Link to='/login' className='btn btn-light'>
Login
</Link>
</div>
</div>
</div>
</section>
</div>
);
};
Landing.propTypes = {
getPromos: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
data: state.data
});
export default connect(
mapStateToProps,
{ getPromos }
)(Landing);
Actions:
import axios from 'axios';
import { setAlert } from './alert';
import { GET_PROMOS, REGISTER_FAIL } from './types';
export const getPromos = () => async dispatch => {
try {
const res = await axios.get('/api/promo');
dispatch({
type: GET_PROMOS,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({ type: REGISTER_FAIL });
}
};
And reducer:
import { GET_PROMOS } from '../actions/types';
const initialState = {
data: null,
title: ''
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PROMOS:
return { ...state, data: payload };
default:
return state;
}
}
Like I said, in Redux Dev Tools I get my desired output. But for some reason I cant get to echo this state in the component. What im getting wrong? Can it be something about the hooks?
Thanks !
First thing that jumps at me is that you have a naming conflict with the getPromos in your component, it's defined in the imports as getPromos then it's destructured in the component as { getPromos } as well. I'm surprised you didn't get an error there for naming conflicts.
You will want to NOT destructure getPromos in the component and instead call it as (props) => { props.getPromos } to actually call the connected action creator instead of the unconnected one.
Second, Is that reducer the main root reducer? or is it nested in the root reducer? if the latter is true then in your mapStateToProps the data prop should be one level deeper, as in state: state.rootLevelState.data
(sorry can't ask questions in the comments due to reputation < 50)
enter image description here
Here's a screenshot of the redux dev tools