I have an issue that is the component did not re-render after action is dispatched. Even in the redux dev tool. The action and state are working as expected. But, it(App.js) just won't re-render(after action DATE_ClickChange is triggered).
Following are the codes. Please help me with this issue. Thank you.
This is index.js
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import "bootstrap/dist/css/bootstrap.min.css";
import App from "./components/App/App";
import { Provider } from "react-redux";
import store from "./store";
class Index extends Component {
render() {
return (
<Provider store={store}>
<div className="indexStyle">
<App />
</div>
</Provider>
);
}
}
ReactDOM.render(<Index />, document.getElementById("index"));
store.js
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers/rootReducer";
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
rootReducer.js
import { combineReducers } from "redux";
import weatherReducer from "./weatherReducer";
import dateReducer from "./dateReducer";
export default combineReducers({
weatherData: weatherReducer,
dateData: dateReducer
});
dateReducer.js
import { DATE_HandlChange, DATE_ClickChange } from "../actions/types";
import moment from "moment";
const initialState = {
startDate: moment()
};
export default (state = initialState, action) => {
switch (action.type) {
case DATE_HandlChange:
return {
startDate: action.payload.startDate
};
case DATE_ClickChange:
return {
...state,
startDate: action.payload.startDate
};
default:
return state;
}
};
dateAction
import { DATE_HandlChange, DATE_ClickChange } from "./types";
export const dateHandleChange = date => dispatch => {
dispatch({
type: DATE_HandlChange,
payload: {
startDate: date
}
});
};
export const dateClickChange = number => (dispatch, getState) => {
dispatch({
type: DATE_ClickChange,
payload: {
startDate: getState().dateData.startDate.add(number, "d")
}
});
};
App.js
import React, { Component } from "react";
import "./app.scss";
import "react-datepicker/dist/react-datepicker.css";
import DatePicker from "react-datepicker";
import { Button } from "reactstrap";
import Content from "../Content/Content";
import { connect } from "react-redux";
import {
dateHandleChange,
dateClickChange } from "../../actions/dateAction";
class App extends Component {
render() {
return (
<div className="mainPanel">
<div className="datePanel">
<Button
outline
onClick={() => this.clickChange(-1)}
color="info"
className="prevDate"
size="md"
>
Previous
</Button>
<DatePicker
dateFormat="YYYY/MM/DD"
selected={this.props.dateData}
onChange={date => this.props.dateHandleChange(date)}
className="dateInput"
/>
<Button
outline
onClick={() => this.props.dateClickChange(1)}
color="info"
className="nextDate"
size="md"
>
Next
</Button>
</div>
<Content />
</div>
);
}
}
const mapStateToProps = state => ({
dateData: state.dateData.startDate
});
export default connect(
mapStateToProps,
{ dateHandleChange, dateClickChange }
)(App);
I'm not completely sure here but it seems like you're modifying the state directly with this code startDate: getState().dateData.startDate.add(number, "d")
getState() doesn't return a copy of the state as far as I know. This means that you have to make a copy of it first. Thats probably why it won't re-render. Redux don't re-render when you modify the state directly. You should never modify the state directly. Treat it as immutable and make a copy first and don't mutate the state. =)
Related
React-Redux doesn't update UI when store changes.
I expect {this.props.isLogged} to get changed dynamically when Change button is clicked.
I searched tons of materials but I cannot find why the text doesn't change when button is clicked.
Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { createStore } from "redux";
import reducers from "./reducers";
import { Provider } from "react-redux";
const store = createStore(
reducers);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js
import React from "react";
import { connect } from "react-redux";
import { loginUser } from "./actions";
class App extends React.Component {
changeText = () => {
this.props.loginUser();
};
render() {
return (
<div>
<span>{this.props.isLogged}</span>
<button onClick={this.changeText}>Change</button>
</div>
);
}
}
const mapStateToProps = (state /*, ownProps*/) => {
return {
isLogged: state.isLogged
};
};
const mapDispatchToProps = { loginUser };
export default connect(mapStateToProps, mapDispatchToProps)(App);
./src/reducers/index.js
import { combineReducers } from "redux";
import { LOGIN_USER } from "../actions";
function userReducer(state = { isLogged: false }, action) {
switch (action.type) {
case LOGIN_USER:
return { isLogged: !state.isLogged };
default:
return state;
}
}
const reducers = combineReducers({
userReducer
});
export default reducers;
./src/actions/index.js
export const LOGIN_USER = "LOGIN_USER";
export function loginUser(text) {
return { type: LOGIN_USER };
}
Check this out..
https://codesandbox.io/s/react-redux-doesnt-update-ui-vj3eh
const reducers = combineReducers({
userReducer //It is actually userReducer: userReducer
});
As you assiging your UserReducer to userReducer prop, you will have to fetch the same way in mapStateToProps
const mapStateToProps = (state /*, ownProps*/) => {
return {
isLogged: state.userReducer.isLogged
};
};
Also, isLogged prop is a Boolean variable.. So you are gonna have to use toString().
<span>{this.props.isLogged.toString()}</span>
Since you are using combineReducers, the isLogged boolean lives in state.userReducer.isLogged.
Consider changing combineReducers to combineReducers({ user: userReducer }) and accessing the flag with state.user.isLogged.
I am new to Redux and was learning how to use it with React. Basically, I did everything correctlyin terms of setting up Redux with react app but when I click on button increment I expect displaying counter to increment by one. But when I do that nothing happens and, certainly, I have checked dispatch and action being sent but basically all is ok. Thus I truly need your help guys here is the code I am using:
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import { createStore } from "redux";
import reducer from "./store/reducer";
import { Provider } from "react-redux";
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
registerServiceWorker();
reducer.js
const initialState = {
counter: 0
};
const reducer = (state = initialState, action) => {
if (action.type === "INCREMENT") {
return { counter: state.counter + 1 };
}
return state;
};
export default reducer;
counter.js
import React, { Component } from "react";
import { connect } from "react-redux";
import CounterControl from "../../components/CounterControl/CounterControl";
import CounterOutput from "../../components/CounterOutput/CounterOutput";
class Counter extends Component {
render() {
return (
<div>
<CounterOutput value={this.props.ctr} />
<CounterControl
label="Increment"
clicked={() => this.props.onIncrement}
/>
</div>
);
}
}
const mapStateToProps = state => {
return { ctr: state.counter };
};
const mapDispatchToProps = dispatch => {
return {
onIncrement: () => dispatch({ type: "INCREMENT" })
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
Did you register your reducer with store?
If not, please do that.
I believe this thing should not return string:
const mapStateToProps = state => {
return { ctr: state.counter };
};
Hello i am just starting to learn redux and am currently having a problem, i have an api i want to get information from and use it in different components i would appreciate if you help me
import React from 'react';
import { render } from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from "redux-thunk";
import { createLogger } from "redux-logger";
import { BrowserRouter} from "react-router-dom";
import Reducer from './Reducers';
import App from './App';
import fetchSimcards from './Actions/fetchSimcards';
const middleware = [ thunk ];
middleware.push( createLogger() );
const store = createStore(
Reducer
applyMiddleware(...middleware),
);
import * as serviceWorker from './serviceWorker';
store.dispatch(fetchSimcards());
render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
and this is my action file
import * as type from '../Constans/ActionTypes';
export const ReceiveSimcards = Simcards => ({
type: type.RECEIVE_SIMCARDS,
Simcards
});
this is my reducer file
import { combineReducers } from "redux";
const Simcards = ( state = {}, action ) => {
console.log( state, action );
return state;
};
export default combineReducers({
Simcards
});
this is my container file for simcards
import React, {Component} from 'react';
import SimcardList from "../Component/SimcardList";
import { connect } from "react-redux";
class SimcardContainer extends Component {
render() {
const Simcards = this.props;
return (
<div>
<SimcardList title={"Simcards"} />
<div className="TableNumberItem">{Simcards.SimCardNumber}</div>
<div className="TableNumberItem">{Simcards.SimCardDescription}</div>
<div className="TableNumberItem">{Simcards.TeammatePrice}</div>
</div>
);
}
}
export default connect()(SimcardContainer);
and i want show this container in home page
With redux, you should call all API and handling logic code in action.
Example with action fetchAPI:
export const fetchAPI = () = async dispatch => {
let response = null;
try {
response = await axios.get('api/...')
// Example use axios
dispatch(fetchSuccess(response.data))
// To handle in reducer with redux
} catch (error) {
... Handle error here
}
}
const fetchSuccess = data => ({
type: FETCH_SUCCESS,
data: response.data
})
And in your component, you can use connect to get state and action:
import { bindActionCreators } from 'redux';
import React, { Component } from 'react';
import SimcardList from "../Component/SimcardList";
import { connect } from "react-redux";
import * as _Actions from '../../action/index'
class SimcardContainer extends Component {
componentDidMount(){
const { fetchAPI } = this.props.actions;
**fetchAPI();** // Call API here
}
render() {
const { stateReducer} = this.props;
console.log(stateReducer)
// Here, you will see data that you handled in reducer
// with action type FETCH_SUCCESS
// You should remember data that you fetch from API is asynchronous,
// So you should check like that `data && {do any thing heree}`
return (
<div>
<SimcardList title={"Simcards"} />
<div className="TableNumberItem">{Simcards.SimCardNumber}</div>
<div className="TableNumberItem">{Simcards.SimCardDescription}</div>
<div className="TableNumberItem">{Simcards.TeammatePrice}</div>
</div>
);
}
}
const mapStateToProps = state => ({
stateReducer: state
})
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(_Actions, dispatch)
})
export default connect(mapStateToProps, mapDispatchToProps)(SimcardContainer)
I'm having trouble retrieving data from the Redux store. Redux logger is showing the data but can't seem to get it to render. Below is the code for my container component and my action/reducer:
//COMPONENT:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchGrade } from '../../modules/smiles';
class Main extends Component {
componentDidMount() {
this.props.fetchSmile();
console.log(this.props);
}
render() {
const { smiles } = this.props;
return (
<div>
<h1>This is the Main Component</h1>
</div>
);
}
}
const mapStateToProps = state => {
return { smiles: state.smiles };
};
const mapDispatchToProps = dispatch => {
return {
fetchSmile: params => dispatch(fetchGrade(params))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Main);
//ACTION/REDUCER:
import axios from 'axios';
const ADD_GRADE = 'SMILES/ADD_GRADE';
export function reducer(state = {}, action) {
switch (action.type) {
case ADD_GRADE:
return {
...state,
grade: action.payload
};
default:
return state;
}
}
export const fetchGrade = () => {
return dispatch => {
axios
.get('/api/test')
.then(res => dispatch({ type: ADD_GRADE, payload: res.data }));
};
};
//STORE:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import logger from 'redux-logger';
import reducer from '../modules';
let store;
export function configureStore(state: {}) {
if (!store) {
store = createStore(
reducer,
state,
composeWithDevTools(applyMiddleware(logger, thunk))
);
}
return store;
}
//INDEX.JS:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import App from './components/App';
import registerServiceWorker from './registerServiceWorker';
import { configureStore } from './store';
window.store = configureStore();
render(
<Provider store={window.store}>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
I really don't know if this is complicated or an easy fix. I feel like I'm doing everything right but no luck.
You named your reducer, reducer:
export function reducer(state = {}, action) {
Seems that you forgot to access it from your reducer object. it should be something like this:
const mapStateToProps = state => {
return { smiles: state.reducer.smiles };
};
I am new to redux and I am trying to build a simple Hello World to try out this library. However, I am having trouble with getting the value in the Home component. The two buttons should trigger two different changes. I think the errors must have something to do with the connect method. After hours of research, I still cannot figure out why it does not work. Thank you in advance.
Below is my code:
Home.js -> component
import React from "react";
import { connect } from "react-redux";
import * as actionCreators from "../actions/display.js";
import { bindActionCreators } from "redux";
const Home = props => {
return (
<div>
Message:
<h1>{props.message}</h1>
<button onClick={props.sayHi}>SayHI</button>
<button onClick={props.sayHello}>Say Hello</button>
</div>
);
};
function mapStateToProps(state) {
return { ...state };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
...actionCreators
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
App.js
import React from "react";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import createHistory from "history/createBrowserHistory";
import { Route } from "react-router";
import {
ConnectedRouter,
routerReducer,
routerMiddleware
} from "react-router-redux";
import Home from "./components/Home";
import reducers from "./reducers/reducer"; // Or wherever you keep your reducers
// Create a history of your choosing (we're using a browser history in this case)
const history = createHistory();
// Build the middleware for intercepting and dispatching navigation actions
const middleware = routerMiddleware(history);
// Add the reducer to your store on the `router` key
// Also apply our middleware for navigating
const store = createStore(
combineReducers({
...reducers,
router: routerReducer
}),
applyMiddleware(middleware)
);
const App = () => (
<Provider store={store}>
{/* ConnectedRouter will use the store from Provider automatically */}
<ConnectedRouter history={history}>
<Route path="/" component={Home} />
</ConnectedRouter>
</Provider>
);
export default App;
reducer.js
import { SAY_HELLO, SAY_HI } from "../constants";
const initialState = {
message: "Mark"
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SAY_HELLO:
return { ...state, message: "Hello Mark" };
case SAY_HI:
return { ...state, message: "Hi Mark" };
default:
return state;
}
};
export default reducer;
actions/display.js
import { SAY_HELLO, SAY_HI } from "../constants";
export const sayHello = () => ({
type: SAY_HELLO
});
export const sayHi = () => ({
type: SAY_HI
});
constants.js
export const SAY_HELLO = "SAY_HELLO";
export const SAY_HI = "SAY_HI";
Update:
I figured a working solution for my code but not an ideal one. I change state=>({message:state.message}) to state=>state which means now my component subscrubes to the global state. I also change{props.message} to {props.defaultmessage} in the hi tag on Home.js. Below is the updated code.
import React from "react";
import { connect } from "react-redux";
import { sayHello, sayHi } from "../actions/display.js";
const Home = props => {
return (
<div>
Message:
{console.log(props.default.message)}
<h1>{props.default.message}</h1>
<button onClick={props.sayHi}>SayHI</button>
<button onClick={props.sayHello}>Say Hello</button>
</div>
);
};
export default connect(state => state, {
sayHello,
sayHi
})(Home);
The problem is in that part of your code:
const store = createStore(
combineReducers({
...reducers,
router: routerReducer
}),
applyMiddleware(middleware)
);
reducers variable contains reducer function, but you are using it as object here.
You should assign your reducer with a specific key in the state, for example data:
const store = createStore(
combineReducers({
data: reducers,
router: routerReducer
}),
applyMiddleware(middleware)
);
Next, message value will be available at state.data path:
function mapStateToProps(state) {
return { message: state.data.message };
}
Hoop it work!
import React from "react";
import { connect } from "react-redux";
import { sayHi, sayHello } from "../actions/display.js";
const Home = props => {
return (
<div>
Message:
<h1>{props.message}</h1>
<button onClick={props.sayHi}>SayHI</button>
<button onClick={props.sayHello}>Say Hello</button>
</div>
);
};
function mapStateToProps(state) {
return { message: state.message };
}
export default connect(mapStateToProps, { sayHi, sayHello })(Home);