Change state using "onClick"/"onSubmit" according to button click - reactjs

I am trying to identify a Student or a Counselor based on the button that is clicked.
What I am trying to achieve is that, if the user clicks on the Counselor button, isCounselor will become true, if the other button is clicked, then isStudent will become true.
I have tried both onClick and onSubmit but the state remains the same.
Therefore, I believe I have done something wrong but not sure where. I have just started using react-redux and cannot get my head around it!
The reducer is included in the combineReducers and I have created the types in the code below:
reducer
import { IS_COUNSELOR, IS_STUDENT } from "../actions/types";
const initialState = {
isCounselor: false,
isStudent: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case IS_COUNSELOR:
return {
...state,
isCounselor: true,
isStudent: false,
};
case IS_STUDENT:
return {
...state,
isCounselor: false,
isStudent: true,
};
default:
return state;
}
}
action
import { IS_COUNSELOR, IS_STUDENT } from "./types";
// IS COUNSELOR
export const toCounselor = () => {
return {
type: IS_COUNSELOR,
payload: { isCounselor, isStudent },
};
};
// IS STUDENT
export const toStudent = () => {
return {
type: IS_STUDENT,
payload: { isCounselor, isStudent },
};
};
component
import React, { Component } from "react";
import { Link } from "react-router-dom";
import PropTypes from "prop-types";
import { toCounselor, toStudent } from "../../actions/clients";
export class Welcome extends Component {
static propTypes = {
isCounselor: PropTypes.bool,
isStudent: PropTypes.bool,
toCounselor: PropTypes.func.isRequired,
toStudent: PropTypes.func.isRequired,
};
onSubmitCounselor = (e) => {
e.preventDefault();
this.props.toCounselor();
};
onSubmitStudent = (e) => {
e.preventDefault();
this.props.toStudent();
};
render() {
return (
{/* some divs */}
<div className="col-md-6 mt-3">
<Link to="/register" className="nav-link">
<button
type="submit"
className="btn btn-success"
// name="isStudent"
onSubmit={this.onSubmitStudent}
>
I am a Student
</button>
</Link>
</div>
<div className="col-md-6 mt-3">
<Link to="/register" className="nav-link">
<button
type="submit"
className="btn btn-info"
// name="isCounselor"
onSubmit={this.onSubmitCounselor}
>
I am a Counselor
</button>
</Link>
</div>
{/* some other divs */}
);
}
}
export default Welcome;
Could you please advise? Thank you!

Your Welcome component does not seem to be connected to the redux state, thus it is not receiving data from the store and the actions are not dispatched in the redux loop.
The following code is not ready as is but should give you a working path
// Connect the Redux Store (state) to the component props
const mapStateToProps = (state) => ({
isCounselor: state.isCounselor,
isStudent: state.isStudent,
});
// Updated thanks to the comment of Drew Reese below
const mapDispatchToProps = { toCounselor, toStudent };
// Previous version
// const mapDispatchToProps = dispatch => bindActionCreators({ toCounselor, toStudent }, dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Welcome)
You should have a look at this section of the documentation and here about the bindActionCreators

In your action file, do this...
import { IS_COUNSELOR, IS_STUDENT } from "./types";
// IS COUNSELOR
export const toCounselor = () => ({ type: IS_COUNSELOR });
// IS STUDENT
export const toStudent = () => ({ type: IS_STUDENT });
Inside the render, pass the function to the onClick prop of the Link component
Then at the end of your component, do this...
const mapStateToProps = (state) => ({
isCounselor: state.isCounselor,
isStudent: state. isStudent,
});
const mapDispatchToProps = { toCounselor, toStudent };
export default connect(
mapStateToProps,
mapDispatchToProps
)(Welcome)

Related

Redux onClick action not firing from Component, no errors or console errors

