Redux actions without return or dispatch - redux

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?

Related

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

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());
};
};

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

too many renders react hooks, useEffect, map

hey guys I'm trying to add to sum a simple product price for each iteration and notice its runs too manytimes. in the use effect I have to listen to the UserCart. can't do it with an empty array/[userCart].
also I get the following error in more components which i couldnt fix for the same reason with the last argument in the useEffect:
update: I did divide the useEffect as suggusted but it with [userCart ] it enter infinite loop
the code:
import React, { useState, useEffect } from 'react'
import firebase from 'firebase';
import { useAuth, useStoreUpdate } from '../contexts/FirebaseContext';
export default function Cart() {
const [userMail, setUserMail] = useState(undefined)
const [userCart, setUserCart] = useState(undefined)
const [totalAmmout, setTotalAmmout] = useState(0)
const user = useAuth()
const userDoc = firebase.firestore().collection("cart").doc(userMail)
const updateStore = useStoreUpdate()
const updateCart = () => {
userDoc.get().then((doc) => {
if (doc.exists) {
let cart = doc.data()
setUserCart(cart)
}
})
}
async function removeFromCart(itemId, name, url, price, category, type, description) {
const cartItem = { itemId, name, url, price, category, type, description }
await userDoc.update({
item: firebase.firestore.FieldValue.arrayRemove(cartItem)
})
await updateCart()
await updateStore()
}
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email)
updateCart()
updateStore()
}
},userCart)
if (!userCart) return <h1>hold</h1>
let total = 0
return (
<main className="main-cart">
<div className="container">
{userCart.item && userCart.item.length >= 1 && userCart.item.map((item, i, arr) => {
console.log(item); //it runs more than 20 times
return (
< div className="item-container" key={item.itemId} >
<h3>{item.name}</h3>
<p>{item.price}</p>
<img height="150px" width="150px" src={item.url} alt="" />
<button onClick={async () => {
await removeFromCart(item.itemId, item.name, item.url, item.price, item.category, item.type, item.description)
}}>X</button>
</div>
)
})}
</div>
<div className="fixed-bottom-link">
<button>finish purchase</button>
</div>
</main >
)
}
edit :
I found the major component, but still couldnt make it work
the 2nd useEffect [userCart] wont run, it works only when not in the array, but it enter a loop:
import React, { useContext, useState, useEffect } from 'react';
import { auth } from '../firebase/firebase';
import firebase from '../firebase/firebase';
const FirebaseContext = React.createContext()
const StoreContext = React.createContext()
const StoreContextUpdate = React.createContext()
export function useAuth() {
return useContext(FirebaseContext)
}
export function useStore() {
return useContext(StoreContext)
}
export function useStoreUpdate() {
return useContext(StoreContextUpdate)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true)
const [userMail, setUserMail] = useState(undefined)
const [userCart, setUserCart] = useState(undefined)
const userDoc = firebase.firestore().collection("cart").doc(userMail)
const updateCart = () => {
userDoc.get().then((doc) => {
if (doc.exists) {
let cart = doc.data()
setUserCart(cart)
}
})
}
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function login(email, pasword) {
return auth.signInWithEmailAndPassword(email, pasword)
}
function logout() {
return auth.signOut()
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async user => {
await setCurrentUser(user)
await setLoading(false)
})
return unsubscribe
}, [userCart])
useEffect(()=>{
async function refreshCart(){
await setUserMail(currentUser.email);
await updateCart()
}
if (currentUser && currentUser.email) {
refreshCart()
}
return
},[userCart])
const value = {
currentUser,
signup,
login,
logout
}
return (
<FirebaseContext.Provider value={value}>
<StoreContext.Provider value={userCart && userCart.item}>
<StoreContextUpdate.Provider value={updateCart}>
{!loading && children}
</StoreContextUpdate.Provider>
</StoreContext.Provider>
</FirebaseContext.Provider>
)
}
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email);
updateCart();
updateStore();
}
}, [userCart]); //<-- userCart needs to be inside array
and the reason for too many rerenders is you're calling updateCart function when the userCart changes and then userCart change will cause the call of updateCart again and this loop will repeat itself until you're out of memory
I think adding another useEffect might help you
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email);
updateStore();
}
}, [userCart]); //<-- userCart needs to be inside array
useEffect(() => {
if (user.currentUser) {
updateCart();
}
}, []);

'GiftedChat' API not rendering and unable to send/receive texts

So I'm basically developing a react-native Chat app using firebase as the backend and I'm stuck at this point where I am unable to send messages and not render the GiftedChat Api even. I'm pasting the Home component here can you please help me out. I'm trying to append the message to the giftedchat component and render it whenever i press the send button.
import React, { Component } from 'react' import { Text, View, Button }
from 'react-native' import { GiftedChat } from
'react-native-gifted-chat' import firebase from '../database/Firebase'
import AsyncStorage from '#react-native-community/async-storage'
class Home extends Component {
state = {
messages: [],
user: 'true',
userData: null
}
componentDidMount() {
const db = firebase.firestore()
const chatsRef = db.collection('chats')
this.readUser()
const unsubscribe = chatsRef.onSnapshot((querySnapshot) => {
const messagesFirestore = querySnapshot
.docChanges()
.filter(({ type }) => type === 'added')
.map(({ doc }) => {
const message = doc.data()
return { ...message, createdAt: message.createdAt.toDate() }
})
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
this.appendMessages(messagesFirestore)
})
return () => unsubscribe()
}
handleSend(messages) {
const writes = messages.map((m) => chatsRef.add(m))
Promise.all(writes)
}
appendMessages = (messages) => {
this.setState((previousMessages) => GiftedChat.append(previousMessages, messages))
}
async readUser() {
const userData = await AsyncStorage.getItem('userData')
if (userData) {
setUser(JSON.parse(userData))
}
}
async handlePress() {
const _id = Math.random().toString(36).substring(7)
const userData = { _id, name }
await AsyncStorage.setItem('userData', JSON.stringify(userData))
}
handleLogout = () => {
this.setState(() => ({
user: false
}))
}
render() {
if (this.state.user === 'true') {
return (<View>
<Button title="logout" style={{ width: 200 }} onPress={() => this.handleLogout()}></Button>
<GiftedChat messages={this.state.messages} user={this.state.userData}
onSend={() => this.handleSend()} />
</View>)
} else {
return (<View>
{this.props.navigation.navigate("Login")}
</View>)
}
} }
export default Home

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])

Resources