Redux - the app does not re-render after dispatching - javascript

I'm new to react + redux. I encountered a problem which my app is not re-render when I dispatch an action. However, I use getState() to examine the state, it did change. I look up documents but still have no idea what the problem is. Please help me, thanks.
The code is as below
====actions.js====
export const ADD_MAIL = 'ADD_MAIL';
export const DEL_MAIL = 'DEL_MAIL';
export function addMail(email) {
return {
type: ADD_MAIL,
email
}
}
export function delMail(id) {
return {
type: DEL_MAIL,
id
}
}
====reducers.js====
import { combineReducers } from 'redux'
import { ADD_MAIL, DEL_MAIL } from '../actions/actions'
import MAILS from '../data'
function emails(state = MAILS, action) {
switch (action.type) {
case ADD_MAIL:
console.log("ADD_MAIL");
return [
action.email,
...state
];
case DEL_MAIL:
let idx = state.length;
let i = 0;
// Find the target mail
while(idx--) {
if (state[idx] && state[idx].serialNo === action.id)
i = idx;
}
let arr1 = state.slice(0, i);
let arr2 = state.slice(i + 1);
let newList = arr1.concat(arr2);
console.log("DEL_MAIL");
return newList;
default:
return state;
}
}
const rootReducer = combineReducers({
emails
});
export default rootReducer;
====main.js====
import React from 'react'
import { render } from 'react-dom'
import { Link } from 'react-router'
import { connect } from 'react-redux'
import { createStore } from 'redux'
import { addMail, delMail } from './actions/actions'
import rootReducer from './reducers/reducers'
import * as btn from './module/button'
import * as module from './module/module'
var store = createStore(rootReducer);
class Inbox extends React.Component {
constructor(props) {
super(props);
this.state = {
searchText: ''
}
this.handleUserInput = this.handleUserInput.bind(this);
this.deleteMail = this.deleteMail.bind(this);
this.sendMail = this.sendMail.bind(this);
}
handleUserInput(searchText) {
this.setState({
searchText: searchText
});
}
deleteMail(obj) {
store.dispatch(delMail(obj.serialNo));
console.log(store.getState());
// This displays the correct new state in console after dispatch
}
sendMail(newMail) {
store.dispatch(addMail(newMail));
console.log(store.getState());
// This displays the correct new state in console after dispatch
}
render() {
let mails = [];
let search = this.state.searchText.toUpperCase();
let emails = this.props.emails;
emails.map(mail => {
if (mail.from.toUpperCase().indexOf(search) !== -1)
mails.push(mail);
});
let sender = (mails.length === emails.length) ? "all" : this.state.searchText;
return (
<div className="main">
<div className="toolbar">
<span>You have {mails.length} message from {sender}.</span>
<module.SearchInput searchText={this.state.searchText} onUserInput={this.handleUserInput} />
<div className="functions">
<btn.AddButton />
</div>
</div>
<div className="mailList">
{mails.map(mail => (
<div className="item" key={mail.serialNo}>
<div className="info sender">
From: {mail.from}
</div>
<div className="info date">
{mail.date}
</div>
<div className="info subject">
Subject: {mail.subject}
</div>
<div className="functions">
<btn.ReadButton serialNo={mail.serialNo} />
<btn.DeleteButton serialNo={mail.serialNo} deleteMail={this.deleteMail} />
</div>
</div>
))}
</div>
<module.NewMailInput sendMail={this.sendMail} />
</div>
);
}
}
function mapStateToProps(state) {
return {
emails: state.emails
};
}
export default connect(mapStateToProps)(Inbox);
====app.js====
import React from 'react'
import { render } from 'react-dom'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import { Menu } from './menu'
import { Mail } from './main'
import Inbox from './main'
import rootReducer from './reducers/reducers'
var store = createStore(rootReducer);
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div style={{height: '100%'}}>
<Menu />
{this.props.children}
</div>
);
}
}
class Home extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Inbox />
);
}
}
render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="/inbox" component={Inbox} />
<Route path="/message/:mailSerial" component={Mail} />
</Route>
</Router>
</Provider>,
document.getElementById('app-container'))

