I am working on a react project and in this project, I created a single redux store with multiple reducers
import { createStore, combineReducers } from "redux"
import HsPageDataReducer from "../reducers/HsPageDataReducer.jsx"
import HsUserDataReducer from "../reducers/HsUserDataReducer.jsx"
export default function HsGlobalStore() {
return createStore(combineReducers({
PageDataReducer: HsPageDataReducer,
UserDataReducer: HsUserDataReducer
}));
}
I am using connect function to provide state to the components
const mapStateToProps = function myMapStateToProps(state) {
return {
page_display_name: state.PageDataReducer.page.page_data.page_display_name,
page_name: state.PageDataReducer.page.page_data.page_name
}
}
const NewHsPageHadNameIdAreaBox = connect(mapStateToProps)(HsPageHadNameIdAreaBox);
const id = "page_had_name_id_area_box";
if (document.getElementById(id) !== null) {
ReactDOM.render(<Provider store={HsGlobalStore()}> <NewHsPageHadNameIdAreaBox /> </Provider>, document.getElementById(id));
}
export default NewHsPageHadNameIdAreaBox;
and this is my action handler in HsPageDataReducer
const initialState = {
page : HsPageDataObj()
}
export default function HsPageDataReducer(state = initialState, action) {
try {
switch (action.type) {
case HsPageDataActionConst.GET_PAGE_FULL_DATA_BY_PAGE_NAME:
return new HsPageGetPageFullDataAction().getData(action.pageName, state)
.then((newState) => { console.log(newState.page.page_data.page_display_name); return newState }).catch((newState) => { return state});
default:
return state;
}
} catch (e) {
return state;
}
}
initially, Everything is working perfectly
Also when I dispatch action from the component
componentDidMount() {
this.goForGetPageData();
}
goForGetPageData() {
this.props.dispatch({
pageName: "sdevpura5",
type: HsPageDataActionConst.GET_PAGE_FULL_DATA_BY_PAGE_NAME
});
}
It's getting the data
Test Data Image
But not re-rendering the component.
What I am doing wrong?
try below code maybe it will help.Because i re-rendering my component using this
resetPage() {
ReactDOM.unmountComponentAtNode(document.getElementById('addVitalSettingComponentId'));
AddVitalSettingComponent = ReactDOM.render(
<AddVitalSettingComponent />, document.getElementById('addVitalSettingComponentId')
);
}
Reducers should be pure functions and you are making api calls there :)
You need to use middlewares like redux-thunk or redux-saga.
Read some info here and check this and this codesandbox example
Related
I'm creating a simple blog using React and Redux, most for learning these two libraries. Everything is working fine, my only question is about the way the state object is structured in the application. When I go to the Redux Toolkit, I see this:
Redux Toolkit screenshot
In the state I have a post object with another post object inside it. My question is: where did I defined the state in that way (with a post object inside a post object)? Below are the content of this application:
MainPost.js
import React, { useEffect } from 'react'
import { connect, useDispatch } from 'react-redux'
import { getPostListAction } from '../store/actions/getPostListAction'
export const MainPost = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(getPostListAction())
})
return (
<div>App</div>
)
}
const mapStateToProps = state => {
return {
post: state.post
}
}
export default connect(mapStateToProps)(MainPost)
store.js
import { createStore, applyMiddleware, compose } from 'redux'
import rootReducer from './reducers/rootReducer'
import { devToolsEnhancer } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import { api } from './middleware/api'
const store = createStore(
rootReducer,
compose(
devToolsEnhancer(),
applyMiddleware(thunk),
)
)
export default store
postListReducer.js
const initialState = {
post: ''
}
export default function getPostListReducer(state = initialState, action) {
if(action.type === 'getPostList') {
return {
...state,
post: action.payload
}
} else {
return state
}
}
The first post (after state) is namespace of postListReducer
Here is how you use combineReducer to create a rootReducer:
const rootReducer = combineReducers({
post: postListReducer,
other: otherReducer
})
And to select data from the store, you do:
const mapStateToProps = state => {
return {
post: state.post.post // the first "post" (after "state") is namespace of postListReducer
}
}
Or if you don't want to write state.post.post, you can change your postListReducer to directly hold the "post" data:
const initialPost = ''
export default function getPostListReducer(state = initialPost, action) {
if(action.type === 'getPostList') {
return action.payload
} else {
return state
}
}
I have a problem, when I get my data from API and update the store the data doesn't changed. It is binded as a prop and I think it should changed, one more thing I noticed is that it doesn't call mapStateToProps after the store was updated. When i give some initial value to the store it displays it so I think it can see the store, something else is wrong obiviously but I can't figure out what.
Reducer code:
import { ADD_POST } from "../actions/addAction";
import { GET_POSTS } from "../actions/getAction";
import { DELETE_POST } from "../actions/deleteAction";
import { UPDATE_POST } from "../actions/updateAction";
import axios from "axios";
const initialState = {
posts: []
};
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case ADD_POST:
state = state.slice();
state.push(payload);
break;
case GET_POSTS:
axios
.get("http://localhost:59511/api/post?date=31-12-2019")
.then(response => {
response.data.forEach(thePost => {
state.posts = [...state.posts, thePost];
});
console.log(state.posts);
return state;
});
break;
default:
return state;
}
return state;
}
index (here I am creating my store and wrapping the app component with provider):
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { combineReducers, createStore } from "redux";
import { Provider } from "react-redux";
import postReducer from "./reducers/postsReducers";
const allReducers = combineReducers(
{
post: postReducer
},
window.devToolsExtension && window.devToolsExtension()
);
const store = createStore(allReducers);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();
Mapping it in my component like this:
const mapStateToProps = state => ({
posts: state.post.posts
});
if you guys need anything else let me know, I have a file that is a bit large so I wouldn't like to add it if it's not neccessary, I am banging my head against the wall for a couple of hours now. Thanks in advance
===EDIT===
I also mapped my action to props
const mapActionToProps = {
onDeletePost: deletePost,
onUpdatePost: updatePost,
onGetPost: getPosts
};
I have my action defined as
export const ADD_POST = "posts:addPost";
export function addPost(newTitle, newHours, newDate) {
return {
type: ADD_POST,
payload: {
id: new Date().toString(),
title: newTitle,
hours: newHours,
date: new Date().toLocaleDateString()
}
};
}
So I already have the action defined there so I am not sure I need a dispatchToAction? I am looking it up as we speak and will try to make something, just a bit confused.
==END OF EDIT==
I think that technically your problem is that your reducer returns (after all of axios) before the fetching is done. But that's not the problem you want to solve.
First of all, you have too much going on in your reducer. You shouldn't be implementing the action (fetching the data) in your reducer. I imagine in your component you're constructing an action that looks like {type: 'GET_POSTS'}, and then...dispatching it? Except you don't appear to be providing your component with a dispatch. So the action is never making it to the store. I can only assume because you haven't shown us where you're calling your action from.
You should be moving your fetching to its own async (thunk) action method:
function getPosts() {
return dispatch => {
axios
.get("http://localhost:59511/api/post?date=31-12-2019")
.then(response => {
const posts = response.data
dispatch({type: 'GET_POSTS', payload: posts})
});
}
}
And then simply add the posts to your state in your reducer:
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case GET_POSTS:
return { ...state, posts: payload }
default:
return state;
}
And then you'll have to connect the getPosts() function to your store using mapDispatchToProps. And you'll also have to use redux-thunk or this won't work at all.
You've got a good start with react-redux, but there's some gaps in your learning. You're going to need to look into async actions and redux thunk (or some other async action middleware). I'd suggest reviewing all the Redux documentation, mainly the advanced tutorials.
Your reducer is mutating state, and that's breaking the app.
In addition, you are making an AJAX call in a reducer, which is never allowed. All async logic happens outside reducers, and reducers only look at their state and action parameters to calculate the new state.
This why the first two "Essential" rules of the Redux Style Guide are Do Not Mutate State and Reducers Must Not Have Side Effects.
I'd strongly encourage you to use our new official Redux Toolkit package. Its configureStore() function sets up mutation detection by default, and it has functions like createSlice() which let you write simpler immutable update logic.
Beyond that, I'd suggest taking some more time to read through the Redux docs to understand how you are supposed to use Redux correctly.
I changed my action to be
import axios from "axios";
export const GET_POSTS = "posts:getPosts";
export function getPosts(theDate) {
return dispatch => {
axios
.get("http://localhost:59511/api/post?date=31-12-2019")
.then(response => {
const posts = response.data;
dispatch({ type: GET_POSTS, payload: posts });
});
};
}
reducer:
import { ADD_POST } from "../actions/addAction";
import { GET_POSTS } from "../actions/getAction";
import { DELETE_POST } from "../actions/deleteAction";
import { UPDATE_POST } from "../actions/updateAction";
const initialState = {
posts: []
};
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case ADD_POST:
state = state.slice();
state.push(payload);
break;
case GET_POSTS:
payload.forEach(element => {
state.posts = [...state.posts, element];
});
break;
default:
return state;
}
return state;
}
in the component that I want to show posts I have:
const mapStateToProps = state => ({
posts: state.post.posts
});
Then showing it with:
render() {
return (
<div className="contentWindow">
{this.props.posts.map((post, i) => {
return (
#some displaying logic
store creation changed with middleware:
const store = createStore(
allReducers,
applyMiddleware(thunk)
);
Still doesn't update my props in my component where I am mapping state to props.
When I inspected the store with react dev tools changes are being made but some how my props weren't updated.
=====EDIT=====
I have changed my reducer code to:
import { ADD_POST } from "../actions/addAction";
import { GET_POSTS } from "../actions/getAction";
import { DELETE_POST } from "../actions/deleteAction";
import { UPDATE_POST } from "../actions/updateAction";
const initialState = {
posts: [123]
};
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case ADD_POST:
state = state.slice();
state.push(payload);
break;
case GET_POSTS:
return { ...state, posts: payload };
case DELETE_POST:
state = state.filter(post => post.id !== payload.id);
break;
case UPDATE_POST:
for (let index = 0; index < state.length; index++) {
if (state[index].id === payload.theId) {
state[index].id = payload.theId;
state[index].date = payload.newDate;
state[index].hours = payload.newHours;
state[index].title = payload.newTitle;
}
}
break;
default:
return state;
}
return state;
}
Now I have a problem that it's changing its initial state every time xD, I am glad I managed to get through the previous one btw feeling so strong now :D.
I have a simple component I'm trying to make work with redux. I map both props and dispatch actions, however only the props I initially get from the store work properly. I traced it all down to my actions: they are being dispatched, but respective reducers don't really do anything. Pretty simple stuff I came up with according to the tutorial and everything looks good to me, but I can't wrap my head around the problem here.
Here is a simplified version of the app:
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Search from './Search'
import { Provider } from 'react-redux'
import store from './store'
const root = document.querySelector('#app')
ReactDOM.render(
<Provider store={store}>
<Search/>
</Provider>, root)
// Search.js
import React from 'react'
import { setText } from '../../actions/appActions'
import { connect } from 'react-redux';
const mapStateToProps = state => {
return {
text: state.app.searchText
}
}
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
class Search extends React.Component {
constructor() {
super()
}
render() {
return (
<input type="text" onChange={() => this.props.setText("text")} value={this.props.text}/>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
// store.js
import { createStore, combineReducers } from 'redux'
import app from './reducers/appReducer'
export default createStore(combineReducers({/*other non-relevant reducers*/, app}))
// appActions.js
export function setText(text) {
return {
type: "APP_SET_TEXT",
payload: text,
}
}
// appReducer.js
const initialState = {
isSearchActive: true,
searchText: "Text",
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case "APP_SET_TEXT":
console.log("fart")
return {
...state,
searchText: action.payload,
}
default:
return state
}
}
What I'm trying to to is to simply make the input value change according to the redux state. I do get the text from {this.props.text}, the change handler onChange={() => this.props.setText("text")} is being dispatched, but the reducer for some reason fails to catch the action that was dispatched.
I think you should change the mapDispatchToProps variable like the following:
const mapDispatchToProps = dispatch => {
return {
setText = (text) => dispatch(setText(text)),
}
}
There are two ways to achieve this
// MODIFYING DISPATHCER
const mapDispatchToProps = dispatch => {
return {
changeText: data => dispatch(setText(data)),
}
}
or
// CONNECT
export default connect(mapStateToProps, {
setText
})(Search)
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
change to
const mapDispatchToProps = dispatch => {
return {
changeText: text => dispatch(setText(text)),
}
}
And in your component use this.props.changeText function
as most of the answers suggests you can dispatch the actions or else you can simply have mapDispatchToProps an object.
mapDispatchToProps = {
setText,
dispatch
}
Your HOC connect should take care of dispatching not need to external definition
Use bindActionCreators from redux
import { bindActionCreators } from 'redux';
const mapDispatchToProps = dispatch => {
const setText = bindActionCreators(setText, dispatch);
return setText;
}
Since you're mapping your dispatch to props like this:
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
You'll need to explicitly call dispatch in your component:
class Search extends React.Component {
constructor() {
super()
}
render() {
const {dispatch, setText} = this.props;
return (
<input type="text" onChange={() => dispatch(setText("text"))} value={this.props.text}/>
)
}
}
It is easier just to map dispatch to props like this: setText = (text) => dispatch(setText(text))
I'm wanting to update my trending array with the results calling the tmdb api. I'm not sure if im going about this the wrong way with calling the api or if im messing up somewhere else along the way. So far I've really been going in circles with what ive tried. Repeating the same things and not coming to a real solution. Havent been able to find another question similar to mine.
my actions
export const getTrending = url => dispatch => {
console.log("trending action");
axios.get(url).then(res =>
dispatch({
type: "TRENDING",
payload: res.data
})
);
};
my reducer
const INITIAL_STATE = {
results: [],
trending: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case "SEARCH_INFO":
return {
results: [action.payload]
};
case "TRENDING":
return { trending: action.payload };
default:
return state;
}
};
and my component im trying to get the results from
import React, { Component } from "react";
import Trending from "./Treding";
import "../App.css";
import { getTrending } from "../actions/index";
import { connect } from "react-redux";
export class Sidebar extends Component {
componentDidMount = () => {
const proxy = `https://cors-anywhere.herokuapp.com/`;
getTrending(`${proxy}https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
render() {
return (
<div>
<h3 className="trending">Trending</h3>
{
this.props.trending ? (
<Trending movies={this.props.trending} />
) : (
<div>Loading</div>
)}
</div>
);
}
}
const mapStateToProps = state => {
return {
trending: state.trending
};
};
export default connect(mapStateToProps)(Sidebar);
Since you are directly calling the getTrending without passing it to connect method, it might be the issue.
Instead that you can pass getTrending to connect method so it can be available as props in the component. After that it can be dispatched and it will be handled by redux/ redux-thunk.
export default connect(mapStateToProps, { getTrending })(Sidebar);
And access it as props in the component.
componentDidMount = () => {
// const proxy = `https://cors-anywhere.herokuapp.com/`;
this.props.getTrending(`https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
I am fairly new to React and Redux and I have an issue with my component not updating on the final dispatch that updates a redux store. I am using a thunk to preload some data to drive various pieces of my site. I can see the thunk working and the state updating seemingly correctly but when the data fetch success dispatch happens, the component is not seeing a change in state and subsequently not re rendering. the interesting part is that the first dispatch which sets a loading flag is being seen by the component and it is reacting correctly. Here is my code:
actions
import { programsConstants } from '../constants';
import axios from 'axios'
export const programsActions = {
begin,
success,
error,
};
export const loadPrograms = () => dispatch => {
dispatch(programsActions.begin());
axios
.get('/programs/data')
.then((res) => {
dispatch(programsActions.success(res.data.results));
})
.catch((err) => {
dispatch(programsActions.error(err.message));
});
};
function begin() {
return {type:programsConstants.BEGIN};
}
function success(data) {
return {type:programsConstants.SUCCESS, payload: data};
}
function error(message) {
return {type:programsConstants.ERROR, payload:message};
}
reducers
import {programsConstants} from '../constants';
import React from "react";
const initialState = {
data: [],
loading: false,
error: null
};
export function programs(state = initialState, action) {
switch (action.type) {
case programsConstants.BEGIN:
return fetchPrograms(state);
case programsConstants.SUCCESS:
return populatePrograms(state, action);
case programsConstants.ERROR:
return fetchError(state, action);
case programsConstants.EXPANDED:
return programsExpanded(state, action);
default:
return state
}
}
function fetchPrograms(state = {}) {
return { ...state, data: [], loading: true, error: null };
}
function populatePrograms(state = {}, action) {
return { ...state, data: action.payload, loading: false, error: null };
}
function fetchError(state = {}, action) {
return { ...state, data: [], loading: false, error: action.payload };
}
component
import React from "react";
import { connect } from 'react-redux';
import { Route, Switch, Redirect } from "react-router-dom";
import { Header, Footer, Sidebar } from "../../components";
import dashboardRoutes from "../../routes/dashboard.jsx";
import Loading from "../../components/Loading/Loading";
import {loadPrograms} from "../../actions/programs.actions";
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.dispatch(loadPrograms());
}
render() {
const { error, loading } = this.props;
if (loading) {
return <div><Loading loading={true} /></div>
}
if (error) {
return <div style={{ color: 'red' }}>ERROR: {error}</div>
}
return (
<div className="wrapper">
<Sidebar {...this.props} routes={dashboardRoutes} />
<div className="main-panel" ref="mainPanel">
<Header {...this.props} />
<Switch>
{dashboardRoutes.map((prop, key) => {
let Component = prop.component;
return (
<Route path={prop.path} component={props => <Component {...props} />} key={key} />
);
})}
</Switch>
<Footer fluid />
</div>
</div>
);
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error
});
export default connect(mapStateToProps)(Dashboard);
The component should receive updated props from the success dispatch and re render with the updated data. Currently the component only re renders on the begin dispatch and shows the loading component correctly but doesn't re render with the data is retrieved and updated to the state by the thunk.
I've researched this for a couple days and the generally accepted cause for the component not getting a state refresh is inadvertent state mutation rather than returning a new state. I don't think I'm mutating the state but perhaps I am.
Any help would much appreciated!
Update 1
As requested here's the code for creating the store and combining the reducers
store:
const loggerMiddleware = createLogger();
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(
thunk,
loggerMiddleware)
);
export const store = createStore(rootReducer, enhancer);
reducer combine:
import { combineReducers } from 'redux';
import { alert } from './alert.reducer';
import { programs } from './programs.reducer';
import { sidenav } from './sidenav.reducer';
const rootReducer = combineReducers({
programs,
sidenav,
alert
});
export default rootReducer;
The 2nd param is expected to be [preloadedState]:
export const store = createStore(rootReducer, {} , enhancer);
axios.get return a promise that you need to await for to get your data:
Try this:
export const loadPrograms = () => async (dispatch) => {
dispatch(programsActions.begin());
try {
const res = await axios.get('/programs/data');
const data = await res.data;
console.log('data recieved', data)
dispatch(programsActions.success(data.results));
} catch (error) {
dispatch(programsActions.error(error));
}
};
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
data: state.programs.data,
});
Action Call
import React from 'react';
import { connect } from 'react-redux';
import { loadPrograms } from '../../actions/programs.actions';
class Dashboard extends React.Component {
componentDidMount() {
// Try to call you action this way:
this.props.loadProgramsAction(); // <== Look at this
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
});
export default connect(
mapStateToProps,
{
loadProgramsAction: loadPrograms,
},
)(Dashboard);
After three days of research and refactoring, I finally figured out the problem and got it working. Turns out that the version of react-redux is was using (6.0.1) was the issue. Rolled back to 5.1.1 and everything worked flawlessly. Not sure if something is broken in 6.0.1 or if I was just using wrong.