React redux state update isn't reflecting to related components - javascript

I have two react components namely Dashboard and singleFeature.In the dashboard I have an ionRangleSlider which takes value from redux state. And I'm rendering the singleFeature component inside the Dashboard. SingleFeature component creates a fetch network request and updates the redux state using the dispatch method.
And the ionRangeSlider which resides inside the Dashboard takes value from the redux state which gets updated by singleFeature component. But regardless of whatever I have tried it's not reflecting the ionRangeSlider. However I can see the redux state is getting updated but not reflecting in any of the component.
Codes:
Dashboard.js
<div id="slider"><IonRangeSlider ref={r => this.ionSlider = r}
skin={this.state.skin} values={this.state.values} /></div>
<SingleFeature name={this.state.name} id={this.state.id} user={this.state.user} />
componentDidUpdate(prevProps,prevState) {
if (this.props.dates !== prevProps.dates) {
console.log(`In update`)
this.ionSlider.update({ values: this.props.dates })
}
}
Which then goes to singleFeature and runs a function and updated the redux state.
singleFeature.js
fetch(`http://api/dates`)
.then(data => data.json())
.then(res => {
for(let i in res){
let dates = res[i]["dates"];
}
this.props.updateState(dates)
})
componentDidUpdate(prevProps,prevState) {
if (this.props.dates !== prevProps.dates) {
console.log(`In update`)
console.log(this.props.dates)
}
}
And both components are connected by { connect } by react-redux which runs these methods.
//fetch from redux store
const FetchFromStore = (state) => {
return {
dates: state.dates
}
}
//update redux store functions
const UpdateStore = (dispatch) => {
return {
updateState: (dates) => dispatch(
{
type: 'UPDATE_DATES',
payload: dates
})
}
}
And the reducer file,
const stateActions = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_DATES':
state.dates = [...state.dates,...action.payload];
console.log(state.dates) //which is updating
return state;
}
return state;
}
None of the componentdidUpdate method working after state update.

Thanks for all the comments. It appears to be I'm updating the state in a wrong way.
//redux payload actions
const stateActions = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_DATES':
// state.dates = [...state.dates,...action.payload];
let dates_arr = [...state.dates];
state.dates = [...dates_arr,...action.payload]
return {...state}
}
return state;
}
Which is working fine and updating all the components.

Related

React component not updating when redux state changes

I have a React component that maps state to props to get data via redux. Everything works fine with the action and the value being updated properly in the reducer. My only problem is that when the state value changes, I want my component to re render so that it is always displaying the most up to date value in the reducer. As of right now I have to call a separate function that refreshes the component, but I'd rather have it automatically re render every time that value changes in the reducer.
Action:
export const createPickup = (selected, pickups) => dispatch => {
let icon;
icon = check(selected);
pickups.icon = icon;
return API('/createPickUp/', {
...pickups,
})
.then(res => {
dispatch({type: types.CREATE_PICKUP, res});
})
.catch(err => console.log(err));
};
Reducer:
const initialState = {
pick: [],
};
export default function pickup(state = initialState, action) {
switch (action.type) {
case types.GET_PICK:
return {
pick: action.pickup,
};
case types.CREATE_PICKUP:
return {
pick: [action.res, ...state.pick],
};
case types.DEL_GAME:
return {
pick: state.pick.filter(p => p._id !== action.id),
};
case types.START_GAME:
return {
pick: state.pick.map(p =>
p._id === action.id ? {...p, start: true} : p,
),
};
case types.STOP_GAME:
return {
pick: state.pick.map(p =>
p._id === action.id ? {...p, stop: true} : p,
),
};
default:
return state;
}
}
Use useSelector hook in Functional Component as it automatically subscribes to the state and your component will re-render.
If you are using Class Component then use connect() from redux and mapStateinProps.
I am assuming you have passed the reducer to the global Store.
Now... make sure you have the up to date value in your component.. try consoling it like this...
import {useSelector} from 'react-redux';
const YourCmponent = () => {
const reduxState = useSelector(state => state);
console.log(reduxState);
return <div>Your Content</div>
}
That way you can get access to the redux store. And you don't need to make any other function for updating component You will always get updated value here.

Arrow functions only have access to initial state created by useReducer, not updated state