Try this:
import React from 'react'
import { render } from 'react-dom'
import { Link } from 'react-router'
import { connect } from 'react-redux'
import { addMail, delMail } from './actions/actions'
import * as btn from './module/button'
import * as module from './module/module'
class Inbox extends React.Component {
constructor(props) {
super(props);
this.state = {
searchText: ''
}
this.handleUserInput = this.handleUserInput.bind(this);
this.deleteMail = this.deleteMail.bind(this);
this.sendMail = this.sendMail.bind(this);
}
handleUserInput(searchText) {
this.setState({
searchText: searchText
});
}
deleteMail(obj) {
this.props.delMail(obj.serialNo); //Call the delMail action
console.log(store.getState());
}
sendMail(newMail) {
this.props.addMail(newMail); //Call the addMail action
console.log(store.getState());
}
render() {
let mails = [];
let search = this.state.searchText.toUpperCase();
let emails = this.props.emails;
emails.map(mail => {
if (mail.from.toUpperCase().indexOf(search) !== -1)
mails.push(mail);
});
let sender = (mails.length === emails.length) ? "all" : this.state.searchText;
return (
<div className="main">
<div className="toolbar">
<span>You have {mails.length} message from {sender}.</span>
<module.SearchInput searchText={this.state.searchText} onUserInput={this.handleUserInput} />
<div className="functions">
<btn.AddButton />
</div>
</div>
<div className="mailList">
{
mails.map(mail => (
<div className="item" key={mail.serialNo}>
<div className="info sender">From: {mail.from}</div>
<div className="info date">{mail.date}</div>
<div className="info subject">Subject: {mail.subject}</div>
<div className="functions">
<btn.ReadButton serialNo={mail.serialNo} />
<btn.DeleteButton serialNo={mail.serialNo} deleteMail={this.deleteMail} />
</div>
</div>
))
}
</div>
<module.NewMailInput sendMail={this.sendMail} />
</div>
);
}
}
function mapStateToProps(state) {
return {
emails: state.emails
};
}
//Connect the component to re-render when state change and
// makes the emails and actions to be available through this.props
export default connect(mapStateToProps, {delMail, addMail})(Inbox);
//To connect Mail component which I suppose that is in another file
function mapStateToProps(state) {
return { emails: state.emails };
}
export default connect(mapStateToProps, {})(Mail);

In your main.js file, you have made a Inbox component. That is a React Component but not a Redux Component.
You have to do something like this while exporting Inbox component.
module.exports = connect((store)=> {
return {emails: store.emails}
})(Inbox)

You have 2x stores: one in main.js and one in app.js. Remove the one in main.js and update the calls to dispatch to use the one passed as props:
class Inbox extends React.Component {
...
deleteMail(obj) {
this.props.dispatch(delMail(obj.serialNo));
}
...
}

Related

this.props.history.push is not working while using HOC for logger

