So i've forgot to dispatch my action and just called the function directly and i've noticed that it actually works and i have no idea why.
Can anybody explain to me why/how it works ?
// actions
export const resetSearchBar = () => ({
type: types.RESET_SEARCHBAR,
});
// Component
fetchProducts = () => {
const { productName } = this.state;
const { fetchProductsByName, resetSearchBar } = this.props;
if (productName) {
fetchProductsByName(productName);
return;
}
resetSearchBar(); <-- no dispatch ?
}
const mapDispatchToProps = {
fetchProductsByName,
resetSearchBar,
}
export default connect(null, mapDispatchToProps)(SearchBar);
Related
Very new to React and redux. I'm trying to add a file drag and drop feature to my React app but struggling with how that works using redux
toolkit's createSlice function.
I keep getting 'TypeError: Cannot read property 'fileList' of undefined'. I think it's because I've messed up with the addFilesToList reducer in searchSlice.js below.
Any help would be massively appreciated. Please see my code below:
searchSlice.js
import { createSlice } from '#reduxjs/toolkit'
export const searchSlice = createSlice({
name:'search',
initialState: {
fileList: [],
inDropZone: false,
},
reducers: {
addToDropZone: (state) => {
return state.inDropZone = true
}
,
addFilesToList: (state, e) => {
return state.fileList = [...e.dataTransfer.files]
}
}
})
export const { addToDropZone, addFilesToList } = searchSlice.actions
export default searchSlice.reducer
DragDrop.js
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { addToDropZone, addFilesToList } from './searchSlice.js'
import DragArea from './DragArea.js'
import TableArea from './TableArea.js'
//import img from './components/images/drag.PNG'//
const DragDrop = (props) => {
const files = useSelector((state) => state.searchSlice.fileList)
const inDropZone = useSelector((state) => state.searchSlice.inDropZone)
const dispatch = useDispatch()
//const { data, dispatch } = props//
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
dispatch(addToDropZone());
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = 'copy';
dispatch(addToDropZone());
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
dispatch(addFilesToList());
dispatch(addToDropZone());
if (files && files.length > 0) {
const existingFiles = files.map(f => f.name)
files = files.filter(f => !existingFiles.includes(f.name))
}
};
if (inDropZone === true && files.length === 0) {
return(
<div className = "drag-and-drop"
onDrop={handleDrop}
onDragOver= {handleDragOver}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}>
<DragArea />
</div>
)
} else {
return(
<div className = "table-area"
onDrop={handleDrop}
onDragOver= {handleDragOver}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}>
<TableArea data = {files} />
</div>
)
}
}
export default DragDrop;
The type of e in your addFilesToList reducer is a PayloadAction. This means its shape is like:
{
type: “search/addFilesToList”,
payload: valueYouPassToActionCreator
}
The valueYouPassToActionCreator is whatever you pass into the addFilesToList action creator when you dispatch it, e.g.:
dispatch(addFilesToList(valueYouPassToActionCreator))
You, however, are doing:
dispatch(addFilesToList())
Which means your the .payload property on your PayloadAction is undefined. In short, you need to pass something into the action creator.
I am migrating my component from a class component to a functional component using hooks. I need to access the states with useSelector by triggering an action when the state mounts. Below is what I have thus far. What am I doing wrong? Also when I log users to the console I get the whole initial state ie { isUpdated: false, users: {}}; instead of just users
reducers.js
const initialState = {
isUpdated: false,
users: {},
};
const generateUsersObject = array => array.reduce((obj, item) => {
const { id } = item;
obj[id] = item;
return obj;
}, {});
export default (state = { ...initialState }, action) => {
switch (action.type) {
case UPDATE_USERS_LIST: {
return {
...state,
users: generateUsersObject(dataSource),
};
}
//...
default:
return state;
}
};
action.js
export const updateUsersList = () => ({
type: UPDATE_USERS_LIST,
});
the component hooks I am using
const users = useSelector(state => state.users);
const isUpdated = useSelector(state => state.isUpdated);
const dispatch = useDispatch();
useEffect(() => {
const { updateUsersList } = actions;
dispatch(updateUsersList());
}, []);
first, it will be easier to help if the index/store etc will be copied as well. (did u used thunk?)
second, your action miss "dispatch" magic word -
export const updateUsersList = () =>
return (dispatch, getState) => dispatch({
type: UPDATE_USERS_LIST
});
it is highly suggested to wrap this code with { try } syntax and be able to catch an error if happened
third, and it might help with the console.log(users) error -
there is no need in { ... } at the reducer,
state = intialState
should be enough. this line it is just for the first run of the store.
and I don't understand where { dataSource } comes from.
I'm not sure why I'm forced to do a check if actions exists in my reducer. Could it be because we are using async await in our actions / API methods?
Reducer
export const partyReducer = (state = initState, action) => {
if (action) { // <-- should not need this
switch (action.type) {
case Actions.SET_ROLES: {
const roles = formatRoles(action.roles);
return {
...state,
roles
};
}
default:
return state;
}
}
return state;
};
export default partyReducer;
Actions
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Component that dispatches the action:
componentDidMount() {
this.props.fetchRoles();
this.onSubmit = this.onSubmit.bind(this);
}
...
export const mapDispatchToProps = dispatch => {
return {
fetchRoles: () => {
dispatch(fetchRoles());
}
};
};
The Store
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import {reducer as formReducer} from 'redux-form';
// Reducers
import partyReducer from '../reducers/party-reducer';
export default function configureStore(initialState) {
let reducer = combineReducers({
form: formReducer,
party: partyReducer
});
let enhancements = [applyMiddleware(thunk)];
if (process.env.PROD_ENV !== 'production' && typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
enhancements.push(window.__REDUX_DEVTOOLS_EXTENSION__());
}
return createStore(reducer, initialState, compose(...enhancements));
}
What I've tried
I noticed my mapDispatchToProps was written kinda strange so I fixed that, but I still get the error actions is undefined if I remove the if statement :'(
import {fetchRoles as fetchRolesAction} from '../../../actions/party-actions';
...
export const mapDispatchToProps = dispatch => ({
fetchRoles: () => dispatch(fetchRolesAction())
});
Figured it out! Was my test!
it('returns expected initState', () => {
let expected = {roles: []};
let actual = partyReducer();
expect(actual).toEqual(expected);
});
^ test above is suppose to see if the initial state is return if no state is passed in. However Actions should Always be passed in.
Fix:
it('returns expected initState', () => {
let expected = {roles: []};
let actual = partyReducer(undefined, {}); // <-- undefined state, + action
expect(actual).toEqual(expected);
});
I have a Connector, which has mapDispatchToProps and mapStateToProps functions, and I need to dispatch a action from my main component.
I'm getting an error saying dispatch is not defined when I'm trying to dispatch fetchPlaces(this.props.user.id)
this.props.user.id has value 1.
I need to get the user id and pass it to fetchPlaces, which intern gets me the places of the user. I'm not sure how to do it.
Connector
const mapStateToProps = function (store) {
return {
elements: store.elements.elements,
places: store.places.places,
geocode : store.geocode.geocode,
user : store.user.user
};
};
const mapDispatchToProps = function (dispatch) {
return {
userAction : dispatch(fetchUser()),
elementsAction : dispatch(fetchCategories()),
placesAction: (id) => { dispatch(fetchPlaces(id)) }
}
}
class BasicinfoConnector extends React.Component{
render() {
console.log(this.props.user);
if(typeof this.props.user.id != "undefined"){
return (
<Basicinfo elements={this.props.elements} places={this.props.places} geocode={this.props.geocode} user={this.props.user}/>
);
}
else{
return null;
}
}
}
export default Redux.connect(mapStateToProps, mapDispatchToProps)(BasicinfoConnector);
Component :
componentWillMount() {
console.log(this.props);
console.log(this.props.user.id);
dispatch(fetchPlaces(this.props.user.id))
}
Is placesAction: (id) => { dispatch(fetchPlaces(id)) } the right syntax of doing it?
UPDATE
I changed componentWillMount :
componentWillMount() {
console.log(this.props);
console.log(this.props.user.id);
dispatch(this.props.placesAction(this.props.user.id))
}
and mapDispatchToProps :
const mapDispatchToProps = function (dispatch) {
return {
userAction: bindActionCreators(fetchUser, dispatch),
elementsAction: bindActionCreators(fetchUser, dispatch),
placesAction: (id) => { dispatch(fetchPlaces(id)) }
}
}
Still have the same error.
You need to pass the property down to the next level, either by sharing all your props like this:
<Basicinfo {...this.props} />
or only the particular ones that you want
<Basicinfo placesAction={(id) => this.props.placesAction(id)} />
I have several routes that use the same controller:
<Route component={Search} path='/accommodation(/:state)(/:region)(/:area)' />
and when the route is changed I call the api function from within the component:
componentWillReceiveProps = (nextProps) => {
if (this.props.params != nextProps.params) {
loadSearch(nextProps.params);
}
}
which is an action as follows:
export function loadSearch (params) {
return (dispatch) => {
return dispatch(
loadDestination(params)
).then(() => {
return dispatch(
loadProperties(params)
);
});
};
}
which loads:
export const DESTINATION_REQUEST = 'DESTINATION_REQUEST';
export const DESTINATION_SUCCESS = 'DESTINATION_SUCCESS';
export const DESTINATION_FAILURE = 'DESTINATION_FAILURE';
export function loadDestination (params) {
const state = params.state ? `/${params.state}` : '';
const region = params.region ? `/${params.region}` : '';
const area = params.area ? `/${params.area}` : '';
return (dispatch) => {
return api('location', {url: `/accommodation${state}${region}${area}`}).then((response) => {
const destination = formatDestinationData(response);
dispatch({
type: DESTINATION_SUCCESS,
destination
});
});
};
}
export const PROPERTIES_REQUEST = 'PROPERTIES_REQUEST';
export const PROPERTIES_SUCCESS = 'PROPERTIES_SUCCESS';
export const PROPERTIES_FAILURE = 'PROPERTIES_FAILURE';
export function loadProperties (params, query, rows = 24) {
return (dispatch, getState) => {
const locationId = getState().destination.id || 99996;
return api('search', {locationId, rows}).then((response) => {
const properties = response.results.map(formatPropertiesData);
dispatch({
type: PROPERTIES_SUCCESS,
properties
});
});
};
}
On initial page load this works and returns data from an api and renders the content. However on changing the route, the loadSearch function is fired but the dispatch (which returns the actual data) doesn't.
Please change your code to this. You missed a dispatch.
Assumption : You are using redux-thunk, and the component has access to dispatch via props (connected). Since you mentioned that you are dispatching on page load, I think this is the case.
componentWillReceiveProps = (nextProps) => {
const {dispatch} = this.props;
if (this.props.params != nextProps.params) {
nextProps.dispatch(loadSearch(nextProps.params));
}
}