I am trying to access the updated state from useReducer inside of an arrow function in a functional component. However, when I call the state, I'm only getting the initial state object.
The reducer function
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE':
return {
...state,
[action.progress.request]: action.progress
}
case 'REMOVE':
const { [action.progress.request]: value, ...rest } = state
return rest
default:
return state
}
}
The react component
const ProgressProvider = ({ children }: Props) => {
const [state, dispatch] = useReducer(reducer, {})
const start = (request) => {
console.log(state) // expected to see updated state, but instead see initial value
// ... do more things
}
const end = (request) => {
console.log(state)
// ...do more things
}
return (
<ProgressContext.Provider value={{ state, start, end }}>
{children}
</ProgressContext.Provider>
)
}
could be used in an api request like this:
const progress = useContext(ProgressContext)
const getData = async params => {
const url = '/my/endpoint'
progress.start(url)
try {
await axios.get(url, { params })
} catch (error) {
console.error(error)
} finally {
progress.end(request)
}
}
I expect in the start and end functions to be able to see an updated state, but I actually see the initial state {}
In order for state to change, you need to dispatch an action. This can either be done via a click of a button or something else entirely. You should update your start function to be along the lines of the following
const start = request => {
const action = {
type: 'UPDATE',
request
};
dispatch(action);
}
The dispatch(action) will cause an update to the state that will be available on render.

React doesn't recognize state change in reducer