I have written below code,
1.I want to use Connect for storing usernamein local storage
2.I am using HOC component for logging purpose (callInfoLogger and callErrorLogger)
3.If I use connect and HOC together then this.props.history.push is not working (Its not redirecting to MyDashboard page)
Could you please let me know what do I need to do to fix the code?
App.js
import { BrowserRouter as Router, Route, Switch, } from "react-router-dom";
class App extends Component {
render() {
return (
<Router>
<Switch>
<Route path="/login" component={Login} />
<Route path="/dashboard" component={MyDashboard} />
</Switch>
</Router>
)
}
}
export default App;
Login.js
import React, { Component } from 'react';
import { withRouter } from "react-router-dom";
import { connect } from 'react-redux';
import HighLevelComponent from './HighLevelComponent';
class Login extends Component {
state = {
username: '',
password: '',
loginsuccess: true
}
callOnSubmit = (e) => {
e.preventDefault();
this.props.callErrorLogger("Inside call on Submit");
if (this.state.loginsuccess === true) {
this.props.callInfoLogger("Calling Info logger ");
this.props.onLoginSuccess(this.state.username);
this.props.history.push('/dashboard');
}
};
render() {
return (
<body>
<form className="login-form" onSubmit={this.callOnSubmit}>
<input
type="text" onChange={e => {
this.setState({
...this.state,
username: e.target.value
})
}}
/>
<input type="password"
onChange={e => {
this.setState({
...this.state,
password: e.target.value
})
}}
/>
<input type="submit" className="btnSbumit" value="LOG IN" />
</form>
</body>
)
}
}
const mapDispatchToProps = dispatch => {
return {
onLoginSuccess: (username) => dispatch({ type: "LOGIN_SUCCESS", username: username })
}
}
export default withRouter(HighLevelComponent(connect(null, mapDispatchToProps)(Login)));
MyDashboard.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class MyDashboard extends Component {
render() {
return (
<body>
<h1>Welcome to React.. {this.props.username}</h1>
</body>
)
}
}
const mapStateToProps = state => {
return {
username: state.username
}
}
export default connect(mapStateToProps, null)(MyDashboard);
HighLevelComponent.js
import React from 'react';
const HighLevelComponent = (WrapperComponent) => {
class NewComponent extends React.Component {
callInfoLogger = (infomsg) => {
console.info(infomsg);
}
callErrorLogger = (errmsg) => {
console.error(errmsg);
}
render() {
return <WrapperComponent callInfoLogger={this.callInfoLogger} callErrorLogger={this.callErrorLogger} />
}
}
return NewComponent;
}
export default HighLevelComponent;
In the HOC names HighLevelComponent pass the props to the wrapper component as follows:
const HighLevelComponent = (WrapperComponent) => {
class NewComponent extends React.Component {
callInfoLogger = (infomsg) => {
console.info(infomsg);
}
callErrorLogger = (errmsg) => {
console.error(errmsg);
}
render() {
return <WrapperComponent callInfoLogger={this.callInfoLogger} callErrorLogger={this.callErrorLogger} {...props} />
}
}
return NewComponent;
}
Please note the {...props} on the wrapper component. In this way all the props will be further passed.

mapStateToProps is not running

