TypeError: dispatch is not a function when clicking the toggle button - redux

I am using react redux-thunk. I have a set of users data that I get from an API and this is the schema:
.
I've connected the "active" property with the checked attribute of a Switch MUI button, so naturally when calling the API I have some users with their switch button already on "true". What I am trying to do is to just make the switch functional, and just be able to click it and change its state, not necessarily doing anything with that.
Here's my toggleType.js:
export const TOGGLE = "TOGGLE";
Here's my toggleAction.js:
import { TOGGLE } from "./toggleType";
const statusToggleAction = () => {
return {
type: TOGGLE,
};
};
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction);
};
};
Here's my toggleReducer.js:
import { TOGGLE } from "./toggleType";
const initialState = {
status: false,
};
const toggleReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE:
status: true;
default:
return state;
}
};
export default toggleReducer;
Everything is under my userContainer.js, like that:
function UserContainer({ userData, fetchUsers }) {
useEffect(() => {
fetchUsers();
}, []);
return userData.loading ? (
<h2>Loading</h2>
) : userData.error ? (
<h2>{userData.error}</h2>
) : (
<Container maxWidth="lg" style={{ flexGrow: 1, height: "100%" }}>
<h2>User List</h2>
<div>
{userData &&
userData.users &&
userData.users.map((user) => (
<div key={user.id}>
<p>{user.name}</p>
<Switch checked={user.active} onChange={statusToggle()} />
</div>
))}
</div>
</Container>
);
}
const mapStateToProps = (state) => {
return { userData: state.user, statusToggle: state.status };
};
const mapDispatchToProps = (dispatch) => {
return {
fetchUsers: () => dispatch(fetchUsers()),
statusToggle: () => dispatch(statusToggle()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserContainer);
This is the error I am getting whenever I am clicking one of those switches:
Any ideas are welcome, I "learned" redux like 3 days ago!

toggleReducer function in toggleReducer.js, replace status: true; with return { status: true }.
Just return action in statusToggle function in toggleAction.js without dispatch as following.
export const statusToggle = () => {
return statusToggleAction();
};
Or just call statusToggleAction directly in userContainer.js as following.
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction());
};
};

Related

How to call a function from another component

I am using Alan AI voice assistant, so I am trying to trigger a function from another component based on the voice command.
This is the component holding the function I want to call
const CartButton: React.FC<CartButtonProps> = ({
className,
isShowing,
}) => {
const { openDrawer, setDrawerView } = useUI();
function handleCartOpen() {
setDrawerView('CART_SIDEBAR');
isShowing;
return openDrawer();
}
return (
<button
className={cn(
'flex items-center justify-center',
className
)}
onClick={handleCartOpen}
aria-label="cart-button"
>
</button>
);
};
export default CartButton;
So in the component above I want to use the handleCartOpen function in the below component
const COMMANDS = {
OPEN_CART: "open-cart",
}
export default function useAlan() {
const [alanInstance, setAlanInstance] = useState()
const openCart = useCallback(() => {
alanInstance.playText("Opening cart")
// I want to call the handleCartOpen function here
}, [alanInstance])
useEffect(() => {
window.addEventListener(COMMANDS.OPEN_CART, openCart)
return () => {
window.removeEventListener(COMMANDS.OPEN_CART, openCart)
}
}, [openCart])
useEffect(() => {
if (alanInstance != null) return
const alanBtn = require('#alan-ai/alan-sdk-web');
setAlanInstance(
alanBtn({
key: process.env.NEXT_PUBLIC_ALAN_KEY,
rootEl: document.getElementById("alan-btn"),
onCommand: ({ command, payload }) => {
window.dispatchEvent(new CustomEvent(command, { detail: payload }))
}
}));
}, []);
}
So in the openCart Callback, i want to trigger the handleCartOpen function which is in the first component

How to handle action dispatching for a nested React Redux component

