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>
)
Related
I have two widgets called "search widget" and "Name Widget", and I build those widget using webpack. I want to pass data from search widget to name widget. This is not like passing data from one component to another component. Below image shows that widgets.
This is the script tags I used.
action.js
export const addReminder = text => ({
type : "ADD_REMINDER",
payload : text
});
reducer.js
const initialState = {
reminders : []
};
const nameReducer = (state = initialState , action) => {
switch(action.type){
case 'ADD_REMINDER':
console.log("Name reducer action - ",action.payload);
return {
...state,
reminders: [...state.reminders, action.payload]
}
default:
return state;
}
}
export default nameReducer;
store.js
import nameReducer from '../reducers/nameReducer';
import { createStore} from 'redux';
const store = createStore(
nameReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
search component code
import { addReminder } from '../Redux/actions/nameAction';
import { connect } from 'react-redux';
class searchComponent extends Component {
constructor() {
super();
this.state = {
name: '',
};
}
onChange(e) {
this.setState({
name: e.target.value,
});
}
onSubmit(e) {
e.preventDefault();
this.props.addReminder(this.state.name);
console.log("this is the form text", this.props.reminders);
}
render () {
return (
<div>
<form>
<input
type="text"
value={this.state.name}
onChange={e => this.onChange(e)} />
<br/><br/>
<button type="submit" onClick={e => {this.onSubmit(e)}}>Submit</button>
</form>
</div>
);
}
}
const mapStateToProps = (state) => ({
reminders: state.reminders
});
export default connect(mapStateToProps, {addReminder})(searchComponent);
name component code
import React, { Component } from 'react';
import { connect } from 'react-redux';
class nameComponent extends Component {
render() {
return (
<div>
<p>{this.props.reminders}</p>
</div>
)
}
}
const mapStateToProps = (state) => ({
reminders: state.reminders
});
export default connect(mapStateToProps)(nameComponent);
What I want is, when I enter something in search component and press submit button, the search value must display in the name component. But in the name component this.props.reminders didn't get any result. Do I need to pass variable in the script tag or How can I pass data from one widget to another widget?
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!
I can't understand why my Redux props are showing empty on the React component. This is the screenshot of the Redux state:
This is the component that visualizes. It is a child of a parent component that fetches the data and runs a for loop through the array of elements.
import React, { Component } from 'react'
import { connect } from "react-redux";
import PropTypes from "prop-types";
class ResourceCard extends Component {
render() {
const { resource } = this.props;
return (
<div className="resource-card">
<div className="resource-card-header">
<span className="res-span-header">{resource.header}</span>
</div>
<div className="resource-card-image">
<img src={resource.image} alt={resource.header} width='300px' />
</div>
<div className="resource-card-desc">
<span className="res-span-desc">{resource.description}</span>
</div>
<div className="resource-card-link">
<span className="res-span">{resource.website}</span>
</div>
</div>
)
}
}
ResourceCard.propTypes = {
resource: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
resource: state.resource
});
export default connect(
mapStateToProps,
{}
)(ResourceCard);
While the css is being rendered properly, and there are no errors in the console, the content itself is not there:
What could possibly be wrong here?
EDITED: Adding actions code:
// Get Resources
export const getResources = () => dispatch => {
axios
.get("/api/resources")
.then(res =>
dispatch({
type: GET_RESOURCES,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_RESOURCES,
payload: null
})
);
};
And the reducer:
import {
ADD_RESOURCE,
GET_RESOURCES,
DELETE_RESOURCE,
GET_RESOURCE
} from "../actions/types";
const initialState = {
resources: [],
resource: {}
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_RESOURCES:
return {
...state,
resources: action.payload
};
case GET_RESOURCE:
return {
...state,
resource: action.payload
};
case DELETE_RESOURCE:
return {
...state,
resources: state.resources.filter(resource => resource._id !== action.payload)
};
case ADD_RESOURCE:
return {
...state,
resources: [action.payload, ...state.resources]
};
default:
return state;
}
}
Parent component that runs a for-loop:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ResourceCard from './ResourceCard';
class ResourceFeed extends Component {
render() {
const { resources } = this.props;
return resources.map(resource => <ResourceCard key={resource._id} resource={resource} />)
}
}
ResourceFeed.propTypes = {
resources: PropTypes.array.isRequired
}
export default ResourceFeed;
And the component above contains this parent:
<ResourceFeed resources={resources}/>
Issue is here:
const mapStateToProps = state => ({
auth: state.auth,
resource: state.resource // <========== in this line
});
In ResourceCard component you are passing the resource props from two places, one from parent component and one from redux store, that's why.
Your component is expecting the resource value from parent, so remove this line:
resource: state.resource
I am learning react-redux, so I decided to implement what I have been learning. But I am have a bug challenge. So I console.logged this.props.users from mapStateToProps function.
I believe there's something I not doing right which I don't understand. Please an explanation in other to move on. Thanks you so much for helping out.
Here is my code.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUsers } from '../actions/userAction';
import UserList from '../components/UserList';
class UserPage extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchUsers();
}
componentDidMount() {
console.log(this.props.users);
}
render() {
return (
<div>
<h2>Users Page</h2>
<UserList users={this.props.users} />
</div>
);
}
}
const mapStateToProps = state => {
return {
users: state.userReducer.users
};
};
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () => dispatch(fetchUsers())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserPage);
So this is what I get from the chrome console - Empty arrays.
props showing empty arrays
But when I check the React DevTool and Redux DevTool, they display the expected Props and States respectively. Below are the snapshot of the dev tools
React devtool shows the correct Props
Redux devtool show the correct States and Actions
userAction.js
import axios from 'axios';
import * as types from './actionTypes';
export let fetchingUser = () => {
return {
type: types.FETCHING_USERS
};
};
export let fetchedUser = payload => {
return {
type: types.FETCHED_USER,
payload
};
};
export let fetchUser_error = () => {
return {
type: types.FETCH_USER_ERROR
};
};
export let fetchUsers = () => {
let url = 'https://eventcity.herokuapp.com/api/v1/users';
return dispatch => {
dispatch(fetchingUser());
return axios
.get(url)
.then(response => {
const users = response.data.data;
dispatch(fetchedUser(users));
})
.catch(err => {
dispatch(fetchUser_error());
});
};
};
userReducer.js
import * as types from '../actions/actionTypes';
import initialState from './initialState';
const userReducer = (state = initialState, action = {}) => {
switch (action.type) {
case types.FETCHING_USERS:
return { ...state, users: [], error: null, loading: true };
case types.FETCHED_USER:
return { ...state, users: action.payload, error: null, loading: false };
case types.FETCH_USER_ERROR:
return {
...state,
users: [],
error: { message: 'Error loading data from the API' },
loading: false
};
default:
return state;
}
};
export default userReducer;
configureStore.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../reducer/rootReducer';
const configureStore = () => {
return createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
};
export default configureStore;
rootReducer.js
import { combineReducers } from 'redux';
import userReducer from './userReducer';
const rootReducer = combineReducers({
userReducer
});
export default rootReducer;
I think you might want to check this
https://github.com/reactjs/react-redux/issues/129. Your problem is using componentDidMount and componentWillMount without having a better understanding of what they are used for.
The problem is not with redux, all you need to understand is that your fetchUsers request is async and componentDidMount function is only executed once after the component has rendered and it may so happen that the data is not present by the time componentDidMount function is executed and hence your console.log(this.props.users); return empty array, Log it in the render method and you will see the correct data
class UserPage extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchUsers();
}
render() {
console.log(this.props.users);
return (
<div>
<h2>Users Page</h2>
<UserList users={this.props.users} />
</div>
);
}
}
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.