React component not re-rendering after dispatch with mapStateToProps and connect() - javascript

I have a functional React component that is wired up with Redux's connect() to listen to any changes in my redux store. If a change were to occur, I would need to re-render my page to update the data being displayed on the screen to be accurate with the data in the store.
The data in the redux store is getting updated with no problem. If I forcefully invoke a re-render by clicking on something to cause a re-render, the new data will update on the page. But you can see how this is not practical in any sense
my component that needs to be re-rendered on any store changes, looks like the following:
function Foo1(props) {
const rightTreeData = props.retrievedRData;
const leftTreeData = props.retrievedLData;
...
...
const mapStateToProps = function (state) {
console.log(state);
return {
retrievedLData: state.retrievedLData,
retrievedRData: state.retrievedRData,
loading: state.loading,
};
};
export default connect(mapStateToProps)(Foo1);
my dispatch occurs in a function, inside of another file. This is a function to build my hierarchy
const loadChildren = (
id: string,
direction: string,
retrievedLeftTree,
retrievedRightTree,
dispatch
) => {
fetch(`...`)
.then((data) => {
...
...
dispatch(UpdateRetrievedData(data, direction)); // <-------
...
...
})
.catch((err) => {
console.log(err);
});
};
Since it is a function and not a react component. I don't believe I am able to hook it up to use connect()
my store looks like the following.
ACTION
export const UpdateRetrievedData = (payload, position) => ({
type: position === "right" ? "UPDATE_RETRIEVED_DATAR" : "UPDATE_RETRIEVED_DATAL",
payload: payload,
});
REDUCER
const RetrievedLDataReducer = (state = false, { type, payload }) => {
switch (type) {
case "UPDATE_RETRIEVED_DATAL":
return payload;
default:
return state;
}
};
export default RetrievedLDataReducer;
some more information:
the dispatch is called on a button click that is nested inside of a hierarchy tree. I am unable to pull this function out and return the data like a regular function since it is baked and rendered into the HTML isolated in the function.
I have tried approaches like using my own observer-like store.subscribe(). This did work but also resulted in getting up to thousands of requests, which eventually would completely freeze the browser.

Related

how to use redux useSelector to check state and do a fetch to a database

I am using redux in a project and I want to make a useSelector that would check to see if the values in the redux state are the default values if not it will do a request to the database and update the the state I feel like it is quite complicated though and I am having a hard time getting my head around how I need to do this.
I need to do this because sometimes the correct state is not loaded in the state I am considering just doing a check every time I use useSelector to check if the values are the default values then fetch from the database but I would much prefer to write it a way that would allow to be handled within the redux selector but I can't really grasp I how I need to do it.
const info = useSelector(getInfo)
Ideally I would like the info to be handled when I fetch here
import { SET_USER_DETAILS } from "../constants/userConstants";
const intialState = {
user: { },
};
const userReducer = (state = intialState, action: any) => {
switch (action.type) {
case SET_USER_DETAILS:
return { ...state, user: action.payload };
default:
return state;
}
};
here is what my current reducer looks like what would be the best way to do this as I am finding it a little bit difficult to follow the documentation on the redux website.
You can use redux-thunk. https://redux.js.org/usage/writing-logic-thunks
then your thunk could look something like that:
const thunkFunction = (dispatch, getState) => {
// logic here that can dispatch actions or read state
const currentState = getState() as typeof initialState;
// check if state is default state
if (JSON.stringify(initialState) === JSON.stringify(currentState)) {
fetch(url).then(data => {
dispatch({type: SET_USER_DETAILS, payload: data})
})
}
}
You need first to fetch data in react component:
const MyComponent = () => {
// while fetch is fetching, data will be default state,
// and when fetch is done, that component will automatically
// rerender with new data
const data = useSelector(getInfo);
const dispatch = useDispatch();
useEffect(() => {
dispatch(thunkFunction)
},[])
return <code>{JSON.stringify(data, null, 2)}</code>
}
I did not test it so may require some changes
but in general concept is like this

Where is a good to call actions for fail and success in react-redux

I have a question about redux.
Now, I'm building my mobile app following redux advanced tutorial.
The tutorial says that you have to create 3 actions each 1 function so I have created 3 actions for sign-in function like below:
requestSignIn
requestSignInSuccess
requestSignInFailure
However, I don't understand where the app should call them from.
Now, in my app, the app calls requestSignInSuccess and requestSignInFailure in requestSignIn.
This is my action code:
export const REQUEST_SIGN_IN = 'REQUEST_SIGN_IN';
export const REQUEST_SIGN_IN_FAILURE = 'REQUEST_SIGN_IN_FAILURE';
export const REQUEST_SIGN_IN_SUCCESS = 'REQUEST_SIGN_IN_SUCCESS';
import firebase from 'react-native-firebase';
export function requestSignIn() {
// start sign in function
firebase.auth().signInWithEmailAndPassword(EMAIL, PASSWORD)
.then(response => {
// success, so call requestSignInSuccess() to change state
requestSignInSuccess(response.user);
})
.catch(error => {
// fail, so call requestSignInFailure() to change state
requestSignInFailure(error);
})
}
function requestSignInSuccess(user) {
// save user info into state in reducer
return {
type: 'REQUEST_SIGN_IN_SUCCESS'
payload: user
}
}
function requestSignInFailure(error) {
// save error message into state in reducer
return {
type: 'REQUEST_SIGN_IN_FAILURE'
payload: error
}
}
[Questions]
Am I following the redux tutorial correctly? (The app calls requestSignInFailure and requestSignInSuccess in requestSignIn function, is it good?)
If I want the app to have isLoading flag into state, which action should change the flag?
Let me try to answer one by one your questions.
Am I following the redux tutorial correctly?
Yes, you are on the right track, just few steps missing. The below explanation is for class based components.
Technically if you created actions - just like above in your question - then what you need to do is dispatching them in the component in order to use.
Firstly need to dispatch the actions in mapDispatchToProps.
Then need to connect the component - to call requestSignIn action - with the Redux store.
Pass the created mapDispatchToProps to connect as the second parameter.
Please find the following example below:
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// ... other imports
class YourComponent extends React.Component {
constructor (props) {
super(props);
}
// ... component code
// ... obviously this is just an example component for representation
render() {
return (
<>
<a onClick={props.requestSignIn()}>Request Sign In</a>
</>
)
}
}
const mapDispatchToProps = dispatch => {
return bindActionCreators({ requestSignIn }, dispatch);
};
export default connect(null, mapDispatchToProps)(YourComponent);
If I want the app to have isLoading flag into state, which action should change the flag?
I would create in the reducer a new property called isLoading just like below:
const initialState = {
isLoading: false,
};
export default (state=initialState, action) => {
switch(action.type) {
case 'ENABLE_LOADING':
return {
...state,
isLoading: true,
};
case 'REQUEST_SIGN_IN_SUCCESS':
return {
...state,
isLoading: false,
};
case 'REQUEST_SIGN_IN_FAILURE':
return {
...state,
isLoading: false,
};
// ... other actions
}
}
In your requestSignIn action need to trigger ENABLE_LOADING once you start fetching the data one line before firebase.auth().signInWithEmailAndPassword(EMAIL, PASSWORD) then it will hopefully work for you. Just like how you did with REQUEST_SIGN_IN_SUCCESS and REQUEST_SIGN_IN_FAILURE.
To access the reducer's properties you need to use mapStateToProps further.
In functional component case you need to use useDispatch to call the created actions:
This hook returns a reference to the dispatch function from the Redux store. You may use it to dispatch actions as needed.
And to access the data from the store there is a hook called useSelector:
Allows you to extract data from the Redux store state, using a selector function.
Quick summary:
If you are looking for a fully working example with useSelector and useDispatch in a functional component then take a look at this git repository:
https://github.com/norbitrial/react-redux-loading-data-example
In the repository you will find a nice representation of a fake API call which loads data into a table with a loader indicator, just like what you need from your question.
In case of further interest in more details please find the below links which are pretty useful:
Connect: Dispatching Actions with mapDispatchToProps
Connect: Extracting Data with mapStateToProps
Dispatching actions with useDispatch() for functional component
Extract data with useSelector() for functional component
I hope this helps, let me know if you need further clarification.
Try using redux-thunk middleware to handle promises in the actions. You will get a fullfilled, pending (your loading) and failure actions for every promises.
You are doing right but to keep code clean and readable i would advise you to take all type variables out of component, create action folder inside src -> create file types.js (or any name you want) and move all variables to it. you can do same with requestSignInFailure and requestSignInSuccess (keep action related stuff in your actions folder. (UNLESS you are not using react hooks because you must use useDispatch in React component funcion, not in pure function)). and to dispatch data to store you must use dispatch (I assume you use connect method while exporting component) and if you pass your action to connect as second argument function will get second function inside with argument dispatch like this
function requestSignInFailure(error) {
// save error message into state in reducer
function(dispatch){
dispatch({
type: 'REQUEST_SIGN_IN_FAILURE'
payload: error
})
}
}
it is same if you pass function directly in connect like so:
connect(mapStateToProps, {
(dispatch) => {
dispatch({
type: 'REQUEST_SIGN_IN_FAILURE'
payload: error
})
},
(dispatch) => {
dispatch({
type: 'REQUEST_SIGN_IN_SUCCESS'
payload: success
})
}
})(Component);
and to change isLoading value you can use componentDidMount, and dispatch action inside componentDidMount inside it to change isLoading , it will dispatch action while component renders (mounts) like so:
function changeLoading(){
function(dispatch){
dispatch({
isLoading: false
})
}
}
componentDidMount(){
this.props.changeLoading();
}
export default connect(null, changeLoading)(Component)
react docs about lifecycle methods.
i had made some changes to above code. requestSignInFailure and requestSignInSuccess in requestSignIn fine you can use it, or you have the choice to change thunk request to RSAA request but connecting it to firebase may be difficult.
import firebase from 'react-native-firebase';
export function requestSignIn() {
// start sign in function
startedRequestForSignIn()
firebase.auth().signInWithEmailAndPassword(EMAIL, PASSWORD)
.then(response => {
// success, so call requestSignInSuccess() to change state
requestSignInSuccess(response.user);
})
.then(()=>{
endedRequestForSignIn()
})
.catch(error => {
// fail, so call requestSignInFailure() to change state
requestSignInFailure(error);
})
}
function requestSignInSuccess(user) {
// save user info into state in reducer
return {
type: 'REQUEST_SIGN_IN_SUCCESS'
payload: user
}
}
function requestSignInFailure(error) {
// save error message into state in reducer
return {
type: 'REQUEST_SIGN_IN_FAILURE'
payload: error
}
}
function startedRequestForSignIn{
return {
type: 'IS_LOADING'
payload: true
}
}
function endedRequestForSignIn{
return {
type: 'IS_LOADING'
payload: false
}

React & Redux, Update state, Error: A state mutation was detected between dispatches

The problem I'm having is in my save data round trip.
The error: browser.js:34 Uncaught (in promise) Invariant Violation: A state mutation was detected between dispatches, in the path profile.user.alias.
I'm new to React & Redux but I think I've done everything by the book. What am I missing?
I create a local state in the component and assign the object returned from mapStateToProps.
I setState on onChange of text fields.
On save click, I call the action defined in mapDispatchToProps passing it the local state. Note, the mapped action is a Thunk function since I use REST Api to save date on server.
Server saves the data and returns the same object with new Ids etc. filled in.
In fetch.then() I dispatch an action passing the object returned from the server.
The error is on the dispatch line.
export function setUserProfile (profile) {
return function(dispatch) {
return setProfile(profile) <-- the API call
.then(p => {
dispatch(handleSetProfileSuccess(p));
})
.catch(error => {
throw error;
});
};
I've been wrestling with this error since yesterday. I've tried all tricks I've found including changing to createReducer from Redux-Starter-Kit.
I've made sure that the exact same chain of objects, actions, reducers, etc. that I use in the 'set' are the same as in the 'get' action that works.
export function handleSetProfileSuccess (profile) {
return { type: types.SET_USER_PROFILE_SUCCESS, profile };
}
export function setUserProfile (profile) {
return function(dispatch) {
return setProfile(profile) <--- the api call with fetch
.then(p => {
dispatch(handleSetProfileSuccess(p));
})
.catch(error => {
throw error;
});
};
// the action is never dispatched on save since the error is on the dispatch line above.
// the Redux way
export default function profileReducer(state = {}, action) {
switch (action.type) {
case types.GET_USER_PROFILE_SUCCESS:
return { ...state, ...action.profile };
case types.SET_USER_PROFILE_SUCCESS:
return { ...state, ...action.profile };
default:
return state;
}
}
// the redux-starter-kit way
const profileReducer=createReducer({}, {
GET_USER_PROFILE_SUCCESS: (state, action) => state=action.profile,
SET_USER_PROFILE_SUCCESS: (state,action) => state=action.profile
});
The whole round trip works. The new data is saved on the server and the returned object from the server is correct. The argument 'p' in the .then() call does contain the correct data. But it crashes on the call to
dispatch(handleSetProfileSuccess(p));
EDIT: Here's the code snippets from profile.js
// profile.js
state = { ...this.props.profile };
// on save click handler
setData = e => {
e.preventDefault();
// validate stuff here
this.props.setProfile(this.state).then(() => {
this.setState(prevState => ({
...prevState,
...this.props.profile
}));
});
};
export function mapStateToProps(state) {
return {
user: state.user,
profile: state.profile
};
}
const mapDispatchToProps = {
getProfile: profileActions.getUserProfile,
setProfile: profileActions.setUserProfile
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Profile);
Please help. I'm all out of options.
it is likely that you are mutating a prop passed by react-redux.
Check all code where you are using props.profile or props.user, code like the following could be the culprit
props.profile.friends.splice(0, 1);
The state mutation error happens when you mutate the props received and dispatches an action with that mutated props. So to do such dispatches, copy the props to a local state while receiving it, make the changes to the state and dispatch the action with the state.
If the props received is a nested object do deep copying, using
JSON.parse(JSON.stringify());
So in your code, before making a dispatch copy the props to a state and make the dispatch with the state.

Redux.js - I fail to mapStateToProps with React Redux - My React Component block on the store initial state and fail to update when store.state update

I'm trying currently to pass the app.state contained to the Redux store in a React Component.
So far, this problem is still a deep mystery...
------> HERE THE GITHUB REPOSITORY OF MY CODE <------
Hope it will help to figure out what is wrong.
Abstract :
My problem is basically about mapStateToProps, is about link a Component to the state store, AFAIK the rest work very fine, but Something seems shortcut my this.props in React's Component, because either I use connect() or delete the mapStateToProps method, my Component stil display the initial state ..!
Redux resists me like an end-level's boss...
STATE OF PLAY
The provider with a store of react-redux: OK
Connect function pass to the props: OK
mapDispatchToProps works fine! So why the state fails to update the props since the connection seems well established?
I know my action is well mapped since when I delete the mapDispatch in the connect composition, the component then fails to trigger the corresponding action.
When console.log, the mapState receive effectively the store update but the Component stay blocked on initial state (tested with a "checkState" button on the component which returns the "store.getState().propertyTargeted"
HINTS :
when I delete the mapStateToProps in connect, my React.Component continue to receive the initialState,
so maybe there is an another source that overwrites my mapStateToProps, I seek for it currently
my this.props.state variable is called in the Component's constructor, maybe the constructor doesn't receive the store.updateState or something like that ? Another track to follow.
Here my combineReducer.js :
import { combineReducers } from "redux";
import {post} from "./status"
import {entry}from "./updateState";
// only one reducer active
const appReducer = combineReducers({
entry,
post
})
export default appReducer
Here my container.js :
const mapStateToProps = (state) => {
return { word: state.entry.word }
}
const mapDispatchToProps = {
postFile: postFileAction
}
const PostFileContainer = connect(mapStateToProps, mapDispatchToProps)(Component) ;
My postFile.js :
export const postFile = (word, base64Data) => dispatch => {
console.log("postFile httpRequest reached")
dispatch({
type: 'POST_WORD',
status: request
});
Axios.post("http://localhost:7500/api/files", {
"word": word,
"data": base64Data
}, {
"Content-Type": "multipart/form-data"
})
.then(res =>
dispatch({
type: 'POST_WORD',
status: success,
res
}))
.catch(err => {
dispatch({
type: 'POST_WORD',
status: error,
err
})
});
}
Here in my store.initialState :
initial state: {
"post": {},
"entry": {
"word": "initialWord"
}
}
the UPDATE_STATE_POSTWORD is provide by an other React component therefore dispatched to the store before that the bugging component trigger it own action with a updated word's entry.
Here my UPDATE_STATE_POSTWORD action snippet :
export const updateWord = word => {
return {
type: UPDATE_STATE_POSTWORD,
word
};
}
/// reducers.js part ///
postReducer.js :
export const post = (state ={}, action) => {
console.log("postStatus reached - reducer")
switch (action.status) {
case request:
console.log("Request start")
return state
case success:
switch (action.type) {
case POST_FILE:
console.log("request succeed: ", action.res)
var _id = action.res._id
// var word= action.res.word
return (Object.assign({}, state, {
_id
}))
case POST_WORD:
console.log("request succeed: ", action.res)
return (Object.assign({}, state, {
_id: ""
}))
default :
console.log(`default state on success case in
postStatusReducer`)
return state
}
case error:
console.log("request error: ", action.err)
return state
default:
return state
}
}
entryReducer.js :
const initialState = { word : "initialWord" }
export const updateStateReducer = (state= initialState, action) =>
{
switch (action.type) {
case UPDATE_STATE_POSTWORD:
var word = action.word
return (Object.assign({}, state, {
word
}))
default:
return state
}
}
Thanks
If you are using react-thunk, your action fn would receive dispatch and getState functions as arguments.
Running getState would give you actual state of the application. Recuired data would be passed to reducer and so on.
In your example RecordingAPI receives props that comes from redux only while initializing - in constructor.
You can fix your component by adding componentWillReceiveProps method
class RecordingAPI extends React.Component {
constructor(props) {
super(props);
...
this.state = {
word : this.props.word,
state: this.props
};
}
// new method that listens to props
componentWillReceiveProps (props) {
this.setState({
word: this.props.word,
state: this.props
});
}
checkState(e){
e.persist();
e.preventDefault();
e.stopPropagation();
console.dir(this.state.word)
console.dir(this.state.state)
}
render() {
...
return (
<div>
<button onClick={(e) => this.checkState(e)}> CheckState </button>
</div>
);
}
}
My current work-around is to import the store directly in my React Component then subscribe to the changes as it :
import {store} from "../App"
store.subscribe(() => {
// When state will be updated
// we will update local component state and force component to rerender
// with new data.
this.setState({
word: store.getState().entry.word // new entry.words at each update in the statge of the React.Component
});
});
ANSWER :
Assigning the store.state value to the Component's state constructor, the Component failed to update the state. So, referring to the store.state using this.props outside any assignment to the Component.state.property works like a charm*.
The trap is that storing a props in the props.constructor.state of the children works when you work only with React.js but this mechanism doesn't works for React-Redux then you have to stay the props outside any assignment in the props.constructor.state

Redux-Thunk getStore() doesn't retain state. Returning 'undefined'

This might be a question of best practices but I'd appreciate an explanation on why this doesn't work. I'm using Typescript + Redux + Thunk and trying to call actions like this:
export const requestUserDashboards = createAction<DashboardModel>(Type.REQUEST_USER_DASHBOARDS);
Dispatch in the fetch:
export const fetchDashboards = () => {
return async (dispatch: Dispatch, getState: any) => {
try {
dispatch(requestUserDashboards({
currentDashboard: getState.currentDashboard,
dashboards: getState.dashboards,
hasDashboards: false,
error: getState.error
}))
...
}
})
}
Here's the corresponding reducer:
export const dashboardReducer = handleActions<RootState.DashboardState, DashboardModel>(
{
[DashboardActions.Type.REQUEST_USER_DASHBOARDS]: (state = initialState, action): RootState.DashboardState => ({
currentDashboard: action.payload!.currentDashboard,
dashboards: action.payload!.dashboards,
hasDashboards: action.payload!.hasDashboards,
error: action.payload!.error
})
},
initialState
);
dispatch is working, however, getState doesn't correctly collect the current store state. I'm testing this by doing the following in the component receiving the updated store:
componentWillReceiveProps(nextProps: Login.Props) {
console.log(nextProps.defaultAccounts.defaultAccount);
}
Calling this in the component using:
this.props.defaultAccountActions.fetchUserDefaultAccount();
The action is working as the values from the fetch are being captured.
However, where I am using the getState.xxxx, these values are returning as undefined:
index.tsx:84 Uncaught TypeError: Cannot read property 'defaultAccount' of undefined
The initialState from my reducer is working. I can see this from doing the console.log(this.props.defaultAccounts.defaultAccount) from the componentWillMount() function.
I'm not sure what else I can provide. I think I'm actually just fundamentally misunderstanding how actions/reducers manage the store.
Questions
I am trying to get the current store values by using the getState.xxxx in the dispatch. Is this the correct way to do this?
isn't getState a function in that place? So you would need to do something
const state = getState();
and then use state inside dispatch
found in documentation, yeah it is a function at that place so you should firstly invoke a function to get state and then use it (e.g. from documentation below)
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
If you are using mapstatetoprops in your component you can use that to get the values from store. mapStateToProps first argument is actually the Redux state. It is practically an abstracted getState().
const mapStateToProps = function(state, ownProps) {
// state is equivalent to store.getState()
// you then get the slice of data you need from the redux store
// and pass it as props to your component
return {
someData: state.someData
}
}

Categories