I have a component that makes an API call and then updates the state through a reducer. The problem is, this doesn't work so well cause the data don't get updated in the component, it's like the react didn't notice a state change a never re-rendered the component, but I'm not sure if that's the real issue here. So the component looks like this:
class MyComponent extends Component {
componentDidMount() {
// ajax call
this.props.loadData(1);
}
render() {
return (
<Grid>
<MySecondComponent
currentData={this.props.currentData}
/>
</Grid>
);
}
}
const mapStateToProps = state => ({
reducer state.myReducer,
currentData: state.myReducer.currentData
});
const mapDispatchToProps = dispatch => {
return {
loadData: () => {
HttpClient.getData(id, (data) => {
dispatch(
action_loadCurrentData(
data
)
);
});
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
I am doing 2 things here: issuing an API call as soon as component is mounted, and then after data is fetched, dispatching action_loadCurrentData
This action looks like this:
//Action
export function action_loadCurrentData(
data
) {
return {
type: 'LOAD_CURRENT_DATA',
payload: {
currentData: data,
}
};
}
and the reducer:
//Reducer
const defaultState = {
};
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
state = {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
export default myReducer;
So the issue here is that the this.props.currentData that I'm passing to MySecondComponent will end up empty, as if I didn't set the data at all. However, If I stop the execution in the debugger and give it a few seconds, the data will be populated correctly, so I'm not sure what I'm doing wrong here?
Don't reassign state, return the newly created object instead
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
return {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
Your reducer needs to return the new state object, which needs to be a different instance from the previous state to trigger components update.
According to redux documentation:
The reducer is a pure function that takes the previous state and an action, and returns the next state.
And
Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().

redux way of doing doesn't work for my sessionStorage

I am trying to learn redux.
I am trying to add favorites functionality through Redux.
so I created actions addFavoriteSPORTSs, reducers SPORTSReducer, and then dispatched in tab-demo.js where i am doing mapDispatchToProps and
mapStateToProps
when I click the heart icon I am adding favorites in session storage window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(action.payload)
);
but the problem is after the refresh the color is not staying in the heart.
I debugged in componentDidMount and I am able to print the favotites get item value but still colr not maintaining.
can you tell me how to fix it.
so that in future I will fix itmyself.
providing my code snippet below
https://codesandbox.io/s/5x02vjjlqp
actions/index.js
import {
ADD_SPORTS,
DELETE_SPORTS,
DELETE_ALL_SPORTS,
ADD_ALL_SPORTSS
} from "./types";
export const addFavoriteSPORTSs = data => ({
type: ADD_ALL_SPORTSS,
payload: data
});
actions/types.js
export const ADD_ALL_SPORTSS = "ADD_ALL_SPORTSS";
tab-demo.js
import { deleteAllPosts, addFavoriteSPORTSs } from "./actions/index";
componentDidMount() {
let favorites = window.sessionStorage.getItem("favoriteValues");
console.log("componentDidMount favorites--->", favorites);
if (favorites) {
this.props.addFavoriteSPORTSs(JSON.parse(favorites));
}
// debugger;
}
const mapDispatchToProps = dispatch => {
return {
onDeleteAllSPORTS: () => {
// console.log("called");
dispatch(deleteAllPosts());
},
addFavoriteSPORTSs: data => {
dispatch(addFavoriteSPORTSs(data));
}
};
};
const mapStateToProps = state => {
return {
SPORTSs: state.SPORTSs
};
};
export default withStyles(styles)(
connect(
mapStateToProps,
mapDispatchToProps
)(ScrollableTabsButtonForce)
);
SPORTSReducer.js
switch (action.type) {
case ADD_ALL_SPORTSS:
window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(action.payload)
);
return action.payload;
case ADD_SPORTS:
state = state.filter(comment => comment.id !== action.payload.id);
value = [...state, action.payload];
console.log("ADD_SPORTS state--->", state);
console.log("ADD_SPORTS value--->", value);
//return [...state, action.payload];
// state = state.filter(SPORTS => SPORTS.SPORTSID !== action.payload.SPORTSID);
// value = [...state, action.payload]
window.sessionStorage.setItem("favoriteValues", JSON.stringify(value));
console.log("JSON.stringify(value)--->", JSON.stringify(value));
console.log("state--->", state);
return state;
When the component mounts you retrieve your favourties and set the redux state via calling your prop method. Your component will receive this new state via mapStateToProps, but it won't update without a suitable lifecycle method like componentDidUpdate or componentWillReceiveProps.
You can check out the lifecycle methods here.
Also, you are mutating your state in redux which is something you want to avoid. See this line:
state = state.filter(comment => comment.id !== action.payload.id);
I would also recommend Redux middleware for these tasks. You can set up middleware that will write to session storage whenever a specific action occurs and you can then rehyrdate Redux from that as well.

Async Action with Redux

I have a React component that's connected to Redux store. I'm fetching resources(posts) in the componentWillMount life-cycle method.
componentWillMount() {
this.props.fetchPosts();
}
The component will subscribe to Redux store and getting isFetching and posts from the store.
const mapStateToProps = (state) => {
return {
posts: getAllPosts(state),
isFetching: getIsFetchingPosts(state),
}
}
I'd like to show a spinner when it's still fetching, so in the render method I'd like to do this.
render() {
if (this.props.isFetching) {
return <Spinner />
}
return this.props.posts.map(post => <PostItem key={post.id}{...post}/>)
}
BUT if I console.log isFetching in the render method, first it shows false and thentrue and then finally false.
Ideally when this container renders for the first time isFetching state is already set to true and showing the spinner. What changes do I need to make to make that happen?
Here is code for the action creator and reducers
/*** Action Creator ***/
export const fetchPosts = () => (dispatch) => {
dispatch({
type: REQUEST_POSTS,
});
return axios({
method: 'get',
url: `${API_URL}/posts`,
})
.then(({data}) => {
dispatch({
type: RECEIVE_POSTS,
payload: data.posts,
})
})
.catch((response) => {
// some error handling.
});
}
/*** Reducers ***/
const initialState = {
isFetching: false,
allIds: [],
byId: {},
};
const isFetching = (state = initialState.isFetcthing, action) => {
switch (action.type) {
case REQUEST_POSTS:
return true;
case RECEIVE_POSTS:
return false;
default:
return state;
}
}
const allIds = (state = initialState.allIds, action) => {
switch (action.type) {
case RECEIVE_POSTS:
return action.payload.map(post => post.id);
default:
return state;
}
}
const byId = (state = initialState.byId, action) => {
switch (action.type) {
case RECEIVE_POSTS:
return action.payload.reduce((nextState, post) => {
nextState[post.id] = post;
return nextState;
}, {...state});
default:
return state;
}
}
const posts = combineReducers({
isFetching,
allIds,
byId,
});
export default posts;
/*** Selectors in 'posts.js' file ***/
export const getAllPosts = (state) => {
const { allId, byId } = state;
return allIds.map(id => byId[id]);
}
/*** rootReducer file ***/
import posts, * as fromPosts from './posts';
const rootReducer = combineReducers({
posts,
})
export default rootReducer;
export const getAllPosts = (state) => {
return fromPosts.getAllPosts(state.posts);
};
Thank you in advance!
The plain answer is that this is the expected behaviour, given your implementation. You're mapping the isFetching state to a prop. Here's what's happening:
The initial value of isFetching in the state tree is false, so the isFetching prop value is false, so it renders as false.
You dispatch an action which changes isFetching to true in the state tree. This new state is mapped to a new isFetching prop value of true, which causes a re-render, where it renders as true.
You (asynchronously) dispatch another action which changes isFetching back to false in the state tree. Same as in (2), this causes a re-render, with isFetching as false.
The plain solution if you want simply a render of true, false*, with the current implementation, is to set isFetching to true in your reducer's initial state.
Whether or not this implementation makes sense on a design level for this component is a broader question, that there's not enough context to answer here :-)
*update for completeness I should say I don't know if the render() function would be called twice, with isFetching resolved as true,false or three times with true,true,false in this case. I suspect that react-redux may optimise the rendering of the component such that no re-render occurs if the mapped isFetching prop changes from true->true, but don't know this for sure - would be grateful and interested if you could let me know what your logging outputs?
In any case, at the DOM level certainly only two renders would occur, due to the standard react virtual-DOM diffing optimisation, so in effect the result is the same either way

Categories

Resources