I am trying to implement a sidebar function coming from https://react.semantic-ui.com/modules/sidebar/#types-sidebar. But I'm still getting the undefined 'visible' error. I want to pass my mapped state to props 'visible' from my index to the navbar.js on the buttons when disabled.
So I created a reducer 'reducers/App.js':
import {
HIDE_CLICK,
SHOW_CLICK,
HIDE_SIDE_BAR,
} from "../actions/app";
const initialState = {
visible: false
};
const appReducer = (state = initialState, {type}) => {
switch(type) {
case HIDE_CLICK:
return {
...state,
visible: false,
}
case SHOW_CLICK:
return{
...state,
visible: true,
}
case HIDE_SIDE_BAR:
return {
...state,
visible: false,
}
};
}
export default appReducer;
Then its action 'action/App.js':
export const HIDE_CLICK = "HIDE_CLICK";
export const SHOW_CLICK = "SHOW_CLICK";
export const HIDE_SIDE_BAR = "HIDE_SIDE_BAR";
export const handleHideClick = () => ({
type: HIDE_CLICK,
})
export const handleShowClick = () => ({
type: SHOW_CLICK,
})
export const handleSideBarHide = () => ({
type: HIDE_SIDE_BAR,
})
Now my component contains: 'NavBar/index.js'
import { connect } from "react-redux";
import NavBar from "./NavBar";
import { handleHideClick, handleShowClick, handleSideBarHide } from "../../redux/actions/app";
/* istanbul ignore next */
const mapStateToProps = state => ({
isAuthenticated: state.authentication.authenticated,
visible: state.app.visible
});
const mapDispatchToProps = (dispatch) => {
return{
handleHideClick: () => dispatch(handleHideClick),
handleShowClick: () => dispatch(handleShowClick),
handleSideBarHide: () => dispatch(handleSideBarHide)
}
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(NavBar);
and my 'NavBar/NavBar.js'
import React from "react";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { Container, Menu, Button, Sidebar,Segment, Icon, Header, Image } from "semantic-ui-react";
export const ShowSideBar = ({
handleShowClick,
handleHideClick,
handleSideBarHide
}) => (
<div>
<Button.Group>
<Button disabled={visible} onClick={handleShowClick}>
Show sidebar
</Button>
<Button disabled={!visible} onClick={handleHideClick}>
Hide sidebar
</Button>
</Button.Group>
<Sidebar.Pushable as={Segment}>
<Sidebar
as={Menu}
animation='overlay'
icon='labeled'
inverted
onHide={handleSideBarHide}
vertical
visible={visible}
width='thin'
>
...
)
and lastly defined the app reducer on my root reducer:
import { combineReducers } from "redux";
import authentication from "./authentication";
import app from "./app";
const rootReducer = combineReducers({
authentication,
app
});
export default rootReducer;
You need to list all the props you need or just don't destructure them:
export const ShowSideBar = ({
handleShowClick,
handleHideClick,
handleSideBarHide,
visible
}) => (
....
)
or export const ShowSideBar = (props) => (...) and access everything with props.
Related
I'm following a tutorial on learning Redux and I'm stuck at this point where state that should have an image url is returned as undefined.
Image is successfully saved in firbase storage and dispatched but when I try to get the url on new route with useSelector it is undefined.
import React, {useEffect} from "react";
import {useSelector} from "react-redux";
import {useHistory} from "react-router-dom";
import "./ChatView.css";
import {selectSelectedImage} from "./features/appSlice";
function ChatView() {
const selectedImage = useSelector(selectSelectedImage);
const history = useHistory();
useEffect(() => {
if(!selectedImage) {
exit();
}
}, [selectedImage])
const exit = () => {
history.replace('/chats');
}
console.log(selectedImage)
return (
<div className="chatView">
<img src={selectedImage} onClick={exit} alt="" />
</div>
)
}
export default ChatView
reducer created for chat (slice):
import { createSlice } from '#reduxjs/toolkit';
export const appSlice = createSlice({
name: 'app',
initialState: {
user:null,
selectedImage:null,
},
reducers: {
login: (state, action) => {
state.user = action.payload;
},
logout: (state) => {
state.user = null;
},
selectImage:(state, action) => {
state.selectedImage = action.payload
},
resetImage:(state) => {
state.selectedImage = null
}
},
});
export const { login, logout, selectImage, resetImage} = appSlice.actions;
export const selectUser = (state) => state.app.user;
export const selectSelectedImage = (state) => state.app.selectImage;
export default appSlice.reducer;
and code for dispatching that imageURL which when i console.log it gives the correct url:
import {Avatar} from "#material-ui/core";
import StopRoundedIcon from "#material-ui/icons/StopRounded"
import "./Chat.css";
import ReactTimeago from "react-timeago";
import {selectImage} from "./features/appSlice";
import {useDispatch} from "react-redux";
import {db} from "./firebase";
import {useHistory} from "react-router-dom";
function Chat({id, username, timestamp, read, imageUrl, profilePic}) {
const dispatch = useDispatch();
const history = useHistory();
const open = () => {
if(!read) {
dispatch(selectImage(imageUrl));
db.collection('posts').doc(id).set({read:true,}, {merge:true});
history.push('/chats/view');
}
};
return (
<div onClick={open} className="chat">
<Avatar className="chat__avatar" src={profilePic} />
<div className="chat__info">
<h4>{username}</h4>
<p>Tap to view - <ReactTimeago date={new Date(timestamp?.toDate()).toUTCString()} /></p>
</div>
{!read && <StopRoundedIcon className="chat__readIcon" />}
</div>
)
}
export default Chat
Your selector is trying to access the wrong field.
export const selectSelectedImage = (state) => state.app.selectImage;
Should actually be:
export const selectSelectedImage = (state) => state.app.selectedImage;
as your state has selectedImage field and not selectImage.
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;
I would like ask you about passing object to Redux.
Below is my code.
// src/actions/writingType.js
export const write = () => ({
type: 'WRITE',
})
export const update = (obj) => ({
type: 'UPDATE',
obj
})
// src/reducers/writingType.js
const initialState = {
writingType: "WRITE",
obj: null
}
const writingTypeReducer = (state = initialState, action) => {
console.log('\n inside writingTypeReducer');
console.log(action);
switch (action.type) {
case 'WRITE':
return {
...state,
writingType: 'WRITE'
};
case 'UPDATE':
return {
...state,
writingType: 'UPDATE',
obj: action.obj
};
default:
return state;
}
}
export default writingTypeReducer;
// Contentview.js
import React, { useContext } from 'react';
import { Route, Link } from 'react-router-dom';
import MarkdownRenderer from 'react-markdown-renderer';
import './Contentview.css';
import { connect } from 'react-redux'
import { write, update } from '../../actions/writingType'
import { UserConsumer } from '../../contexts/userContext';
import { Test } from '../../contexts/Test';
const Contentview = (props) => {
/*
category: "React"
contentObj:
contents: "something"
createdDatetime: "2019.10.26 08:52:05"
title: "something"
wikiIndex: 1
*/
console.log('\n Contentview');
console.log(props);
console.log('\n update(props.contentObj);');
update(props.contentObj);
const url = "/editor/" + props.category;
const updateUrl = "/update/" + props.category;
return (
<div>
<div className="categoryDiv">{props.category}</div>
<div className="writingDiv"><Link to={url}> A </Link></div>
<div className="updateDiv"><Link to={updateUrl} > B </Link></div>
<hr />
<MarkdownRenderer markdown={props.contentObj.contents} />
</div>
);
};
// export default Contentview;
const mapStateToProps = (state, props) => ({
writetypestate: state.writingType,
obj: props.contentObj
})
const mapDispatchToProps = dispatch => ({
write: () => dispatch(write()),
update: (obj) => {
console.log('Contentview, mapDispatchToProps, update');
dispatch(update(obj))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Contentview)
I used update(props.contentObj); in Contentview.js to pass props.contentObj to Redux and update obj of initialState in src/reducers/writingType.js. But obj of initialState hasn't changed and existed as null.
How should I change code?
Thank you.
use props.update to call in the main file
// Contentview.js
import React, { useContext } from 'react';
import { Route, Link } from 'react-router-dom';
import MarkdownRenderer from 'react-markdown-renderer';
import './Contentview.css';
import { connect } from 'react-redux'
import { write, update } from '../../actions/writingType'
import { UserConsumer } from '../../contexts/userContext';
import { Test } from '../../contexts/Test';
const Contentview = (props) => {
/*
category: "React"
contentObj:
contents: "something"
createdDatetime: "2019.10.26 08:52:05"
title: "something"
wikiIndex: 1
*/
console.log('\n Contentview');
console.log(props);
console.log('\n update(props.contentObj);');
props.update(props.contentObj);
const url = "/editor/" + props.category;
const updateUrl = "/update/" + props.category;
return (
<div>
<div className="categoryDiv">{props.category}</div>
<div className="writingDiv"><Link to={url}> A </Link></div>
<div className="updateDiv"><Link to={updateUrl} > B </Link></div>
<hr />
<MarkdownRenderer markdown={props.contentObj.contents} />
</div>
);
};
// export default Contentview;
const mapStateToProps = (state, props) => ({
writetypestate: state.writingType,
obj: props.contentObj
})
const mapDispatchToProps = dispatch => ({
write: () => dispatch(write()),
update: (obj) => {
console.log('Contentview, mapDispatchToProps, update');
dispatch(update(obj))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Contentview)
Please use the above code
I'm new to redux so just trying to apply redux to a very simple app. It just toggles the word whenever the button is clicked. But how should I dispatch my handleClick function except the action? For now nothing happens when I click the button.
App.js
import React, { Component } from "react";
import { connect } from 'react-redux';
import MyButton from "./MyButton";
import { handleClick } from "./actions";
import "./styles.css";
class App extends Component {
handleClick = () => {
if (this.state.text === "initial text") {
this.setState({ text: "" });
} else {
this.setState({ text: "initial text" });
}
}
render() {
return (
<div className="App">
<MyButton onClick={()=>this.props.handleClick('hi')} />
<p>{this.props.text}</p>
</div>
);
}
}
const mapStateToProps = state => ({
text: state.text
})
const mapDispatchToProps = dispatch => ({
handleClick: () => dispatch(handleClick)
})
export default connect(mapStateToProps, mapDispatchToProps)(App)
MyButton.js
import React, { Component } from "react";
class MyButton extends Component {
render() {
return <button onClick={this.props.onClick}>
Click Me!
</button>;
}
}
export default MyButton;
actions.js
export const handleClick = text => ({
type: "test_action",
payload: { ...text }
});
reducers.js
export const reducer = (state = {text:'initial_text'}, action) => {
if(action.type === 'test_action') {
return Object.assign({}, state, action.payload)
}
return state;
}
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { reducer } from "./reducers";
import App from "./App";
import "./styles.css";
const store = createStore(reducer);
const rootElement = document.getElementById("root");
ReactDOM.render(<Provider store={store}><App /></Provider>, rootElement);
You should pass an argument to your handleClick function:
const mapDispatchToProps = dispatch => ({
handleClick: (text) => dispatch(handleClick(text))
})
or just:
const mapDispatchToProps = { handleClick }
Your action is spreading a string inside an object, you should use it as-is:
export const handleClick = text => ({
type: "test_action",
payload: text
});
And your reducer is setting the whole state, instead of just the text property. You can avoid the confusion by splitting then recomining the reducer:
import { combineReducers } from 'redux'
export const text = (state='', action) => {
if(action.type === 'test_action') {
return action.payload;
}
return state;
}
export const reducer = combineReducers({
text
})
The problem is that the mapDispatchToProps handleClick prop in the above code does not accept arguments
const mapDispatchToProps = dispatch => ({
handleClick: (val) => dispatch(handleClick(val)) // update here so that the 'hi' text is passed to the action creator
})
<MyButton onClick={()=>this.props.handleClick('hi')} />
Update
The state is not updated correctly
return Object.assign({}, state, { text: action.payload }) //pass an object and not just the value
CounterContainer
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropType from 'prop-types';
import Counter from '../components/Counter';
import * as counterActions from '../store/modules/counter';
import * as postActions from '../store/modules/post';
class CounterContainer extends Component {
handleIncrement = () => {
const { CounterActions } = this.props;
CounterActions.increment();
}
handleDecrement = () => {
const { CounterActions } = this.props;
CounterActions.decrement();
}
addDummy = () => {
const { PostActions } = this.props;
console.log(PostActions);
PostActions.addDummy({
content: 'dummy',
userUID: 123,
});
}
render() {
const { handleIncrement, handleDecrement, addDummy } = this;
const { number } = this.props;
return (
<Counter
onIncrement={handleIncrement}
onDecrement={handleDecrement}
addDummy={addDummy}
number={number}
/>
);
}
}
CounterContainer.propTypes = {
number: PropType.number.isRequired,
CounterActions: PropType.shape({
increment: PropType.func,
decrement: PropType.func,
}).isRequired,
};
export default connect(
state => ({
number: state.counter.number,
}),
dispatch => ({
CounterActions: bindActionCreators(counterActions, dispatch),
PostActions: bindActionCreators(postActions, dispatch),
}),
)(CounterContainer);
PostContainer
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// import { ListItem } from 'react-native-elements';
import { Text } from 'react-native';
import PropType from 'prop-types';
import Post from '../components/Post';
import * as postActions from '../store/modules/post';
class PostContainer extends Component {
refreshing = () => {}
onRefresh = () => {}
renderItem = ({ item }) => (<Text>{item.content}</Text>)
render() {
const { renderItem } = this;
const { postList } = this.props;
return (
<Post
postList={postList}
renderItem={renderItem}
/>
);
}
}
export default connect(
state => ({
postList: state.post.postList,
}),
dispatch => ({
CounterActions: bindActionCreators(postActions, dispatch),
}),
)(PostContainer);
modules/post
import { createAction, handleActions } from 'redux-actions';
const initialState = {
postList: [{
content: 'test',
userUID: 123,
},
{
content: '123123',
userUID: 123123,
},
],
};
const ADD_DUMMY = 'ADD_DUMMY';
export const addDummy = createAction(ADD_DUMMY, ({ content, userUID }) => ({ content, userUID }));
const reducer = handleActions({
[ADD_DUMMY]: (state, action) => ({
...state,
postList: [action.data, ...state.postList],
}),
}, initialState);
export default reducer;
Clicking the button adds a dummy to the postList.
However, when I click the button, I get
TypeError: Can not read property 'content' of undefined error.
I thought I made it the same as the count-up down tutorial.
But Count Up Down works.
Adding a dummy I made does not work.
Where did it go wrong?
Until I click the Add Dummy Data button
The list is worked.
i change action.data -> actions.payload
const reducer = handleActions({
[ADD_DUMMY]: (state, action) => ({
...state,
postList: [action.payload, ...state.postList],
}),
}, initialState);
It is simply a mistake.