I have an eCommerce React app I'm putting together that has a view of items. Each item has an Add to cart button with an onClick function that dispatches an ADD_ITEM action to update the cart in state.
The problem I'm seeing is that the Action is never firing and the state is never updating, but there aren't any console errors or anything to point me in the direction of what's broken.
I've looked at everything over and over, there's no typos and everything is connected to the store so I'm really at a loss as to why it's not working.
Cart Reducer
import { AnyAction } from "redux";
import CartActionTypes from "./cart.types";
const INITIAL_STATE = {
hidden: true,
cartItems: [],
};
const cartReducer = (state = INITIAL_STATE, action: AnyAction) => {
switch (action.type) {
case CartActionTypes.TOGGLE_CART_HIDDEN:
return {
...state,
hidden: !state.hidden,
};
case CartActionTypes.ADD_ITEM:
return {
...state,
cartItems: [...state.cartItems, action.payload],
};
default:
return state;
}
};
export default cartReducer;
Cart Actions
import CartActionTypes from "./cart.types";
export const toggleCartHidden = () => ({
type: CartActionTypes.TOGGLE_CART_HIDDEN,
});
export const addItem = (item) => ({
type: CartActionTypes.ADD_ITEM,
payload: item,
});
Cart Types
const CartActionTypes = {
TOGGLE_CART_HIDDEN: "TOGGLE_CART_HIDDEN",
ADD_ITEM: "ADD_ITEM",
};
export default CartActionTypes;
Root Reducer
import { combineReducers } from "redux";
import userReducer from "./user/user.reducer";
import cartReducer from "./cart/cart.reducer";
export default combineReducers({
user: userReducer,
cart: cartReducer,
});
Item Component with onClick/mapDispatchToProps function
import React from "react";
import styled from "styled-components";
import { connect } from "react-redux";
import { addItem } from "../../redux/cart/cart.actions";
import { Item } from "../../pages/shop/shop.component";
const CollectionItem = ({ item }: { item: Item }) => {
const { name, price, imageUrl } = item;
return (
<CollectionItemContainer>
<Image
style={{ backgroundImage: `url(${imageUrl})` }}
className="image"
/>
<CollectionFooter>
<Name>{name}</Name>
<Price>{price}</Price>
</CollectionFooter>
<CollectionItemButton
onClick={() => addItem(item)}
className="custom-button inverted"
>
Add to cart
</CollectionItemButton>
</CollectionItemContainer>
);
};
const mapDispatchToProps = (dispatch) => ({
addItem: (item) => dispatch(addItem(item)),
});
export default connect(null, mapDispatchToProps)(CollectionItem);
Item Collection component (Parent of Item Component)
import React from "react";
import styled from "styled-components";
import { Item } from "../../pages/shop/shop.component";
import CollectionItem from "../collection-item/collection-item.component";
const CollectionPreview = ({
title,
items,
}: {
title: string;
items: Array<Item>;
}) => {
return (
<CollectionPreviewContainer>
<Title>{title.toUpperCase()}</Title>
<Preview>
{items
.filter((item, idx) => idx < 4)
.map((item) => (
<CollectionItem key={item.id} item={item} />
))}
</Preview>
</CollectionPreviewContainer>
);
};
export default CollectionPreview;
There is only a very small issue, but very relevant issue in your code: In your CollectionItem component your not using the addItem function from your props, which was injected by connect with your mapDispatchToProps function. You probably meant to destructure it in the function definition of your CollectionItem component but just forgot it.
changing
const CollectionItem = ({ item }: { item: Item }) =>
to
const CollectionItem = ({ item, addItem }: { item: Item, addItem: () => void }) =>
should fix the issue.
Note that you didn't see any error because your action creator is called addItem too. Therefore when you call addItem in the onClick function, the function is still defined even though you didn't destructure it from the props. However calling the action creator instead of the function from mapDispatchToProps will just create the action (a plain js object) and return it, without dispatching it...
To avoid such hard to spot mistakes I would recommend to name the function injected through mapDispatchToProps differently than the action creator.
Example:
const CollectionItem = ({ item /* missing fn here */ }: { item: Item }) => {
const { name, price, imageUrl } = item;
return (
<CollectionItemContainer>
<Image
style={{ backgroundImage: `url(${imageUrl})` }}
className="image"
/>
<CollectionFooter>
<Name>{name}</Name>
<Price>{price}</Price>
</CollectionFooter>
<CollectionItemButton
onClick={() => handleAddItem(item)}
className="custom-button inverted"
>
Add to cart
</CollectionItemButton>
</CollectionItemContainer>
);
};
const mapDispatchToProps = (dispatch) => ({
handleAddItem: (item) => dispatch(addItem(item)),
});
Not the the error would become really obvious, because a handleAddItem function not defined error would be thrown and you'd immediately know that you are missing the handleAddItem function in the first line of this example.
import React from 'react'
import "./login.css"
import {Button} from "#material-ui/core";
import { auth, provider} from "./firebase";
function login() {
const signin= () =>
{
auth.signInWithPopup(provider).catch(error => alert(error.message));
};
return (
<div id="Login">
<div id="login_logo">
<img src="https://cdn.worldvectorlogo.com/logos/discord-logo-color-wordmark-1.svg"/>
</div>
<Button onClick={() => signin} >Sign in</Button>
</div>
)
}
export default login;

