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.
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 am working on React,Redux and Redux-sagas, am getting infinite loop on the appliaition, please help out to fix this issue.
Item.js
import React from "react";
import ReactDOM from "react-dom";
import { Link } from "react-router-dom";
import { gateway as MoltinGateway } from "#moltin/sdk";
import getList from "./../Action/Action";
import { connect } from "react-redux";
//import data from "./data";
export class Item extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.pickItem = this.pickItem.bind(this);
}
pickItem(pickedItem, id) {
//this.props.getList();
//pickedItem.push(id);
//this.setState({ pickItem: pickedItem });
}
componentWillMount() {
this.props.getList();
}
render() {
const { pickedItem } = this.state;
//const data = this.props.getList()
console.log(this.props);
return (
<div className="ItemPage">
<header>
<h1>Online shopping</h1>
<h2>Visit | Pick | Pay</h2>
</header>
<div
onClick={this.pickItem.bind(this, pickedItem, 2)}
className="item-list"
>
<div className="logoWarapper">
<img
src="https://rukminim1.flixcart.com/image/660/792/jmdrr0w0/shirt/q/q/r/xxl-tblwtshirtful-sh4-tripr-original-imaf9ajwb3mfbhmh.jpeg?q=50"
width="100"
height="100"
alt=""
/>
</div>
<div className="itemWarapper">
<h3>Item Name</h3>
<p>
<span>₹</span>
<span>3000</span>
</p>
</div>
</div>
<div onClick={this.pickItem} className="item-list">
<div className="logoWarapper">
<img
src="https://rukminim1.flixcart.com/image/660/792/jmdrr0w0/shirt/q/q/r/xxl-tblwtshirtful-sh4-tripr-original-imaf9ajwb3mfbhmh.jpeg?q=50"
width="100"
height="100"
alt=""
/>
</div>
<div className="itemWarapper">
<h3>Item Name</h3>
<p>
<span>₹</span>
<span>3000</span>
</p>
</div>
</div>
<Link to="/payment">
<button className="button">Make Payment</button>
</Link>
</div>
);
}
}
const mapStateToProps = state => ({
list: state.list
});
const mapDispatchToProps = {
getList
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Item);
Action JS
export const ADD_TODO = "GET_LIST";
export const getList = () => ({
type: "GET_LIST"
});
export default getList;
Reducer JS
const Reducer = (state = [], action) => {
switch (action.type) {
case "GET_LIST":
return [
...state,
{
list: action
}
];
default:
return state;
}
};
export default Reducer;
Sagas JS
import { put, takeLatest, all, call } from "redux-saga/effects";
function* fetchNews() {
const json = yield fetch(
"https://api.themoviedb.org/3/movie/550?api_key=258ca659445121cb5d52f31961635ba7"
).then(response => response.json());
yield put({ type: "GET_LIST", json: json.articles });
}
function* actionWatcher() {
yield takeLatest("GET_LIST", fetchNews);
}
export default function* rootSaga() {
yield all([actionWatcher()]);
}
This API which used in the Sagas will get the list of movies. so i was to get the list of movies when Item.js components rendered. currently, it's seems infinite loop on the application
You're putting same action from saga, which you are "watching" for.
Usually, you should have some action with typeGET_LIST_REQUEST for dispatching from your component, and then, put action with type GET_LIST_SUCCESS from saga to get it in reducer.
So, your Action JS should looks like:
export const ADD_TODO_REQUEST = "GET_LIST_REQUEST";
export const ADD_TODO_SUCCESS = "GET_LIST_SUCCESS";
export const getList = () => ({
type: "GET_LIST_REQUEST"
});
export default getList;
Your Reducer
const Reducer = (state = [], action) => {
switch (action.type) {
case "GET_LIST_SUCCESS":
return {
...state,
list: action.json
};
default:
return state;
}
};
export default Reducer;
Your Saga
import { put, takeLatest, all, call } from "redux-saga/effects";
function* fetchNews() {
const json = yield fetch(
"https://api.themoviedb.org/3/movie/550?api_key=258ca659445121cb5d52f31961635ba7"
).then(response => response.json());
yield put({ type: "GET_LIST_SUCCESS", json: json.articles });
}
function* actionWatcher() {
yield takeLatest("GET_LIST_REQUEST", fetchNews);
}
export default function* rootSaga() {
yield all([actionWatcher()]);
}
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
I am creating a web app where a user chooses a restaurant on one screen and the name of the restaurant is stored and then the menu screen checks the state for the restaurant name and gets the menu items from the server for that restaurant. When I press the button in userscreen.js the first time, the state doesn't change but it does change the next time. However, when I check the state from menu.js it is still the initialized state, which is empty. How do I get the state to not change back to its original value?
Here are the files im working on:
userscreen.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setRestaurant } from '../actions/restaurant';
class Userscreen extends Component {
constructor(props){
super(props);
this.state={
r2:'',
};
this.handleClick1 = this.handleClick1.bind(this);
}
componentDidMount(){
fetch('api/rest-ratings', {
method: 'GET'
})
.then(res => res.json())
.then(body =>{
this.setState({r2: body.C})
})
}
handleClick1(event){
event.preventDefault()
this.props.setRestaurant("ChopChop");
console.log(this.props.rest)
}
render() {
return (
<div>
<img src={user_background} alt="" style= {{width: "100%", height: "auto", margin:"0auto"}}/>
<div id="btn2" onClick={this.handleClick1}>
Chop Chop
<div>
<StarRatingComponent name="ChopChop" editing={false} starCount={5} value={parseInt(this.state.r2)}/>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({
rest: state.rest,
})
export default connect(mapStateToProps, { setRestaurant })(Userscreen)
menu.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setRestaurant } from '../actions/restaurant';
class Menu extends Component {
constructor(props){
super(props);
this.state={
rest1: 'abc',
};
}
componentDidMount(){
console.log("mine",this.state.rest1)
console.log("store",this.props.rest)
}
render() {
return (
<div>
{this.state.rest}
</div>
);
}
}
const mapStateToProps = (state) => ({
rest: state.rest
})
export default connect(mapStateToProps, {setRestaurant})(Menu);
restaurant.js
export const setRestaurant = (restaurant) => dispatch => {
dispatch({
type: 'SET_RESTAURANT',
payload: restaurant
})
}
restReducer.js
const initialState = {
restaurant : ''
}
export default function(state = initialState, action ) {
switch(action.type) {
case 'SET_RESTAURANT':
return {
...state,
restaurant: action.payload,
}
default:
return state;
}
}
index.js
import { combineReducers } from 'redux';
import errorReducer from './errorReducer';
import authReducer from './authReducer';
import restReducer from './restReducer';
export default combineReducers({
errors: errorReducer,
auth: authReducer,
rest: restReducer
});
Your dispatch binder function should be defined as a simple action creator if it is going to be used like it is in export default connect(mapStateToProps, {setRestaurant})(Menu);:
export const setRestaurant = restaurant => ({
type: 'SET_RESTAURANT',
payload: restaurant
})
See defining mapDispatchToProps as an object
Also note that the mapStateToProps function passes the returned object via the props (as the name suggests) so using those passed in props should be done through this.props (instead of this.state).
return (
<div>{this.props.rest.restaurant}</div>
)
I am trying to map an action to props however however I'm getting an error:
TypeError: _this2.props.updateUsername is not a function
How does one successfully map redux actions to props and call the function successfully? I havnt seen this error pop up in any other stackoverflow question/answers is it a simple mistake? Could it be a wrong setup of redux in .index or .app?
I have tried:
- importing without using default export
- having different formats of mapDispatchToProps (eg without using bindactioncreators)
- fixing typos
Component:
import { updateUsername } from "../../actions/user-actions";
import React, { Component } from "react";
import { InputText } from "primereact/inputtext";
import { Button } from "primereact/button";
import { Password } from "primereact/password";
import "./UserLogin.css";
import { connect } from "react-redux";
import { bindActionCreators } from 'redux'
export class UserLoginPage extends Component {
constructor(props) {
super(props);
this.state = { //used to be using states so ill leave these here for now
username: "",
password: "",
renderTryAgain: false
};
this.checkLoginDetails.bind(this.checkLoginDetails);
}
async checkLoginDetails() {
...
}
render() {
const usernameBox = (
<InputText
...
value={this.props.username}
onChange={e => this.props.updateUsername(e.target.value)}
/>
);
const passwordBox = (
<Password
...
/>
);
const loginButton = (
<Button
...
/>
);
return (
<header className="User-login">
<p>Dashboard User Login</p>
<div className="p-grid">
<div className="p-col">{usernameBox}</div>
<div className="p-col">{passwordBox}</div>
<div className="p-col">{loginButton}</div>
</div>
</header>
);
}
}
const mapStateToProps = state => ({
username: state.username
});
const mapDispatchToProps = dispatch => bindActionCreators(
{
updateUsername,
},
dispatch,
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserLoginPage);
Reducers:
import { UPDATE_USERNAME} from '../actions/user-actions'
export function passReducer(state = "", {type, payload}) {
switch (type) {
case true:
return payload
default:
return state
}
}
export function usernameReducer(state = '', {type, payload}) {
switch (type) {
case UPDATE_USERNAME:
return payload.username
default:
return state
}
}
export default { passReducer, usernameReducer };
Action:
export const UPDATE_USERNAME = 'username:updateUsername'
export function updateUsername(newUsername){
return {
type: UPDATE_USERNAME,
payload: {
username: newUsername
}
}
}
export default {UPDATE_USERNAME, updateUsername}
Many Thanks
Can you check once after updating your constructor as below?
constructor(props) {
super(props);
//...
}
Don't use mapDispatchToProps. Instead just wrap all the actions you want to map inside an object and pass them as the second argument to the connect helper method.
Like this connect(mapStateToProps, { updateUsername })(UserLoginPage)
Hope this helps!