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.
Related
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.
I am trying to create a component that allows detecting changes in the redux store. Once the shouldUpdateData flag is set in the store, the component responsible for updating should fetch the data by using an async action creator. In my case, either the error "Maximum updates have reached" occurs or the update never happens.
Depending on the dispatch function stopFetching() (turns off the shouldUpdateData flag), the error or outcome changes. If I do the dispatch inside the action creator there are endless updates. If the code is used as it is below, no update occurs.
The reason I used the useSelector() hook from 'react-redux' is to detect a change in the store for the loading attribute.
Thank you in advance.
Here is the action creator:
export function updateDataAsync(id) {
return function (dispatch) {
// dispatch(fetchDataRequest());
return fetch(`/api/user/${id}/data`, {
method: "GET",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(
(result) => {
let {projects, notes} = result;
// New data and dispatch function
dispatch(fetchDataSuccess({projects, notes}));
},
(error) => { dispatch(fetchDataFailure(error)) }
)
}
}
Here is the reducer for this action creator:
export function savedData(state = DATA_INITIAL_STATE, action) {
switch(action.type) {
case FETCH_STATES.FETCH_DATA_REQUEST:
return {
...state,
loading: true
}
case FETCH_STATES.FETCH_DATA_SUCCESS:
return {
loading: false,
data: action.data,
error: ''
}
case FETCH_STATES.FETCH_DATA_FAILURE:
return {
loading: false,
data: {},
error: action.error.message
}
default:
return state;
}
}
The React component that is doing the update:
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
let reqSent = useRef(false);
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
})
return loading ? <LoadingAnimation /> : children;
}
const mapStateToProps = (state) => {
return {
userId: state.user.id,
shouldUpdate: state.shouldUpdateData // The flag that should trigger the update
}
}
const mapDispatchToProps = (dispatch) => {
return {
stopFetch: () => { dispatch(setShouldFetchData(false)) },
update: (id) => { dispatch(updateDataAsync(id)) },
startFetch: () => dispatch(fetchDataRequest()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(StoreUpdater);
You dint pass any dependency to useEffect so it will be called on every render which is causing infinite renders
change useEffect to
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
},[])
For complete information regarding useEffect refer this link
The reference I created inside the component responsible of the updates, was causing the problem. The reference was preventing the update dispatch to occur due to the if statement being false.
mapStateToProps and mapDispatchToProps were react-redux higher order functions to connect classes components into the store. there equalants at functional components are useSelector and useDispatch. re-write your HOC redux adaption into hooks, and add [ dependency ] at useEffect usage
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
const userId = useSelector(state => state.user.id);
const shouldUpdate = useSelector(state => state.shouldUpdateData);
let reqSent = useRef(false);
const dispatch = useDispatch() // import from 'react-redux'
useEffect(()=>{
if(!reqSent && shouldUpdate) {
dispatch(startFetch());
dispatch(update(userId));
reqSent.context = true;
}
}, [reqSent, shouldUpdate, startFetch, dispatch, update, userId])
return loading ? <LoadingAnimation /> : children;
}
export default StoreUpdater ;
I am learning redux using react. I am trying to update an array of numbers based on a button click. I am specifically want to update the counter at specific index based on imported json file.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { upVote, downVote } from '../store/actions/voteAction';
class Voter extends Component {
render() {
const { count, upVote, downVote, id} = this.props
return (
<div>
<button onClick={() => upVote(id)}>+</button>
The count is {count[id]}
<button onClick={() => downVote(id)}>-</button>
</div>
)
}
}
const mapDispatchToProps = dispatch => ({
upVote: (payload) => dispatch(upVote(payload)),
downVote: (payload) => dispatch(downVote(payload))
});
const mapStateToProps = (state) => ({
count: state.vote.count
})
export default connect(mapStateToProps, mapDispatchToProps)(Voter);
I think my issue comes with how i pass and update the payload in my reducer.
import {UP_VOTE,DOWN_VOTE} from '../actions/actionTypes'
import Mice from './../../imports/mice'
const initialState = {
count: new Array(Mice.length).fill(0)
}
const voteReducer = (state=initialState, action) => {
const id = action.payload
switch(action.type){
case UP_VOTE:
return{
...state, count: state.count[id] + 1
}
case DOWN_VOTE:
return{
...state, count: state.count[id] - 1
}
default:
return state
}
}
export default voteReducer;
I update the array, but every index is still changing and it appears i am still mutating the count array instead of an index inside it.
I have uploaded all my code to CodeSandbox for viewing and experimenting:
CodeSandbox Link
Thanks for reading
Use map method to create a new array, add change one element. The Redux switch will be:
switch (action.type) {
case UP_VOTE:
return {
...state,
count: state.count.map((vote, i) => (i === id ? vote + 1 : vote))
};
case DOWN_VOTE:
return {
...state,
count: state.count.map((vote, i) => (i === id ? vote - 1 : vote))
};
default:
return state;
}
Working code here https://codesandbox.io/s/74pmomo42j
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.
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