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.
Related
I'm having some difficulty with React Redux. It's related to components not re-rendering after a state change. Every question that is asked online refers to it probably being that you are mutating the state, however, I am almost 100% sure that I am not making that mistake. After having tried multiple approaches I just don't know what is going wrong.
Here is my original reducer code:
import * as actionTypes from '../actions/actionTypes';
import { updateObject } from '../utility';
const initialState = {
jwsToken: null,
accessToken: null,
};
const updateTokens = (state, action) => {
return updateObject(state, {jwsToken: action.jwsToken, accessToken: action.accessToken})
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_TOKENS: return updateTokens(state, action);
default:
return state;
};
};
export default reducer
I'm using a utility function (updateObject) to make a copy of my object that I want to return in the reducer. It looks like this:
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
I also tried it without the updateObject utility function and using Object.assign():
const updateTokens = (state, action) => {
return Object.assign({}, state, {
jwsToken: action.jwsToken,
accessToken: action.accessToken,
})
};
I fear that I'm doing something super stupid, but I've spent too much time on this now not to ask. Can someone please tell me what I'm doing wrong?
Edit
Here is my component:
import React, { Component } from "react";
import * as actions from "../../store/actions/index";
import { connect } from "react-redux";
class Calendar extends Component {
componentDidMount () {
if (this.props.accessToken) {
this.onGetEvents()
}
}
onGetEvents = () => {
this.props.getEventsSelectedMonth(this.props.selectedMonth,
this.props.accessToken)
}
render() {
return (
//JSX here
)
}
}
const mapStateToProps = state => {
return {
accessToken: state.accessToken,
selectedMonth: state.selectedMonth
};
};
const mapDispatchToProps = dispatch => {
return {
getEventsSelectedMonth: (selectedMonth, accessToken) =>
dispatch(actions.getEventsSelectedMonth(selectedMonth, accessToken))
};
};
export default connect(mapStateToProps, mapDispatchToProps) (Calendar);
To avoid the infinite loop you can make sure to call the required function only when the value is changed:
componentDidUpdate(prevProps, prevState) {
if (prevProps.accesstoken !== this.props.accessToken) {
this.onGetEvents()
}
}
The infinite loop happens as without the check we would continuously change the state on every update(which occurs because of the state change).
From your comments, I'm assuming you're getting different access tokens every time so maybe you just want to call onGetEvents when you get an accessToken for the first time.
componentDidUpdate(prevProps, prevState) {
// only call when the previous token is falsy and there's a new truthy token
if (!prevProps.accesstoken && this.props.accessToken) {
this.onGetEvents()
}
}
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
I am trying to implement Redux on a React Hooks project, but it doesnt seems to work good. Am I doing something wrong here?
reducer.js
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS: {
state.educations = action.payload;
return state;
}
default:
return state;
}
}
action.js
import * as types from '../constans/home';
export const getEducations = () => {
return dispatch => {
const edus = [
{value: 1, name: 'Bachelor'},
{value: 2, name: "Master"}
]
dispatch({
type: types.GET_EDUCATIONS,
payload: edus
})
}
}
component
import React, {useEffect} from 'react';
import {connect} from 'react-redux';
import {getEducations} from '../../redux/actions/home';
function Header({educations, getEducations}) {
useEffect(() => {
getEducations(); //calling getEducations()
}, [])
useEffect(() => {
console.log(educations) //console educations after every change
})
return (
<div className="main-header">
</div>
)
}
const mapStateToProps = (state) => {
return {
educations: state.home.educations
}
}
const mapDispatchToProps = (dispatch) => {
return {
getEducations: () => { dispatch(getEducations())}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
And the education property in Header function is always an empty array, as in initialState.
While when I check on browser with Redux Devtools, it shows that the state contains those two object in array.
So no matter if I change the redux state or not, the properties of the component are going to stay as initialState.
In redux, you should avoid directly mutating the state of your reducer. Refrain from doing something like state.reducers = blah. In order for redux to know that you are trying to make an update to state, you need to return a completely new state object. Following these principles, your reducers will update correctly and your components will get the new data.
Reducer.js
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS: {
return {
...state,
educations: action.payload
};
}
default:
return state;
}
}
In the code above, we return a new state object. It will include everything from the existing state, hence ...state, and we just update the educations property with the action.payload.
Can try with the reducer written this way :
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS:
return {
...state, educations:action.payload
}
default:
return state;
}
}
It looks like you’re mutating the state in the reducer. The reducer should always return a new state object if something updated.
You could do what the answers above suggest, but i would recommend using a package like immer (https://www.npmjs.com/package/immer) or immutable.js to prevent any bugs down the line. Using the spread syntax can be dangerous if your state object has some deeply nested properties, and it’s hard to be 100% sure that you haven’t accidentally mutated something, especially as your app grows in size.
It looks like you have solved this while I was getting this typed up - I decided to post it regardless, as it may be helpful.
On top of what Christopher Ngo already mentioned, the following example outlines how you can interact with your store to create new educations and then view them, in separate components..
Cheers!
I encounter this all the time and resolved it with CLEAR then GET/SET state. This ensures a reset of the state call.
Reducers.js
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS: {
return {
...state,
educations: action.payload
};
}
case CLEAR_EDUCATIONS: {
return initialState;
}
default:
return state;
}
}
Hooks.js
...
const clearEducation = () => {
dispatch({ type: CLEAR_EDUCATION });
}
const getEducations = (payload) => {
clearEducation(); // this clearing of the state is key fire re-render
dispatch({ type: GET_EDUCATIONS, payload });
};
}
I'm trying to add Redux to my test app but I'm having trouble fetching data from my API. It feels like I've gone through all of the steps but I can't access the props from the fetch in my component, so I'm messing up somewhere along the way. My code:
actions/index.js:
import 'whatwg-fetch';
import ReduxThunk from 'redux-thunk';
export const FETCH_RECIPES = 'FETCH_RECIPES';
const ROOT_URL = 'http://myapi/recipe/';
export function fetchRecipes(id) {
const url = ROOT_URL + "0";
// const request = fetch(url);
return (dispatch) => {
fetch(url)
.then((response) => response.json())
.then((request) => dispatch(fetchRecipesSuccess(request)))
};
}
export function fetchRecipesSuccess(request) {
return {
type: FETCH_RECIPES,
request
};
}
reducer_recipe.js:
import { FETCH_RECIPES } from "../actions/index";
export default function(state = [], action) {
switch(action.type) {
case FETCH_RECIPES:
return [action.payload.data, ...state];
}
return state;
}
reducers/index.js:
import { combineReducers } from 'redux';
import RecipesReducer from './reducer_recipes';
const rootReducer = combineReducers({
recipes: RecipesReducer
})
export default rootReducer;
aaaaand in my component I'm using this code:
function mapStateToProps({ recipes }) {
return { recipes };
}
connect(mapStateToProps, {fetchRecipes})(Recipe);
And in my index.js I'm creating my store with const createStoreWithMiddleware = createStore(reducers, applyMiddleware(ReduxPromise));
With my thinking I should be good to use this.props to access the data I've fetched from my API but I guess I'm dropping the data somewhere along the way. What am I missing?
Check your reducer well. You seem to be returning action.payload.data whereas in your fetchRecipesSuccess, it's named request. And you can console.log the action object to see what you've got
import { FETCH_RECIPES } from "../actions/index";
export default function(state = [], action) {
switch(action.type) {
case FETCH_RECIPES:
// Verify here that your request object has data
return [...state, action.request.data];
// Default state
default:
return state;
}
}
Hope this helps!
Basically what I wanted to do was to stop making axios calls inside of my component. So I thought; “Why not just create an action for that?”
I googled around to find a good “guide” to use Redux and this is what I’m using:
Add a constant to the constants file. Something like const GREAT_COURSE = GREAT_COURSE
Add an action creator to the actions folder. Return an action JavaScript object with a type of the constant you created.
Add a reducer to the reducers folder that handles this action creator.
So I began to create my action creator:
import axios from 'axios'
import { CUSTOMER_FETCH } from './types'
import settings from '../settings'
axios.defaults.baseURL = settings.hostname
export const customers = () => {
return dispatch => {
return axios.get('http://hejhej/customers').then(res => {
dispatch({
type: CUSTOMER_FETCH,
data: res.data
})
})
}
}
And later to add a reducer that handles my action creator:
import { CUSTOMER_FETCH } from '../actions/types'
const initial = []
const customer = action => {
return {
data: action.data
}
}
const customers = (state = initial, action) => {
switch (action.type) {
case CUSTOMER_FETCH:
customers = [...state, customer(action)]
console.log('customers as state', customers)
return customers
default:
return state
}
}
export default customers
And inside of my component I'm importing it:
import { customers } from '../../actions/customersAction'
And later using connect: export default connect(null, { customers })(Events)
And finally I'm using it inside of my component:
customers() {
this.props.customers(this.state.data)
}
So I'm wondering what I'm doing wrong, because I can't see my console.log in my dev tools. Thanks a lot for reading!
Inside of my component atm:
axios.get('http://hejhej/customers').then(res => {
this.setState({
res,
customer: res.data
})
})