I am new to react and I can't debug why mapStateToProps is not running. Pls see the last function in login.js.
I have added alert statements in my mapStateToProps function but its just not running. Dont know where to look for problems.
store.js
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from '../reducers';
export const store = createStore(
rootReducer,
applyMiddleware(thunkMiddleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
index.js:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './helpers';
import { App } from './App';
import { configureFakeAPI } from './helpers';
configureFakeAPI();
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);
App.js
import React from 'react';
import { Router, Route } from 'react-router-dom';
import { connect } from 'react-redux';
import { PrivateRoute } from './PrivateRoute.js';
import { history } from './helpers';
import { alertActions } from './actions';
import { HomePage } from './components/HomePage';
import LoginPage from './components/LoginPage';
import { RegisterPage } from './components/RegisterPage';
export class App extends React.Component {
constructor(props) {
super(props);
const { dispatch } = this.props;
history.listen((location, action) => {
});
}
render() {
const { alert } = this.props;
return (
<div className="container">
<div className="col-sm-8 col-sm-offset-2">
<LoginPage />
</div>
</div>
);
}
}
function mapStateToProps(state) {
const { alert } = state;
return {
alert
};
}
export default connect(mapStateToProps)(App);
LoginPage.js
import React from 'react';
import {Component} from 'react';
import {Link} from 'react-router-dom';
import {connect} from 'react-redux';
import {userActions} from '../actions';
import {userConstants} from "../constants";
class LoginPage extends Component {
constructor(props) {
super(props);
// reset login status
this.state = {
username: '',
password: '',
submitted: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = (e) => {
let formControlName = e.target.name;
let value = e.target.value;
this.setState({
[formControlName]: value
});
};
handleSubmit = (e) => {
e.preventDefault();
this.sendTheAlert();
}
render() {
const {username, password, submitted} = this.state;
return (
<div className="col-md-6 col-md-offset-3">
<i>{JSON.stringify(this.state)}</i>
<h2>Login</h2>
<form name="form" onSubmit={this.handleSubmit}>
<div className={'form-group' + (submitted && !username ? ' has-error' : '')}>
<label htmlFor="username">Username</label>
<input type="text" className="form-control username" name="username"
onChange={this.handleChange}/>
{submitted && !username &&
<div className="help-block">Username is required</div>
}
</div>
<div className={'form-group' + (submitted && !password ? ' has-error' : '')}>
<label htmlFor="password">Password</label>
<input type="password" className="form-control" name="password" onChange={this.handleChange}/>
{submitted && !password &&
<div className="help-block">Password is required</div>
}
</div>
<div className="form-group">
<button className="btn btn-primary">Login</button>
<a className="btn btn-link">Register</a>
</div>
</form>
</div>
);
}
}
function mapStateToProps(state) {
// const { todos } = state;
// return { todoList: todos.allIds };
return {};
}
// function mapDispatchToProps(dispatch) {
// alert();
// return ({
// sendTheAlert: () => {
// dispatch(userConstants.LOGIN_REQUEST)
// }
// })
// }
const mapDispatchToProps = (dispatch) => ({ <====== NOT RUNNING
sendTheAlert(coin) {
debugger;
alert();
dispatch(userConstants.LOGIN_REQUEST) }
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage);
I assume that it is mapDispatchToProps that isnt working, right?
Try this
...
const mapDispatchToProps = (dispatch) => (
return {
sendTheAlert(coin) {
debugger;
alert();
return dispatch(userConstants.LOGIN_REQUEST) }
})
A sample of how to structure mapDispatchToProps would be (from https://react-redux.js.org/using-react-redux/connect-mapdispatch). Be mindful of the return statement.
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' })
}
}
Everything is fine I was just calling this.sendTheAlert() wrong. it should be this.props.sendTheAlert()

How to change this React(Redux) Component so it works by itself

I learn React Redux and have some issues.
In the code below I do mapStateToProps to listen for changes and I use the mapDispatchToProps to send a notification. When there is a change in the Store I send a Notification. In order for this to work I have to put this Helper in the App.jsx render() method even do this Helper Component code below does not add anything to the App.jsx. I learn React and wonder how I can change this Notefication.js so it listen for mapStateToProps and can do mapDispatchToProps without adding it to App.jsx render().
It just feels so unnecessary to have to add this Component to the App.jsx render only to get the mapStateToProps mapDispatchToProps working?
Notefication.js
import { connect } from "react-redux";
import 'react-redux-notify/dist/ReactReduxNotify.css';
import { createNotification, NOTIFICATION_TYPE_SUCCESS } from 'react-redux-notify';
import { Notify } from 'react-redux-notify';
import React, { Component } from "react";
const mySuccessNotification = {
message: 'You have been logged in!',
type: NOTIFICATION_TYPE_SUCCESS,
duration: 0,
canDismiss: true,
icon: <i className="fa fa-check" />
}
class Helper extends Component {
senNotification() {
const { createNotification } = this.props;
createNotification(mySuccessNotification);
}
render() {
return (
<div>
<Notify />
{this.senNotification()}
</div>
)
}
}
const mapDispatchToProps = dispatch => ({
createNotification: (config) => {
dispatch(createNotification(config))
},
})
const mapStateToProps = state => {
return { badword: state.rootReducer.badword };
};
export default connect(mapStateToProps, mapDispatchToProps)(Helper)
App.jsx
import React, { Component } from "react";
import List from "./List.jsx";
import Form from "./Form.jsx";
import Helper from "../components/Notification";
class App extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const { addToast } = this.props.actions;
addToast({ text: "Hello, World!" });
}
render() {
return (
<main>
<div className="row mt-5">
<div className="col-md-4 offset-md-1">
<h2>Articles</h2>
<List />
</div>
<div className="col-md-4 offset-md-1">
<h2>Add a new article</h2>
<Form />
</div>
</div>
<Helper/>
</main>
);
}
}
export default App;
React only renders new, if the component values changes. So if the reducer is connected and you load some values which changes, the component renders and the function will be triggered.
I would not trigger the function this.senNotification in the render function I would prefer to use componentDidUpdate to trigger the function.
import { connect } from "react-redux";
import 'react-redux-notify/dist/ReactReduxNotify.css';
import { createNotification, NOTIFICATION_TYPE_SUCCESS } from 'react-redux-notify';
import { Notify } from 'react-redux-notify';
import React, { Component } from "react";
const mySuccessNotification = {
message: 'You have been logged in!',
type: NOTIFICATION_TYPE_SUCCESS,
duration: 0,
canDismiss: true,
icon: <i className="fa fa-check" />
}
class Helper extends Component {
componentDidUpdate (prevProps) {
const { createNotification, badword } = this.props
if(prevProps.badword !== badword) {
this.senNotification()
}
}
senNotification() {
const { createNotification } = this.props;
createNotification(mySuccessNotification);
}
render() {
return <Notify />
}
....
}

Cannot access URL path components with react-router

I'm doing this in App.js:
<Route path="/discover/:query" component={Discover}/>
Then I'm trying to access the URL parameters in Discover:
componentDidMount() {
alert(this.props.match); // undefined
}
I've tried many other ways, like: alert(this.match); or alert(match);. They are all undefined!
What am I doing wrong? I'm following the docs as far as I can tell.
I'm running React version 16.3.2.
EDIT:
All of App.js:
import React, { Component } from 'react';
import './styles/app.css';
import { Route } from 'react-router-dom';
import Welcome from './welcome';
import Discover from './discover';
import MySearches from './my-searches';
import Login from './login';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
// import Database from './database';
class App extends Component {
constructor(props) {
super(props)
this.state = {
title: '',
}
}
render() {
return (
<div className="App">
<header className="App-header">
{/* <Route path="/" component={Login}/> */}
<Route exact path="/" component={Welcome}/>
<Route path="/discover/:query" component={Discover}/>
<Route path="/my-searches" component={MySearches}/>
{/* <Route path="/database" component={Database}/> */}
</header>
</div>
);
}
}
export default App;
All of discover.js:
import React from 'react';
import Map from './map';
import Search from './search';
import SentimentContainer from './sentiment';
import { Steps } from 'intro.js-react';
import ButtonImportant from '../components/button-important';
import { modelInstance } from '../model/model';
import DrawingAnimation from '../components/intro-drawing-animation'
import 'intro.js/introjs.css';
import '../styles/discover.css';
import '../styles/search.css';
class DiscoverContainer extends React.Component {
constructor(props){
super(props);
this.state = {
status: 'INITIAL',
//Intro.js
initialStep: 0,
introState: 'INITIAL',
steps: [
{
element: '.sentiment-pie',
intro: "This app shows people's sentiment towards subjects based on tweets.</br> <h5><ButtonImportant><a target='_blank' href='https://en.wikipedia.org/wiki/Sentiment_analysis'>What is Sentiment Analysis?</a></ButtonImportant></h5> ",
},
{
element: '#searchInput',
intro: 'You can search for subjects here',
},
{
element: '.date',
intro: 'You can look for tweets in the past 7 days',
},
{
element: '.location',
intro: 'Type in place names or interact with the map to look for tweets in specific locations',
},
{
element: '.sentiment-tweet',
intro: 'The tweets will be displayed here',
},
{
element: '.createPDF',
intro: 'Finally you can export the data in a PDF',
},
],
}
}
componentDidMount() {
console.log("props:");
console.log(this.props.locationl); // undefined
}
handleStatusChange = newStatus => {
this.setState({
status: newStatus
});
}
onExit = () => {
this.setState(() => ({
stepsEnabled: false,
introState: 'INITIAL'
}));
};
toggleSteps = () => {
this.setState(prevState => ({ stepsEnabled: !prevState.stepsEnabled }));
// this.onAfterChange(prevState);
};
onAfterChange = nextStepIndex => {
if (nextStepIndex === 0 && this.state.status !=='LOADED') {
this.setState({
status: 'LOADED'
})
// this.step.updateStepElement(nextStepIndex);
}
else if (nextStepIndex === 3) {
this.setState({
introState: 'MAP'
})
// this.step.updateStepElement(nextStepIndex);
}
else{
this.setState({
introState: 'INITIAL'
})
}
}
render () {
const { stepsEnabled, steps, initialStep} = this.state;
let media = null;
switch (this.state.introState) {
case 'INITIAL':
media = null
break;
case 'MAP':
media = <DrawingAnimation />
break;
}
return (
<div className="container-discover">
<Steps
className='intro-steps'
enabled={stepsEnabled}
steps={steps}
initialStep={initialStep}
onExit={this.onExit}
onAfterChange={this.onAfterChange}
/>
<div className="container-discover-top">
<div className='map'>
<Map/>
</div>
<div className="intro">
{media}
<ButtonImportant size="small" text='Explain App' toggleSteps={this.toggleSteps.bind(this)}/>
</div>
<div className='container-search'>
<Search handleStatusChange={this.handleStatusChange}/>
</div>
</div>
<div className="container-discover-bottom">
<SentimentContainer status={this.state.status}/>
</div>
</div>
);
}
}
export default DiscoverContainer;
You need to use the withRouter HOC to access the match props:
export default withRouter(DiscoverContainer);
...
console.log(this.props.match);

Error in connecting components in redux

I get an error saying
You must pass a component to the function returned by connect.
Instead received undefined.
I don't know why this is happening. It works with my Checkout.js component, but not my Store.js component. Why is it working for Checkout but not Store? I want to have access to {total} like I do in the Checkout component.
This is my container Store.js
import { connect } from 'react-redux';
import { getItems, getCurrency, getTotal} from '../ducks/cart';
import Store from '../components/Store';
const mapStateToProps = (state, props) => {
return {
total: getTotal(state, props)
}
}
export default connect(mapStateToProps)(Store);
And this is my component Store.js
import React, {Component} from 'react';
import { PropTypes } from 'react';
import Home from '../components/Home';
import Cart from '../containers/Cart';
import ProductList from '../containers/ProductList';
import Checkout from '../containers/Checkout';
class Store extends Component{
render(){
return(
<div className="container">
<div className="row">
<div className="col-md-12">
<h3>Armor and Weapon Store</h3>
<h4 className="badge badge-warning margin-right">Gold: </h4>
</div>
</div>
<div className="row">
<div className="col-md-8">
<ProductList />
</div>
<div className="col-md-4">
<Cart />
<Checkout />
</div>
</div>
</div>
);
}
}
Store.propTypes = {
total: PropTypes.number,
}
export default Store;
Here is the container Checkout.js that works
import { connect } from 'react-redux';
import { getItems, getCurrency, getTotal} from '../ducks/cart';
import Checkout from '../components/Checkout/Checkout';
const mapStateToProps = (state, props) => {
return {
total: getTotal(state, props)
}
}
export default connect(mapStateToProps)(Checkout);
And the component Checkout.js
import React, { PropTypes } from 'react';
import {Component} from 'react';
import Home from '../components/Home.js';
class Checkout extends Component{
buttonClick = () => {
let { total } = this.props;
var home = new Home
var max = home.getMax()
console.log(total);
console.log(max);
if (max >= total) {
max = max - total;
console.log(max);
}
else {
console.log('Not enough gold!');
}
}
render() {
return(
<div>
<button type="button" onClick={this.buttonClick}> Checkout </button>
</div>
);
}
}
Checkout.propTypes = {
total: PropTypes.number,
}
export default Checkout;

Categories