I'm doing something like this for my UI component in a React Redux app:
// Outer.js
import Inner from './path'
export const Outer = () => {
return (
<div>
...
<Inner />
...
</div>
)
}
// Inner.js
const param = ''
export const Inner = () => {
return (
<div>
<TextField
input={param}
onChange = {(param) => {
Function(param)
}}
/>
</div>
)
}
I also set up a Container component for Outer.js:
// OuterContainer.js
import Outer from './path'
const mapStateToProps = (state) => {
paramToBeUpdated: ???
}
const mapDispatchToProps = (dispatch) => {
Function: (param) => dispatch(Function(param))
}
export default connect(mapStateToProps, mapDispatchToProps)(Outer)
My action created for this step:
action/index.js
export const Function = (param) => (dispatch, getState) => {
dispatch({ type: 'FUNCTION', param })
}
And my reducer included the following function:
// reducer.js
export default reducer = (state="", action) => {
switch(action.type) {
case 'FUNCTION':
return {
...state,
param: action.param
}
...
}
}
I'm trying to update the variable paramToBeUpdated's value from the Inner UI component. But it didn't work.
Can Inner and Outer components share a container component connected with Outer?
How should I do it without making too much changes to my current setup? Is it possible to avoid creating a new Inner container, which will basically be a copy of the Outer container?
If you can't connect Inner with the state value and or the action then you must have done something wrong, here is a working example:
const { Provider, connect } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { produce } = immer;
const initialState = {
value: '',
};
//action types
const CHANGE = 'CHANGE';
//action creators
const change = (value) => ({
type: CHANGE,
payload: value,
});
const reducer = (state, { type, payload }) => {
if (type === CHANGE) {
return produce(state, (draft) => {
draft.value = payload;
});
}
return state;
};
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
//components
const Inner = connect((state) => state.value, {
Function: change,
})(({ Function, value }) => {
return (
<input
type="text"
value={value}
onChange={(e) => Function(e.target.value)}
/>
);
});
const Outer = () => {
return (
<div>
<Inner />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<Outer />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://unpkg.com/immer#7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>
Only Outer is connected to the redux store.
If you want to dispatch an action from Inner you may do:
Connect Inner to the redux store
// Inner.js
const Inner = (props) => {
return (
<div>
<TextField
input={param}
onChange = {(param) => {
props.Function(param)
}}
/>
</div>
)
}
export default connect(null, mapDispatchToProps)(Inner)
no need to create any InnerContainer
Pass dispatch function from Outer (+ no need for Container)
// Outer.js
import Inner from './path'
export const Outer = (props) => {
return (
<div>
...
<Inner Function={props.Function} />
...
</div>
)
}
const mapStateToProps = (state) => {
paramToBeUpdated: ???
}
const mapStateToProps = (dispatch) => {
Function: (param) => dispatch(Function(param))
}
export default connect(mapStateToProps, mapDispatchToProps)(Outer)
// Inner.js
const Inner = (props) => {
return (
<div>
<TextField
input={param}
onChange = {(param) => {
props.Function(param)
}}
/>
</div>
)
}

Why, while using useEffect() and .then() in Redux, I get an Error: Actions must be plain objects. Use custom middleware for async actions

using Redux and am now straggling with a signin and signout button while using oauth.
When I press on the button to logIn, the popup window appears and I can choose an account. But in the meantime the webpage throws an error.
I got the following error as stated in the title:
Error: Actions must be plain objects. Use custom middleware for async actions.
I am using hooks, in this case useEffect().then() to fetch the data.
1) Why?
2) Also do not know, why I am getting a warning: The 'onAuthChange' function makes the dependencies of useEffect Hook (at line 35) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'onAuthChange' definition into its own useCallback() Hook react-hooks/exhaustive-deps
Here is my code:
GoogleAuth.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { signIn, signOut } from "../actions";
const API_KEY = process.env.REACT_APP_API_KEY;
const GoogleAuth = () => {
const isSignedIn = useSelector((state) => state.auth.isSignedIn);
console.log("IsSignedIn useSelector: " + isSignedIn);
const dispatch = useDispatch();
const onAuthChange = () => {
if (isSignedIn) {
dispatch(signIn());
} else {
dispatch(signOut());
}
};
useEffect(
() => {
window.gapi.load("client:auth2", () => {
window.gapi.client
.init({
clientId: API_KEY,
scope: "email"
})
.then(() => {
onAuthChange(window.gapi.auth2.getAuthInstance().isSignedIn.get());
console.log("isSignedIn.get(): " + window.gapi.auth2.getAuthInstance().isSignedIn.get());
window.gapi.auth2.getAuthInstance().isSignedIn.listen(onAuthChange);
});
});
},
[ onAuthChange ]
);
const onSignInOnClick = () => {
dispatch(window.gapi.auth2.getAuthInstance().signIn());
};
const onSignOutOnClick = () => {
dispatch(window.gapi.auth2.getAuthInstance().signOut());
};
const renderAuthButton = () => {
if (isSignedIn === null) {
return null;
} else if (isSignedIn) {
return (
<button onClick={onSignOutOnClick} className="ui red google button">
<i className="google icon" />
Sign Out
</button>
);
} else {
return (
<button onClick={onSignInOnClick} className="ui red google button">
<i className="google icon" />
Sign In with Google
</button>
);
}
};
return <div>{renderAuthButton()}</div>;
};
export default GoogleAuth;
reducer/index.js
import { combineReducers } from "redux";
import authReducer from "./authReducer";
export default combineReducers({
auth: authReducer
});
reducers/authReducer.js
import { SIGN_IN, SIGN_OUT } from "../actions/types";
const INITIAL_STATE = {
isSignedIn: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SIGN_IN:
return { ...state, isSignedIn: true };
case SIGN_OUT:
return { ...state, isSignedIn: false };
default:
return state;
}
};
actions/index.js
import { SIGN_IN, SIGN_OUT } from "./types";
export const signIn = () => {
return {
type: SIGN_IN
};
};
export const signOut = () => {
return {
type: SIGN_OUT
};
};
types.js
export const SIGN_IN = "SIGN_IN";
export const SIGN_OUT = "SIGN_OUT";
The reason of the first error is that, inside both onSignInOnClick and onSignInOnClick, dispatch() receives a Promise (since window.gapi.auth2.getAuthInstance().signIn() returns a Promise).
There are different solution to handle effects in redux, the simplest are redux promise or redux thunk.
Otherwise you can dispatch the { type: SIGN_IN } action, and write a custom middleware to handle it.
The reason of the second error, is that the onAuthChange is redefined on every render, as you can see here:
const f = () => () => 42
f() === f() // output: false
Here's a possible solution to fix the warning:
useEffect(() => {
const onAuthChange = () => {
if (isSignedIn) {
dispatch(signIn())
} else {
dispatch(signOut())
}
}
window.gapi.load('client:auth2', () => {
window.gapi.client
.init({
clientId: API_KEY,
scope: 'email',
})
.then(() => {
onAuthChange(window.gapi.auth2.getAuthInstance().isSignedIn.get())
console.log(
'isSignedIn.get(): ' +
window.gapi.auth2.getAuthInstance().isSignedIn.get(),
)
window.gapi.auth2.getAuthInstance().isSignedIn.listen(onAuthChange)
})
})
}, [isSignedIn])

