I am aware there are a lot of threads referring to this error message but I cannot find one that explains why I am getting this error.
While I am relatively new to React and Redux, I think I understand the concept of Promises and asynch functions but I have to be missing something here. So I have my index.js Modal container, Modal component and a modal reducer.
index.js: -
import React from 'react'
import ReactDOM from 'react-dom'
import routes from './config/routes'
import {createStore, applyMiddleware, compose, combineReducers} from 'redux'
import {Provider} from 'react-redux'
import * as reducers from '_redux/modules/'
import thunk from 'redux-thunk'
import { checkIfAuthed } from '_helpers/auth'
const store = createStore(
combineReducers(reducers),
compose(applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
))
// CSS
import "_css/main.scss";
ReactDOM.render(
<Provider store={store}>{routes}</Provider>,
document.getElementById('app'))
ModalContainer.js: -
import { Modal } from '_components'
import { bindActionCreators } from 'redux'
import * as modalActionCreators from '_redux/modules/Modal/Modal'
import { connect } from 'react-redux'
const mapStateToProps = ({users, modal}) => {
const duckTextLength = modal.duckText.length
return {
user: users[users.authedId] ? users[users.authedId].info : {},
duckText: modal.duckText,
isOpen: modal.isOpen,
isSubmitDisabled: duckTextLength <= 0 || duckTextLength > 140,
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(modalActionCreators, dispatch)
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Modal)
Modal.js
import React from 'react'
import PropTypes from 'prop-types';
import { default as ReactModal } from 'react-modal'
const modalStyle = {
content: {
width: 350,
margin: '0px auto',
height: 220,
borderRadius: 5,
background: '#EBEBEB',
padding: 0,
}
}
const Modal = (props) => {
const submitDuck = () => {
console.log('Duck', props.duckText)
console.log('user', props.user)
}
return(
<span className='darkBtn' onClick={props.openModal}>
{'Duck'}
</span>
)
}
Modal.PropTypes = {
duckText: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired,
isSubmitDisabled: PropTypes.bool.isRequired,
openModal: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired,
updateDuckText: PropTypes.func.isRequired,
}
export default Modal
modal reducer: -
const OPEN_MODAL = 'OPEN_MODAL'
const CLOSE_MODAL = 'CLOSE_MODAL'
const UPDATE_DUCK_TEXT = 'UPDATE_DUCK_TEXT'
export const openModal = () => {
return
{
type: OPEN_MODAL
}
}
export const closeModal = () => {
return
{
type: CLOSE_MODAL
}
}
export const newDuckText = () => {
return
{
type: UPDATE_DUCK_TEXT,
newDuckText
}
}
const initialState = {
duckText: '',
isOpen: false,
}
export const modal = (state = initialState, action) => {
switch (action.type) {
case OPEN_MODAL :
return {
...state,
isOpen: true,
}
case CLOSE_MODAL :
return {
duckText: '',
isOpen: false,
}
case UPDATE_DUCK_TEXT :
return {
...state,
duckText: action.newDuckText,
}
default :
return state
}
}
The problem arises from clicking on: -
<span className='darkBtn' onClick={props.openModal}>
It successfully invokes the reducer action function but also gives me 'Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.' error. I don't understand this because
1) I am using Thunk
2) As this reducer action does not return a promise it is therefore not an asynch function?
I would really appreciate help in resolving this. I've been trying to solve this for a couple hours now and I feel like my eyes are going to start bleeding soon.
It's a quirk in JavaScript. The value that you are going to return should be on the same line with the return keyword.
instead of:
// (this will return `undefined`)
export const openModal = () => {
return
{
type: OPEN_MODAL
}
}
You should write:
//(this will return the action object)
export const openModal = () => {
return {
type: OPEN_MODAL
};
}
Related
I had faced with a problem. The problem: I had tried to use context inside a middleware, but i`dont know how i can do it, because we can use useContext only in... 'Hooks can only be called inside of the body of a function component. Is it possible to use context inside the middleware? Thx for help!
'I have a context:
import { createContext, useState } from "react";
export const PopupContext = createContext();
export const PopupContextProvider = (props) => {
const [isShow, setIsShow] = useState(false);
return (<PopupContext.Provider
value={[isShow, setIsShow]}
>
{props.children}
</PopupContext.Provider>
)
}
my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './redux/store';
import { PopupContextProvider } from './context/popup/popup';
ReactDOM.render(
<React.StrictMode>
<PopupContextProvider>
<Provider store={store}>
<App />
</Provider>
</PopupContextProvider>
</React.StrictMode>,
document.getElementById('root')
);
and my store.js - it`s a Redux store, you know...
import { configureStore } from '#reduxjs/toolkit';
import alertMiddleware from '../middleware/alert.middleware';
import authReducer from './features/auth/auth-slice';
import cardReducer from './features/card/cardSlice';
const store = configureStore({
reducer: {
auth: authReducer,
card: cardReducer
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(alertMiddleware)
})
export default store;
and my dumb middleware
import { PopupContext } from '../context/popup/popup';
import { useContext, useState } from 'react';
const alertMiddleware = store => next => action => {
const isShow = useContext(PopupContext);
if (action.type === 'auth/login/rejected') {
console.log(isShow);
}
console.log('middleware');
next(action)
}
export default alertMiddleware;
It's not. Hooks cant be used outside of components. You should send isShow as an action payload when you dispatch the action. Then you would have something like in your middleware
...
if (action.type === 'auth/login/rejected') {
console.log(action.payload.isShow);
}
...
Well, like i had understood, it's imposible to use context in your middleware (so sad). In this way, i had created a slice with reducers and etc. And now, a can take out all my logic in reducers, if someone doesn't know what is it... well, it's like a global state with services, which are available from all points in your application. The logic is: when some action type happen, the middleware handles it and dispatches some actions, in this action you can do everething, in my case i change the state and read this state from my functional component. I belive, what i had done a good explain.
Sequence of actions: some action => middleware => process action in reducer => change state
My middleware:
import { showPopup } from '../features/popup/popup-slice';
const POPUP_PROPERTIES = {
loginRejected: {
message: "LOGIN ERROR",
styles: {
color: "white",
backgroundColor: "red"
}
},
invalidateLoggedInUser: {
message: "You are logged out from your account",
styles: {
color: "white",
backgroundColor: "#4BE066"
}
},
cardCreateFulfilled: {
message: "Card set successfully created",
styles: {
color: "white",
backgroundColor: "#01C9F7"
}
},
cardDeleteFulfilled: {
message: "Card set successfully deleted",
styles: {
color: "white",
backgroundColor: "#4BE066"
}
}
}
const PopupMiddleware = ({ dispatch, getState }) => next => action => {
const { type } = action;
switch (type) {
case 'auth/login/rejected': {
dispatch(showPopup(POPUP_PROPERTIES.loginRejected));
break;
}
case 'auth/invalidateLoggedInUser': {
dispatch(showPopup(POPUP_PROPERTIES.invalidateLoggedInUser));
break;
}
case 'card/create/fulfilled': {
dispatch(showPopup(POPUP_PROPERTIES.cardCreateFulfilled));
break;
}
case 'card/delete/fulfilled': {
dispatch(showPopup(POPUP_PROPERTIES.cardDeleteFulfilled));
break;
}
default: break;
}
next(action);
}
export default PopupMiddleware;
My slice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
// -------------------------------------- Slice --------------------------------------
const initialState = {
popupEntity: {
message: null,
color: null,
},
isVisible: false
}
const popupSlice = createSlice({
name: 'popup',
initialState,
reducers: {
showPopup: {
reducer(state, action) {
state.popupEntity = action.payload;
state.isVisible = true;
}
},
hidePopup: {
reducer(state) {
state.isVisible = false;
}
}
},
})
export const { showPopup, hidePopup } = popupSlice.actions;
export default popupSlice.reducer;
// -------------------------------------- Selectors --------------------------------------
export const popupStateSelector = state => state.popup;
export const isVisibleSelector = state => state.popup.isVisible;
and store:
import { configureStore } from '#reduxjs/toolkit';
import PopupMiddleware from './middleware/popup.middleware';
import authReducer from './features/auth/auth-slice';
import cardReducer from './features/card/cardSlice';
import popupReducer from './features/popup/popup-slice';
const store = configureStore({
reducer: {
auth: authReducer,
card: cardReducer,
popup: popupReducer
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(PopupMiddleware)
})
export default store;
How can I add a charging indicator based on the Redux status?
I need to place a loading screen while sending the data.
charging indicator component
import React from 'react';
import { StyleSheet } from 'react-native';
import AnimatedLoader from "react-native-animated-loader";
import {connect} from 'react-redux'
class Loader extends React.Component {
constructor(props) {
super(props);
this.state = {visible: false };
}
// componentDidMount() {
// setInterval(() => {
// this.setState({
// visible: !this.state.visible
// });
// }, 2000);
// }
render() {
const { visible } = this.props;
if (!visible) return outVisible();
return (
<AnimatedLoader
visible={visible}
overlayColor="rgba(255,255,255,0.75)"
source={require("./loader.json")}
animationStyle={styles.lottie}
speed={1}
>
<Text>Carregando...</Text>
</AnimatedLoader>
);
}
}
const styles = StyleSheet.create({
lottie: {
width: 200,
height: 200
}
});
const mapStateToProps = (state) => ({visible: state.visible});
const mapDispatchToProps = dispatch => {
return {outVisible: () => dispatch(setVisible({visible: false}))}
}
export default connect(mapStateToProps,mapDispatchToProps)(Loader)
Action
import { SET_VISIBLE } from './actionsTypes'
export const setVisible = visible => {
return {
type: SET_VISIBLE,
payload: visible
}
}
Reducer
import { SET_VISIBLE } from '../actions/actionsTypes'
const initialState = {
visible: false
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case SET_VISIBLE:
return {
...state,
visible: action.payload.visible
};
default:
return state;
}
}
export default reducer
Store Config
import {
createStore,
combineReducers,
compose,
applyMiddleware
} from 'redux'
import thunk from 'redux-thunk'
import postReducer from './reducers/post'
import userReducer from './reducers/user'
import messageReducer from './reducers/message'
import loadingReducer from './reducers/loading'
const reducers = combineReducers({
user: userReducer,
post: postReducer,
message: messageReducer,
visible: loadingReducer
})
const storeConfig = () => {
return createStore(reducers, compose(applyMiddleware(thunk)))
}
export default storeConfig
actions types
export const USER_LOGGED_IN = 'USER_LOGGED_IN'
export const USER_LOGGED_OUT = 'USER_LOGGED_OUT'
export const SET_MESSAGE = 'SET_MESSAGE'
export const LOADING_USER = 'LOADING_USER'
export const USER_LOADED = 'USER_LOADED'
export const CREATING_POST = 'CREATING_POST'
export const POST_CREATED = 'POST_CREATED'
export const SET_POSTS = 'SET_POSTS'
export const SET_VISIBLE = 'SET_VISIBLE'
app.js
import React, { Component } from 'react'
import { Alert } from 'react-native'
import { connect } from 'react-redux'
import Routes from "./routes";
import { setMessage } from './store/actions/message'
class App extends Component {
componentDidUpdate = () => {
if(this.props.text && this.props.text.toString().trim())
{
Alert.alert(this.props.title || 'Mensagem',this.props.text.toString())
this.props.clearMessage()
}
}
render() {
return (
<Routes />
)
}
}
const mapStateToProps = ({ message}) => {
return {
title: message.title,
text: message.text,
}
}
const mapDispatchToProps = dispatch => {
return {
clearMessage: () => dispatch(setMessage({ title: '', text: '' }))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Right after the data is sent to the API, I need to return a load indicator to the user, until that data is stored.
I am pretty new to Redux and the whole Redux-Saga thing and wanted to use React-Boilerplate to try a small project that basically just makes an API call and iterates over the data. And I currently have a problem I've been stuck at for hours. Maybe you have an idea?
My React Component looks like this:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import { useInjectSaga } from 'utils/injectSaga';
import { useInjectReducer } from 'utils/injectReducer';
import {
makeSelectDevices,
makeSelectLoading,
makeSelectError
} from './selectors';
import reducer from './reducer';
import { fetchDevices } from './actions';
import saga from './saga';
export function LeafletMap(props) {
const {devices, loading, error, fetchDevices } = props;
useInjectReducer({ key: 'leafletMap', reducer });
useInjectSaga({ key: 'leafletMap', saga });
useEffect(() => {
fetchDevices();
}, [fetchDevices]);
if (loading) return(<div>Loading...</div>)
return (
<div>
{ !error ?
<Map center={[47.3, 9.9]} zoom={9} style={{height: '500px'}}>
<TileLayer
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
attribution='© OpenStreetMap contributors'
/>
{ devices && devices.map((device)=> {
let coordinates = [device.latitude, device.longitude];
return (
<Marker key={device.id} position={coordinates}></Marker>
);
})}
</Map>
: ''
}
</div>
);
};
LeafletMap.propTypes = {
devices: PropTypes.array,
loading: PropTypes.bool,
error: PropTypes.any,
};
const mapStateToProps = createStructuredSelector({
devices: makeSelectDevices(),
loading: makeSelectLoading(),
error: makeSelectError(),
});
function mapDispatchToProps(dispatch) {
return {
fetchDevices: () => dispatch(fetchDevices())
};
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
export default compose(withConnect)(LeafletMap);
When my component mounts I use the useEffect Hook to dispatch an action that I bound to my props using mapDispatchToProps. The actions file looks like this:
import {
FETCH_DATA,
FETCH_DATA_ERROR,
FETCH_DATA_SUCCESS,
CLICK_DEVICE
} from './constants';
export function fetchDevices() {
return {
type: FETCH_DATA,
};
}
export function fetchDevicesSuccess(devices) {
return {
type: FETCH_DATA_SUCCESS,
devices
};
}
export function fetchDevicesError(error) {
return {
type: FETCH_DATA_ERROR,
error
};
}
My saga then reacts to the FETCH_DATA action and calls a generator to fetch the data from my local API:
import { all, call, put, takeEvery } from 'redux-saga/effects';
import request from 'utils/request';
import { fetchDevicesSuccess, fetchDevicesError } from './actions';
import { FETCH_DATA } from './constants';
function* fetchDevicesAsync() {
yield takeEvery(FETCH_DATA, fetchAllDevices);
}
function* fetchAllDevices() {
try {
const requestUrl = '/api/devices';
const devices = yield call(request, requestUrl);
yield put(fetchDevicesSuccess(devices));
} catch (error) {
yield put(fetchDevicesError(error.toString()));
}
}
export default function* rootSaga() {
yield all([fetchDevicesAsync()]);
}
This in return should trigger my reducer which looks as follows:
import produce from 'immer';
import {
FETCH_DATA,
FETCH_DATA_ERROR,
FETCH_DATA_SUCCESS,
} from './constants';
export const initialState = {
devices: [],
loading: true,
error: false,
};
/* eslint-disable default-case, no-param-reassign */
const leafletMapReducer = (state = initialState, action) =>
produce(state, () => {
switch (action.type) {
case FETCH_DATA:
state.loading = true;
state.error = false;
break;
case FETCH_DATA_ERROR:
state.loading = false
state.error = action.error;
break;
case FETCH_DATA_SUCCESS:
state.loading = false;
state.error = false;
state.devices = action.devices;
break;
}
});
export default leafletMapReducer;
My problem here is that everything seems to work but my action is neither being displayed in Redux DevTools nor does my component update after the initial render. It seems as if the action is being dispatched before the ##INIT event.
Any idea why this happens?
Thanks in advance!
EDIT:
Just in case it has something to do with my selectors:
import { createSelector } from 'reselect';
import { initialState } from './reducer';
/**
* Direct selector to the leafletMap state domain
*/
const selectLeafletMapDomain = state => state.leafletMap || initialState;
/**
* Other specific selectors
*/
const makeSelectDevices = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.devices
);
const makeSelectLoading = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.loading,
);
const makeSelectError = () =>
createSelector(
selectLeafletMapDomain,
leafletMapState => leafletMapState.error,
);
/**
* Default selector used by LeafletMap
*/
const makeSelectLeafletMap = () =>
createSelector(selectLeafletMapDomain, leafletMapState => leafletMapState.toJS());
export default makeSelectLeafletMap;
export {
selectLeafletMapDomain,
makeSelectDevices,
makeSelectLoading,
makeSelectError
};
Found the problem myself :)
The problem was in my reducer:
const leafletMapReducer = (state = initialState, action) =>
produce(state, () => { // <-- here
switch (action.type) {
case FETCH_DATA:
state.loading = true;
state.error = false;
break;
I here wrongly mutated my state which leads to the error. The correct solution is:
const leafletMapReducer = (state = initialState, action) =>
produce(state, draftState => { // use draftState instead of normal state
switch (action.type) {
case FETCH_DATA:
draftState.loading = true; //<------
draftState.error = false; //<------
break;
I am having issues using the mapDispatchToProps. It does not seem to pass down my "startAddComponent" action as a prop.
This is my component page code:
import React from 'react';
import {connect} from 'react-redux';
import ComponentForm from './ComponentForm';
import {startAddComponent} from '../actions/components';
export class AddComponentPage extends React.Component {
onSubmit = (component) => {
console.log(this.props);
this.props.startAddComponent(component);
this.props.history.push('/');
};
render() {
//console.log(this.props);
return (
<div>
<h1 className="page-header__title">Add Component</h1>
<ComponentForm onSubmit={this.onSubmit}/>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return { startAddComponent: (comp) => dispatch(startAddComponent(comp))}
};
export default connect(undefined, mapDispatchToProps)(AddComponentPage);
This is the code for my actions:
import database from '../firebase/firebase';
export const addComponent = (component) => ({
type: 'ADD_COMPONENT',
component
});
export const startAddComponent = (componentData = {})=> {
return (dispatch, getState) => {
const uid = getState().auth.uid;
const {
description = '',
startDate = 0,
endDate = 0,
tags = []
} = componentData;
const component = {description, startDate, endDate, tags };
database.ref(`users/${uid}/components`).push(component)
.then ((ref) => {
dispatch(addComponent({
id: ref.key,
...component
}));
});
};
};
My store code:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import authReducer from '../reducers/auth';
import componentReducer from '../reducers/components';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default () => {
const store = createStore(
combineReducers({
auth: authReducer,
components: componentReducer
}),
composeEnhancers(applyMiddleware(thunk))
//window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
return store;
};
And my reducer:
const componentsReducerDefaultState = [];
export default (state = componentsReducerDefaultState, action) => {
//console.log(action)
switch (action.type) {
case 'ADD_COMPONENT':
return [
...state,
action.component
];
case 'SET_COMPONENTS':
return action.components;
default:
return state;
}
};
If you need more files to help me, please ask me. Any help on this would be really appreciated! Thank you so much in advance!
I just got the problem fixed! The problem was that my importing of this component was wrong in another file. I did {AddComponentPage} instead of just AddComponentPage. Thank you all!
I'm making my first react-native app and I cant seem to bind my actions to props. In the component this.props.actions is an empty, and LoginActions is also an empty object in the mapDispatchToProps function. This leads me to believe its an issue in the action or the connect binding. Can anyone see where I'm going wrong?
Component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
View,
StyleSheet,
} from 'react-native';
import {
google,
facebook,
twitter,
} from 'react-native-simple-auth';
import LoginConstants from '../../constants/Login.constants';
import * as LoginActions from '../../actions/Login.actions';
import LoginForm from '../../components/LoginForm';
class Login extends Component {
constructor(props) {
super(props);
alert(JSON.stringify(this.props.actions))
this.loginActions = {
google,
facebook,
twitter,
};
this.loginAction = this.loginAction.bind(this);
}
loginAction(platform) {
alert(JSON.stringify(this.props.actions))
// this.loginActions[platform](LoginConstants[platform])
// .then((info) => {
// alert(info);
// // info.user - user details from the provider
// // info.credentials - tokens from the provider
// }).catch((error) => {
// throw Error(`Error ${error.code}: ${error.description}`);
// });
}
render() {
return (
<LoginForm actions={this.loginActions} loginAction={this.loginAction} />
);
}
}
Login.propTypes = {
actions: PropTypes.object.isRequired,
user: PropTypes.object
};
const styles = StyleSheet.create({
});
const mapStateToProps = (state) => {
return {
user: state.user
};
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(LoginActions, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Actions:
import LoginConstants from '../constants/Login.constants';
export function getUser(userId) {
return {
type: LoginConstants.actions.getUser,
payload: new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
userId: '123ddgs',
});
}, 2000);
});
};
};
export function saveUser(user) {
return {
type: LoginConstants.actions.saveUser,
payload: new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
userId: '123ddgs',
});
}, 2000);
})
};
};
Reducer:
import LoginConstants from '../constants/Login.constants';
const loginReducers = (state = {
user: {},
prevStates: []
}, action) => {
switch (action.type) {
case LoginConstants.actions.getUser:
state = {
...state,
user: action.payload,
prevStates: [...state.prevStates, action.payload]
};
break;
case LoginConstants.actions.saveUser:
state = {
...state,
user: action.payload,
prevStates: [...state.prevStates, action.payload]
};
break;
}
return state;
};
export default loginReducers;
Store:
import {
createStore,
combineReducers,
applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import { createLogger } from 'redux-logger';
import loginReducers from './src/reducers/Login.reducers';
import beerReducers from './src/reducers/Beer.reducers';
export default createStore(
combineReducers({
loginReducers,
beerReducers,
}),
{},
applyMiddleware(createLogger(), thunk, promise())
);
JSON.stringify strips functions from its output and therefore, the actions and dispatcher were not visible in the alert output.