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
Related
Hi im new to redux and im trying to create a movie app using the API from www.themoviedb.org. I am trying to display the popular movies and im sure the API link works since ive tested it in postman but i cant seem to figure out why redux doesnt pick up the data.
//action
import { FETCH_POPULAR } from "./types";
import axios from "axios";
export const fetchPopularMovies = () => (dispatch) => {
axios
.get(
`https://api.themoviedb.org/3/movie/popular?api_key=${API}&language=en-US`
)
.then((response) =>
dispatch({
type: FETCH_POPULAR,
payload: response.data
})
)
.catch((err) => console.log(err));
};
//reducer
import { FETCH_POPULAR } from "../actions/types";
const initialState = {
popular: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_POPULAR:
return {
...state,
popular: action.payload,
};
default:
return state;
}
}
import React from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
export default connect(mapStateToProps)(FetchedPopular);
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.poster_path}`}
/>
</div>
);
};
export default Popular;
I cant really tell what I'm missing can someone help?
Next to mapStateToProps you need to create mapDispatchToProps. After that, you will be able to call your Redux action from your React component.
I suggest you the mapDispatchToProps as an Object form. Then you need to use this mapDispatchToProps as the second parameter of your connect method.
When you will have your action mapped to your component, you need to call it somewhere. It is recommended to do it for example on a component mount. As your React components are Functional components, you need to do it in React useEffect hook.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
import { fetchPopularMovies } from 'path_to_your_actions_file'
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
useEffect(()=> {
// call your mapped action (here it is called once on component mount due the empty dependency array of useEffect hook)
props.fetchPopularMovies();
}, [])
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
// create mapDispatchToProps
const mapDispatchToProps = {
fetchPopularMovies
}
// use mapDispatchToProps as the second parameter of your `connect` method.
export default connect(mapStateToProps, mapDispatchToProps)(FetchedPopular);
Moreover, as I wrote above in my comment, your Popular does not have the prop poster_path but it has the prop popular which probably has the property poster_path.
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.popular.poster_path}`}
/>
</div>
);
};
export default Popular;
I have a simple search aplication, where depending by user input, it get the result on front end.
My redux code:
import { persons } from "./persons";
import { createStore } from "redux";
//contant
export const SEARCH = {
SEARCH_PERSON: "SEARCH_PERSON"
};
//action
export const searchPersonAction = (person) => {
const personSearched = persons.filter((p) =>
p.name.toLowerCase().includes(person.toLowerCase())
);
return {
type: SEARCH.SEARCH_PERSON,
payload: personSearched
};
};
//reducer
const initialState = {
name: persons
};
export const search = (state = initialState, { type, payload }) => {
switch (type) {
case SEARCH.SEARCH_PERSON:
return {
...state,
name: payload
};
default:
return state;
}
};
//store
export const store = createStore(search);
UI component:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { searchPersonAction } from "./store";
const Search = () => {
const dispatch = useDispatch();
const selector = useSelector((s) => s);
const search = (e) => {
const txt = e.target.value;
dispatch(searchPersonAction(txt));
};
return (
<div>
<input onChange={search} placeholder="search" />
<ul>
{selector.name.map((p) => (
<li key={p.name}>{p.name}</li>
))}
</ul>
</div>
);
};
export default Search;
Now the application works properly, but i want to integrate in my application reselect library. I want to use reselect in filter logic.
Question: Which changes should i add in my application code?
demo: https://codesandbox.io/s/brave-monad-litc1?file=/src/Search.js:0-577
You can wrap the function in useSelector into createSelector from reselect which will memoise the selector values. You can do it like this:
import React, { useEffect } from "react";
import { createSelector } from "reselect";
import { useDispatch, useSelector } from "react-redux";
import { searchPersonAction } from "./store";
const memoiseSelector = createSelector(
(s) => s.name,
(name) => name
);
const Search = () => {
const dispatch = useDispatch();
const name = useSelector(memoiseSelector);
const search = (e) => {
const txt = e.target.value;
dispatch(searchPersonAction(txt));
};
return (
<div>
<input onChange={search} placeholder="search" />
<ul>
{name?.map((p) => (
<li key={p.name}>{p.name}</li>
))}
</ul>
</div>
);
};
export default Search;
Further more examples you can check here on official docs.
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
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,
});
}
};
};
I've been debugging this program but with no clue, I followed this tutorial word by word trying to make a TODO app but I could not figure out why I am getting this error message.
./src/containers.js
Line 12: 'dispatch' is not defined no-undef
Line 13: 'dispatch' is not defined no-undef
components.js
import React from 'react'
class Todo extends React.Component {
render() {
const { todo } = this.props
if (todo.isDone) {
return <strike>{todo.text}</strike>
} else {
return <span>{todo.text}</span>
}
}
}
export default class TodoList extends React.Component {
render() {
const {todos, toggleTodo, addTodo } = this.props
console.log(toggleTodo)
return (
<div className="todo">
<input type="text" placeholder="Add todo"/>
<ul className='todo__list'>
{todos.map(t => (
<li key={t.id} className='todo__item'>
<Todo todo={t} />
</li>
))}
</ul>
</div>
)
}
}
containers.js
import * as components from './components.js'
import { addTodo, toggleTodo } from './actions.js'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
return {todos: state}
}
const mapDispatchToProps = (state) => {
return {
addTodo: text => dispatch(addTodo(text)),
toggleTodo: id => dispatch(toggleTodo(id))
}
}
const TodoList = connect(mapStateToProps, mapDispatchToProps)(components.TodoList)
export default TodoList
reducers.js
const reducer = (todos = [], action) => {
switch(action.type) {
case 'ADD_TODO': {
return [...todos, {id: action.id, text: action.text, completed: false}]
}
case 'TOGGLE_TODO': {
return todos.map(todo => todo.id === action.id ? {...todo, completed: !todo.completed} : todo)
}
default: {
return todos
}
}
}
export default reducer
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { TodoList } from './containers'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import reducer from './reducers'
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<TodoList />
</Provider>,
document.getElementById("app")
)
Instead of state here:
mapDispatchToProps = (state) =>
Use dispatch:
mapDispatchToProps = (dispatch) =>
In containers.js.
DOC:
container components can dispatch actions. In a similar fashion, you
can define a function called mapDispatchToProps() that receives the
dispatch() method and returns callback props that you want to inject
into the presentational component.