Redux actions without return or dispatch

I am implementing Oauth from google with redux, and I wanted to have all google API calls handled from my redux and ended up writing helper functions in my actions file that doesn't return anything or call dispatch. I ended up with code where I only dispatch once from my JSX file and wondering if this is okay or there is another better way to do it?
The code is as follows:
authActions.js
const clientId = process.env.REACT_APP_GOOGLE_OAUTH_KEY;
let auth;
export const authInit = () => (dispatch) => {
window.gapi.load('client:auth2', () =>
window.gapi.client.init({ clientId, scope: 'email' }).then(() => {
auth = window.gapi.auth2.getAuthInstance();
dispatch(changeSignedIn(auth.isSignedIn.get()));
auth.isSignedIn.listen((signedIn) => dispatch(changeSignedIn(signedIn)));
})
);
};
export const signIn = () => {
auth.signIn();
};
export const signOut = () => {
auth.signOut();
};
export const changeSignedIn = (signedIn) => {
const userId = signedIn ? auth.currentUser.get().getId() : null;
return {
type: SIGN_CHANGE,
payload: { signedIn, userId },
};
};
GoogleAuth.jsx
import { useSelector, useDispatch } from 'react-redux';
import classNames from 'classnames';
import { authInit, signIn, signOut } from '../../actions/authActions';
function GoogleAuth() {
const { signedIn } = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
dispatch(authInit());
}, [dispatch]);
const onClick = () => {
if (signedIn) {
signOut();
} else {
signIn();
}
};
let content;
if (signedIn === null) {
return null;
} else if (signedIn) {
content = 'Sign Out';
} else {
content = 'Sign In';
}
return (
<div className="item">
<button
className={classNames('ui google button', {
green: !signedIn,
red: signedIn,
})}
onClick={onClick}
>
<i className="ui icon google" />
{content}
</button>
</div>
);
}
export default GoogleAuth;
The code works fine, but it feels like it might be misleading having action calls in JSX but not dispatching it, is it okay?

redux reducer not called

Can someone help me figure out why this simple redux implementation is not calling the reducer on the anchor tag click?
The action gets initiated on each click, while the reducer only once when the program starts.
Any pointer appreaciated.
// action
const changeText = (text) => {
console.log('action changeDate')
return {
type: 'CHANGE_TEXT',
text
};
};
// reducer
const changeTextReducer = (state = [], action) => {
console.log('reducer changeTextReducer')
return [
...state,
{
text: 'Some Text'
}
]
}
class Sales extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this)
}
handleClick(e) {
e.preventDefault();
this.props.onClick('test')
}
render() {
return (
<div className="content">
<a href="" onClick={(e) => this.handleClick(e)} > click me </a>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('mapStateToProps', state)
return {changeTextReducer: state.text}
};
const mapDispatchToProps = (dispatch) => {
return {
onClick: (dates) => {
dispatch(changeText(dates))
}
}
};
const SalesApp = connect(
mapStateToProps,
mapDispatchToProps
)(Sales);
export default SalesApp
// store
const store = createStore(
allReducers, composeWithDevTools(
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
createLogger // neat middleware that logs actions
),
)
);

Resources