React Firebase Cart Add Items Cart Reducer not working

currently working on adding the items to cart using react and redux but the add item does not work
I'm taking the items from my collections page and then passing the key to the product preview page
I'm using react-redux cartReducer the three files are
just can't figure out how to pass the fish products
product page
cart actions
cart reducer
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import firebase from '../../firebase/firebase';
import { connect } from 'react-redux';
import { addItem } from '../../redux/cart/cart-actions'
class FishPage extends Component {
constructor(props) {
super(props);
this.ref = firebase.firestore().collection('fishproducts');
this.unsubscribe = null;
this.state = {
fishproducts: []
};
}
componentDidMount() {
const ref = firebase.firestore().collection('fishproducts').doc(this.props.match.params.id);
ref.get().then((doc) => {
if (doc.exists) {
this.setState({
fishproducts: doc.data(),
key: doc.id,
isLoading: false
});
} else {
console.log("No such document!");
}
});
}
render() {
return (
<div >
<div>
<div>
<h4><Link to="/">back</Link></h4>
<h3>
{this.state.fishproducts.name}
</h3>
</div>
<div >
<dl>
<dt>Description:</dt>
<dd>{this.state.fishproducts.description}</dd>
<dt>Discount:</dt>
<dd>{this.state.fishproducts.discount}</dd>
<dt>Size:</dt>
<dd>{this.state.fishproducts.size}</dd>
<dt>Weight:</dt>
<dd>{this.state.fishproducts.weight}</dd>
<dt>Price:</dt>
<dd>{this.state.fishproducts.price}</dd>
<dt>Stock:</dt>
<dd>{this.state.fishproducts.stock}</dd>
</dl>
<button onClick={() => addItem(this.state.fishproducts)} >ADD TO CART</button>
</div>
</div>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
addItem: item => dispatch(addItem(item))
})
export default connect(null, mapDispatchToProps)(FishPage);```
this is cart action page
```import CartActionTypes from './cart-types';
export const toggleCartHidden = () => ({
type:CartActionTypes.TOGGLE_CART_HIDDEN
});
export const addItem = item => ({
type: CartActionTypes.ADD_ITEM,
payload: item
})```
this is cart reducer
```import CartActionTypes from './cart-types';
const INITIAL_STATE = {
hidden: true,
cartItems: []
};
export const cartReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case CartActionTypes.TOGGLE_CART_HIDDEN:
return {
...state,
hidden: !state.hidden
};
case CartActionTypes.ADD_ITEM:
return {
...state,
//cartItems: addItem(state.cartItems, action.payload)
cartItems: [...state.cartItems,action.payload]
};
default:
return state;
}
}
export default cartReducer;```
cant figure out how to pass fishproducts
So concept of React is that you need to access Firebase with a function. For that you should use a functional component.
React allows Hooks to get access to your state without a constructor so that's all
and then you'll need to use dispatch.
import React, { useState, useEffect } from 'react';
import firebase from '../../firebase/firebase';
import { Link } from 'react-router-dom';
import { connect , useDispatch} from "react-redux";
import { addItem} from '../../redux/cart/cart-actions';
const FishPage = (props) => {
const [state, setState] = useState({
name: '',
… rest of the values
isLoading: true,
})
const { name, … rest of the values } = state;
useEffect(() => {
setState({ isLoading: true });
const ref = firebase.firestore().collection('fishproducts').doc(props.match.params.id);
ref.get().then((doc) => {
setState({
name: doc.data().name,
… rest of the values
isLoading: false,
});
})
}, [props.match.params.id])
const item = [];
const dispatch = useDispatch();
return (
<div >
<div>
//your body here
<button onClick={() => dispatch(addItem(item))} >ADD TO CART</button>
</div>
</div>
</div>
);
}
const mapDispatchToProps = dispatch => {
return{
addItem: (item) => dispatch(addItem(item))
}
}
export default connect(null, mapDispatchToProps)(FishPage)

The click button is not working when assigned a redux action

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

Redux mapStateToProps state in components

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

Child component not connecting to store

I have a component that connects to a store and displays a child component like below:
render() {
return <div>
<div className="userBox">
<ProfilePhoto userid={this.props.id} />
</div>
<div className="nameTitleBox">
<div className="firstLastTitle">
<h1>{this.props.firstName} {this.props.lastName}</h1>
</div>
<IDBox userid={this.props.id} />
</div>
<div className="childcomponent">
<childComponent />
</div>
<div className="profileBox">
<EditInterests interestsList={this.props.interest} />
</div>
</div>
}
}
export default connect(
(state) => state.user,
UserState.actionCreators
)(User);
I want the child component to be a smart component that loads it's own data and controls everything itself. The code for it is pretty simple.
import * as React from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import { ApplicationState } from '../../store';
import { connect } from 'react-redux';
import * as ChildState from '../../store/childStore';
export class ChildComponent extends React.Component {
componentWillMount() {
this.props;
}
render() {
return (<div>
<div className="textCenter"><h2 id="sss">{this.props.text}</h2></div>
<div className="textRight">
<input type="button" className="button" value="Yes" /> <b className="textColor">No</b>
</div>
</div>
</div>
</div>)
}
}
const mapDispatchToProps = (dispatch) => {
return {
action: dispatch(ChildState.actionCreators.requestChildren())
}
}
export default connect(
mapDispatchToProps,
ChildState.actionCreators
)(ChildComponent);
this.props in the child component is always an empty object. Nothing from the child state is in there, the initial state, the actions, dispatch...anything. I've tried a few different things. ChildState loads fine if I actually load it in the parent. Don't know why it's not loading in the child and connecting the props.
Adding the store below:
import { Action, Reducer } from 'redux';
import { fetch, addTask } from 'domain-task';
import { AppThunkAction } from './';
export const actionCreators = {
requestChildren: () => (dispatch, getState) => {
let url = 'random';
var myheaders = new Headers();
myheaders.append("X-Requested-With", "XMLHttpRequest");
let fetchTask = fetch(url, {
headers: myheaders,
credentials: "same-origin"
})
.then(response => response.json())
.then(data => {
dispatch({ type: 'POST_ACTION', children: data });
});
addTask(fetchTask);
}
}
export const initialState = { ... };
export const reducer = (state = initialState, incomingAction) => {
const action = incomingAction;
switch (action.type) {
case 'REQUEST_ACTION':
return {
...
};
case 'POST_ACTION':
return {
...
};
default:
}
return state || initialState;
};
I believe the problem is in mapDispatchtoProps have you tried using bindActionCreators
bindActionCreators make sure action (ChildState.actionCreators.requestChildren) flows through the middleware if there is any and then to the reducers
import { bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
ChildState.actionCreators.requestChildren}, dispatch); }
export default connect(
ChildState.actionCreators,
mapDispatchToProps
)(ChildComponent);
This was happening because I was exporting both the child component and the connect function. I removed the export on the child component and its working now as expected.

Resources