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
Related
How to pass text value to another component using Redux in React?
I am learning Redux in React. I am trying to pass text value to another component using Redux in React.
My code is like below
Mycomponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.dispatch({ type: "add" });
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
export default connect(mapStateToProps)(Mycomponent);
nameAction.js
export const nameAction = () => ({
type: 'add'
});
export default { nameAction };
nameReducer.js
const nameReducer = (state = {}, action) => {
switch (action.type) {
case 'add': {
return {
...state,
nameState: action.payload
};
}
default:
return state;
}
};
export default nameReducer;
Outputcomponent.js
import React, { Component } from 'react';
class Outputcomponent extends Component {
render = (props) => {
return (
<div>
<div>{this.props.nameState }</div>
</div>
);
}
}
export default Outputcomponent;
The use of redux hooks explained by Josiah is for me the best approach but you can also use mapDispatchToProps.
Even if the main problem is that you don't pass any data in your 'add' action.
nameAction.js
You call the action.payload in nameReducer.js but it does not appear in your action
export const nameAction = (text) => ({
type: 'add',
payload: text
});
Mycomponent.js
Then as for your state we can mapDispatchToProps.
(I think it's better to trigger the action with a submit button and save the input change in your textInput state, but I guess it's intentional that there is none)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {nameAction} from './nameAction'
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.nameAction(event.target.value);
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
const mapDispatchToProps = dispatch => ({ nameAction: (text) => dispatch(nameAction(text))});
export default connect(mapStateToProps,mapDispatchToProps)(Mycomponent);
OutputComponent.js
to get the data two possibilities either with a class using connect and mapStateToProps , or using the useSelector hook with a functional component.
with a Class
import React, { Component } from "react";
import { connect } from "react-redux";
class OutputComponent extends Component {
render = () => {
return (
<div>
<div>{this.props.nameState}</div>
</div>
);
};
}
const mapStateToProps = state => state;
export default connect(mapStateToProps)(OutputComponent);
with a functional component
import React from "react";
import { useSelector } from "react-redux";
const OutputComponent = () => {
const nameState = useSelector((state) => state.nameState);
return (
<div>
<div>{nameState}</div>
</div>
);
};
export default OutputComponent;
Of course you must not forget to create a strore and to provide it to the highest component
store.js
import { createStore } from "redux";
import nameReducer from "./nameReducer";
const store = createStore(nameReducer);
export default store;
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Component
const AddTodo = () => {
const [todo, setTodo] = useState("");
const dispatch = useDispatch();
const handleChange = (e) => setTodo(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addTodoAction(todo));
}
return {
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
</form>
}
)
Actions
const addTodoAction = (text) => {
dispatch({
type: "ADD_TODO",
payload: text
})
}
Reducers
const addTodoReducer = (state, action) => {
switch(action.type) {
case "ADD_TODO":
return {
todo: action.payload,
}
default:
return state;
}
}
store
// some code for store.js
Accessing this todo from another component
const ComponentA = () => {
const {todo} = useSelector(state => state.todo);
return (
<p> {todo} </p>
)
}
Side Note:
Redux comes with too much boilerplate if you want to pass text from one component to another, just use props
I have a simple component I'm trying to make work with redux. I map both props and dispatch actions, however only the props I initially get from the store work properly. I traced it all down to my actions: they are being dispatched, but respective reducers don't really do anything. Pretty simple stuff I came up with according to the tutorial and everything looks good to me, but I can't wrap my head around the problem here.
Here is a simplified version of the app:
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Search from './Search'
import { Provider } from 'react-redux'
import store from './store'
const root = document.querySelector('#app')
ReactDOM.render(
<Provider store={store}>
<Search/>
</Provider>, root)
// Search.js
import React from 'react'
import { setText } from '../../actions/appActions'
import { connect } from 'react-redux';
const mapStateToProps = state => {
return {
text: state.app.searchText
}
}
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
class Search extends React.Component {
constructor() {
super()
}
render() {
return (
<input type="text" onChange={() => this.props.setText("text")} value={this.props.text}/>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
// store.js
import { createStore, combineReducers } from 'redux'
import app from './reducers/appReducer'
export default createStore(combineReducers({/*other non-relevant reducers*/, app}))
// appActions.js
export function setText(text) {
return {
type: "APP_SET_TEXT",
payload: text,
}
}
// appReducer.js
const initialState = {
isSearchActive: true,
searchText: "Text",
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case "APP_SET_TEXT":
console.log("fart")
return {
...state,
searchText: action.payload,
}
default:
return state
}
}
What I'm trying to to is to simply make the input value change according to the redux state. I do get the text from {this.props.text}, the change handler onChange={() => this.props.setText("text")} is being dispatched, but the reducer for some reason fails to catch the action that was dispatched.
I think you should change the mapDispatchToProps variable like the following:
const mapDispatchToProps = dispatch => {
return {
setText = (text) => dispatch(setText(text)),
}
}
There are two ways to achieve this
// MODIFYING DISPATHCER
const mapDispatchToProps = dispatch => {
return {
changeText: data => dispatch(setText(data)),
}
}
or
// CONNECT
export default connect(mapStateToProps, {
setText
})(Search)
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
change to
const mapDispatchToProps = dispatch => {
return {
changeText: text => dispatch(setText(text)),
}
}
And in your component use this.props.changeText function
as most of the answers suggests you can dispatch the actions or else you can simply have mapDispatchToProps an object.
mapDispatchToProps = {
setText,
dispatch
}
Your HOC connect should take care of dispatching not need to external definition
Use bindActionCreators from redux
import { bindActionCreators } from 'redux';
const mapDispatchToProps = dispatch => {
const setText = bindActionCreators(setText, dispatch);
return setText;
}
Since you're mapping your dispatch to props like this:
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
You'll need to explicitly call dispatch in your component:
class Search extends React.Component {
constructor() {
super()
}
render() {
const {dispatch, setText} = this.props;
return (
<input type="text" onChange={() => dispatch(setText("text"))} value={this.props.text}/>
)
}
}
It is easier just to map dispatch to props like this: setText = (text) => dispatch(setText(text))
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.
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.
This is part of an extensive application so i will just post the relevant parts. I'm trying to implement an e.target.value onChange from my App.js up to the index.js file of the application. The app loads but breaks the moment a value is inserted in the input field and I'm referring to the mapDispatchToProps function:
App.js
import React, { Component } from 'react';
import Navbar from './components/Navbar';
import ToggleLayout from './components/ToggleLayout';
import { connect } from 'react-redux';
class App extends Component {
render() {
return (
<div>
<Navbar
searchTerm={this.props.searchItunes.searchTerm}
onSearchChange={(e) =>this.props.onSearchChange(e.target.value)}
/>
<ToggleLayout
switchLayout={()=> this.props.switchLayout()}
grid={this.props.toggle.grid}
/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
toggle: state.booleanReducer,
searchItunes: state.searchItunesReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
switchLayout: () => {
dispatch({
type:"GRID"
});
},
onSearchChange: (e) => {
dispatch({
type:"SEARCHTERM",
payload:e.target.value
});
}
};
};
export default connect(mapStateToProps,mapDispatchToProps)(App);
and the index file is as below:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux';
import { createStore, combineReducers, applyMiddleware } from 'redux';
const booleanReducer = (state = { grid:true, additionalPages:false }, action) => {
if (action.type === "GRID"){
return state = {
...state,
grid:!state.grid
}
}
return state;
};
const searchItunesReducer = (state = { searchTerm:'', itunes:null }, action) => {
if (action.type === 'SEARCHTERM'){
return state = {
...state,
searchTerm:action.payload
}
}
}
const store = createStore(combineReducers({booleanReducer,searchItunesReducer}));
console.log(store.getState());
store.subscribe(() =>{
console.log("store updated!", store.getState());
});
registerServiceWorker();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
The switch layout function works as intended, so could you tell me what I'm doing wrong with the onSearchChange function?
In your Navbar component you're passing onSearchChange prop as an anonymous function that calls this.props.onSearchChange with e.target.value - so you're passing the exact value to the callback while the callback in mapDispatchToProps you're defining onSearchChange as a function that accepts the change event. That's why you're getting an error when you change the search input value.
You have 2 options here, either you pass just an event to onSearchChange in the Navbar component:
<Navbar searchTerm={this.props.searchItunes.searchTerm}
onSearchChange={this.props.onSearchChange} />
or change the onSearchChange signature so that it accepts only the final value:
onSearchChange: (value) => {
dispatch({
type: "SEARCHTERM",
payload: value
});
}
<Navbar
searchTerm={this.props.searchItunes.searchTerm}
onSearchChange={(e) =>this.props.onSearchChange(e.target.value)}
/>
In the method call you are sending the value of the event, your search term, so in mapDispatchToProps you don't need to send the whole event again, you just need to send the string you are setting in onSearchChange, because in your reducer you are setting the full action payload to the searchTerm reducer attribute.
const mapDispatchToProps = (dispatch) => {
return {
switchLayout: () => {
dispatch({
type:"GRID"
});
},
onSearchChange: (term) => {
dispatch({
type:"SEARCHTERM",
payload: term,
});
}
};
};