Redux reducers aren't updating the store - javascript

I am having bit of a wrinkle with Redux-Saga as the reducers I had done isn't updating the store as it should suppose to do.
I have used Saga to load a static in-app data and then fired the action with the payload passing the data to reducers, I did console.log() to make sure the reducers are getting the payload from action, which they are - but the problem appears to happen when I return the data into the state so that it could be accessible within the components. In props I only get default state from the reducers, any help on this matter would be highly appreciated. Following is the code I am using;
actions.js
export function loadAppAction() {
return {
type: types.LOAD_APP
}
}
export function loadAppDataAction(data) {
return {
type: types.LOAD_APP_DATA,
payload: data
}
}
api.js
import appData from '../components/appData';
export function appDataResponse() {
return appData;
}
app.js
export class App extends Component {
constructor(props) {
super(props);
const { loadAppAction } = this.props;
loadAppAction();
}
render() {
const {
initialLoadData,
activateModalAction,
deactivateModalAction,
toggleModal
} = this.props;
console.log('props', this.props)
return (
<div className="app">
{
toggleModal &&
<SignInModal
deactivateModalAction={deactivateModalAction}
/>
}
</div>
}
}
function mapStateToProps({ initialLoadReducer, toggleModalReducer }) {
console.log('lets see', initialLoadReducer);
return {
initialLoadData: initialLoadReducer,
toggleModal: toggleModalReducer
};
};
export default connect(
mapStateToProps, {
loadAppAction: actions.loadAppAction,
activateModalAction: actions.activateModalAction,
deactivateModalAction: actions.deactivateModalAction,
})
(App);
initialLoadReducers.js
export default function (state = [], action) {
switch (action.type) {
case types.LOAD_APP_DATA:
return [...action.payload];
default:
return state;
}
}
saga - index.js
function* watchLoadAppAction() {
yield takeEvery(types.LOAD_APP, loadAppSaga);
}
export default function* rootSaga() {
yield all ([watchLoadAppAction()]);
}
loadAppSaga.js
export default function* loadAppSaga(action) {
const response = yield call(api.appDataResponse);
yield put(actions.loadAppDataAction(response));
}
Following is the screenshot of my console for reference

I would suggest to call loadAppAction when componentDidMount not in the constructor. React doc also suggested the same.
https://reactjs.org/docs/react-component.html#componentdidmount
export class App extends Component {
componentDidMount() {
this.props.loadAppAction();
}
...
}

Right so, I got down to the problem here, I was trying to initialise the app with an empty array which will not going to work anyway, as the component is expecting to receive props from redux which is an empty array. Which is why, React didn't create the DOM at its first run and that caused the app to stop re-rendering even though the props are changing.
To make it work, I now initialise the app with the same data structure but with empty string values and in the next step making the data available through Redux Saga into the reducer and passing it back into the React component.

Related

Changing the storage in Redux

I need to change the "global" state of Redux (I believe it's called storage). This is my code:
reducer
export const user = (state = {}, action) => {
console.log(4);
console.log(action.type)
console.log(action.payload)
switch (action.type) {
case C.SET_USER:
console.log(action.payload);
return action.payload;
case C.CLEAR_USER:
return action.payload;
default:
return state;
}
};
Action:
export const setUser = (user = {}) => {
console.log(user);
return {
type: C.SET_USER,
payload: user,
}
};
Calling the action:
const user = {test:true};
setUser(this.state.user);
But if I run this code, it fails and doesn't call the reducer. It calls the action, but not the reducer. What am I missing?
My current app.js code:
export default class App extends Component {
constructor(p) {
super(p);
this.state = {user: null};
}
setUser = () => {
const {uid} = firebase.auth().currentUser;
firebase.database().ref('Users').child(uid).on('value', r => {
const user = r.val();
this.setState({user: user});
console.log(this.state.user);
setUser(this.state.user);
});
};
componentWillMount() {
if (firebase.auth().currentUser) {
this.setUser();
}
firebase.auth().onAuthStateChanged(async () => {
console.log('authChanged');
if (!firebase.auth().currentUser) {
return null;
}
this.setUser();
});
}
render() {
return (
<div className="App">
<Nav/>
</div>
);
}
}
setUser have to be dispatched and not simply called:
store.dispatch(setUser(user));
But that's not really the react way, you'd better use mapDispatchToProps in your connect function to dispatch actions directly from component props. Something along the lines of:
import { setUser } from 'store/user';
// ...
class UserComponent extends React.Component {
// ...
someMethod() {
this.props.setUser(user);
}
}
export default connect(
null,
({setUser: setUser})
)(UserComponent);
This allows your React component to be linked to your Redux store in an optimized and bug-free way. That's also the way most developer use, so you're likely to find a lot of docs on this.
Example: Your connected Component where you want to use your setUser action with redux
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setUser} from '../../actions';
class YourComponent extends Component {
render(){
// now your redux action is passed to component as prop
// and you can use it like
this.props.setUser(some-user);
return()
}
}
export default connect(null, {setUser})(YourComponent);
first of all you have to dispatch action to change the state , second you have to connect your component to the store
to connect your component to the store
...
import { connect } from 'react-redux';
class MyComponent extends React.Component {
...
}
export default connect((store) => ({...}))
when you connect your component to the store you will have access to dispatch function in the props
to dispatch action do this :
this.props.dispatch(setUser());
I believe it's called storage
BTW it called store

Get latest Redux state in React immediately after state change

I have a simple redux action and reducer that changes state without making any sort of web service request:
// Action
export const setMyState = (value) => {
return {
type: 'SET_STATE',
payload: value
};
};
// Reducer
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SET_STATE':
return { myState: action.payload }
}
}
When I set the state via the action in my react component and immediately call that state from the redux prop binding, I do not get the current state:
import React from 'react';
import { connect } from 'react-redux';
import { setMyState } from '../actions';
class MyScreen extends React.Component {
componentWillMount() {
this.props.setMyState('value');
console.log(this.props.myState);
}
}
const mapStateToProps = state => {
const { myState } = state;
return myState;
};
export default connect(mapStateToProps, { setMyState })(MyScreen);
I understand that render will be called again when the redux state and binded props change, but what I'm trying to do is fire off another Redux action based on the resulting state. My options appear to be:
Figure out how to latest Redux state in React immediately after state change
Fire off a redux action in the reducer instead, which sounds like an anti-pattern.
Set some sort of state in my component or redux that the action should be fired on the next render, which seems clunky.
config your reducer like:
switch (action.type) {
case 'SET_STATE':
return {
...state,
loading: true,
};
case 'SET_STATE_SUCCESS':
return {
...state,
loading: false,
payload: value
};
}
and then in your component listen to
this.props.myState.loading
then trigger your action if (loading) and so on!
My solution is to use event loop handles the update, not the call stack. In particular for your case you the implementation would include setTimeout(() => {}); as the following:
import React from 'react';
import { connect } from 'react-redux';
import { setMyState } from '../actions';
class MyScreen extends React.Component {
componentWillMount() {
setTimeout(() => { this.props.setMyState('value'); },0);
setTimeout(() => { console.log(this.props.myState); },0);
}
}
const mapStateToProps = state => {
const { myState } = state;
return myState;
};
export default connect(mapStateToProps, { setMyState })(MyScreen);
What are you using this Redux state for?
If you are trying to access the data you had just set in the Redux state or if it's data that's already sitting in the Redux state you could dispatch both calls at the same time.
If you need to make the call because there is some work done to the data while setting it in the Redux state (I recommend doing this work in the action not the reducer) then componentWillReceiveProps(nextProps) will do what you need. You can compare the current props with the next props and trigger another update to the Redux state.
The best way to do this would probably be to use a Redux middleware.

React components unmount and remount when dispatching redux action in lifecycle methods

This occurs with React + Redux Saga application my team is working on
We buy the React Isomorphic theme from Themeforest which bundles the Redux, Saga, and React Router V.4. We are now working on top of it.
I have been using React for a while but new to Redux and never experienced with such behavior. The problem is that whenever I dispatch an action, the component unmounts and remounts as the state changes
Okay, what I am doing is to fetch some user data from the API but. So this is how I come up with the following action & reducer
// Actions.js
const UserActions = {
FETCH_USER_REQUEST: 'FETCH_USER_REQUEST',
FETCH_USER_SUCCESS: 'FETCH_USER_SUCCESS',
fetch_users: () => {
return {
type: UserActions.FETCH_USER_REQUEST
}
}
}
export default UserActions;
And the reducer
// Reducer.js
export default function UserReducer(state = initialState, action) {
switch (action.type) {
case 'REQUEST_USER_SUCCESS':
return state.set('user_list', action.user_list);
default:
return state;
}
}
With Redux Saga, the middleware is created to handle async actions. This is how it looks
// Saga.js
import { all, takeEvery, call, put, fork } from 'redux-saga/effects';
import {get, post} from 'axios';
export function fetchUser() {
return get('https://mockapi.testapp.dev/users')
.then(response => {
return response.data;
}).catch(error => {
return error.data
});
}
export function* fetchUserRequest() {
yield takeEvery('FETCH_USER_REQUEST', function*() {
const resp = yield call(fetchUser);
yield put({
action: 'FETCH_USER_SUCCESS',
user_list: resp
});
});
}
export default function* rootSaga() {
yield all([
fork(fetchUserRequest)
]);
}
Now I implement the code in my component like this
// App.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import UserActions from './actions/UserAction';
const mapStateToProps = (state, ownProps) => {
return {
userList: state.User.get('user_list')
}
}
const mapDispatchToProps = dispatch => {
return {
fetchUser: () => dispatch(UserActions.fetch_users())
}
}
class App extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
// ... The rest of the rendering processes
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Now that you can see, with the behavior mentioned prior to this. Dispatching an action via this.props.fetchUser() cause the state to change but what I don't expect is that the component shouldn't unmount and remount because once it does so, an infinite loop occurs because the componentDidMount runs over and over and state also changes accordingly.
What I expect is to fetch the data from the API once the component mounts without remounting itself once the state changes for any reason because the theme we purchased is equipped with other base components which make use of Redux-saga to handle state and async actions. For example, the collapsable sidebar triggers a dispatch which changes the state that controls its bahavior once the users click on the it. Currently, once it does that my current component immediately unmounts unexpectedly.
Is there any possible way to solve such a problem or this is the default behavior of Redux?

React not re-rendering when I update my redux store

I know it's me... Because I suck :)
But I believe I am doing everything correct but when I dispatch my action and the state changes my view does not re-render.
I believe this is the simple change that might be needed but the
render()
render(){
....
<div className={data.participants}>
+{store.getState().currentSex.participants}
</div>
....
}
Function that calls the action
onSetParticipants = () => {
console.info(store.getState()); //participants = 1
store.dispatch (currentSex.setParticipants(3));
console.info(store.getState()); //participants = 3
}
reducer currentSex.js
import { List, Map } from 'immutable';
const initialState = {
participants: 1
};
function currentSex (state = initialState, action) {
switch (action.type) {
case 'SET_PARTICIPANTS':
return {
...state,
participants:action.participants
}
}
return state
}
export default currentSex;
Actions
export const SET_PARTICIPANTS = 'SET_PARTICIPANTS';
export function setParticipants(participants) {
return {
type: SET_PARTICIPANTS,
participants: participants
}
}
How I have done my connect, as I believe this helps
function mapStateToProps(state, ownProps) {
return {
errorMessage: state.errorMessage,
inputValue: ownProps.location.pathname.substring(1)
}
}
export default connect(mapStateToProps, { })(App)
Please forgive me is this isn't enough or completely the wrong information.
But why does my page no re-render when i can see the state has changed?
edit
Its worth mentioning that my state has objects inside of it:
In your mapStateToProps you need to add the state that you want to render inside your component. In this case it looks like state.participants.
function mapStateToProps(state, ownProps) {
return {
errorMessage: state.errorMessage,
participants: state.participants,
inputValue: ownProps.location.pathname.substring(1)
}
}
And then use this.props.participants in your component.
import * as actions from '../actions';
/* ... */
render(){
....
<div className={data.participants}>
+{this.props.participants}
</div>
....
}
/* ... */
export default connect(mapStateToProps, actions)(App)
edit
And add the actions to your connect function, as well as importing them. Call your actions using this.props.currentSex(3) inside the function within your component that handles change events.

React/Redux can't iterate over array in state

I'm in the process of learning React/Redux and I've run into an issue while converting a single page web app of mine to the framework/paradigm. What i'm trying to do is let the initial state of the web app have an array that is to be populated by objects from an API request, this array is called "makes". I want to do this so I can display "makes" from the API on the first page of the website upon it loading. This can be seen in the index.js file below:
import App from './App';
import './index.css';
import configureStore from './redux/store'
import { Provider } from 'react-redux'
let makesUrl = 'the url to the API'
let cached = cache.get(makesUrl)
let makes = []
// Future cache setup.
if(!cached) {
console.log("NOT CACHED")
}
else {
console.log("CACHED")
}
// Get the makes via API.
fetch(makesUrl).then((response) => {
// Pass JSON to the next 'then'
return response.json()
}).then((json) => {
json.makes.forEach((make) => {
makes.push(make)
})
})
let initialState = {
grids: [],
makes: makes
}
let store = configureStore(initialState)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
The state and dispatch are mapped to the props and passed down to the components that need them in my App.js file as such:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import './App.css'
import Head from './components/Head'
import Middle from './components/Middle'
import Foot from './components/Foot'
import actions from './redux/actions'
class App extends Component {
render() {
return (
<div className="App">
<div>
<Head />
<div className="middle container">
<Middle actions={this.props.actions} makes={this.props.makes}/>
</div>
<Foot />
</div>
</div>
);
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
At all points, in the chrome dev tools, I can see that the API call was successful and the state is shown to have makes: Array[62] with 62 objects inside, however if I console log the length of the array in the component that these makes are passed down to as seen below, it says the length is 0, and each index of the array is undefinded.
import React, { Component } from 'react'
class MakeButtons extends Component {
handleClick(event) {
event.preventDefault()
console.log("CLICK")
}
render() {
return(
<div>
{
console.log(this.props.makes.length)
}
</div>
)
}
}
export default MakeButtons
This is essentially what I've been trying to figure out for the past couple hours, so I can use the forEach or map function to return links/buttons for each of the objects in the array, however at the moment this does not work, despite dev tools showing the state to be normal. Any help/explanations would be greatly appreciated!
So you really just need to set up an action/reducer for your init, then you can call it in componentWillMount or componentDidMount because they are only called once upon loading your app.
In the way you are doing it now you have a fetch and an app using the data from the fetch that is not waiting for the async call to finish before it starts the app.
You just want to create your init action, so your action creator would be something like :
import * as services from './services';
function initMyAppDispatcher(type, data, status) {
return {
type,
data,
status
};
}
export function initMyApp(dispatch, makesUrl) {
dispatch(initMyAppDispatcher(actions.INIT_APP, {}, 'PENDING');
return services.myInitCall(makesUrl)
.then((data) =>
dispatch(initMyAppDispatcher(actions.INIT_APP, data, 'SUCCESS'),
(error) =>
dispatch(initMyAppDispatcher(actions.INIT_APP, error, 'FAILURE'),
)
.catch(yourErrorHandling);
}
Services.myInitCall is however you want to implement it, just make sure you export it back as a promise. In your case you can replace that line with fetch(makesUrl) as long as you have access to it there. Then having it set up like this, you can set your reducers like so :
case actions.INIT_APP:
if (action.status) {
switch (action.status) {
case PENDING:
//you can use this to create a "loading" state like a spinner or whatever
return state;
case SUCCESS:
// note: this is immutablejs syntax, use whatever you prefer
return state.set('makes', action.data);
case FAILURE:
return state;
default:
return state;
}
}
return state;
One thing to note is I have dispatch in my action creators because I use mapDispatchToProps in place of mapToProps. So your container looks something like this :
import * as actionCreators from './action-creators';
function mapStateToProps(state) {
return {
makes: state.get('makes')
};
}
function mapDispatchToProps(dispatch, ownProps) {
return {
initMyApp: actionCreators.initMyApp.bind(null, dispatch)
};
}
export default function(component = Component) {
return connect(mapStateToProps, mapDispatchToProps)(component);
}
then in your component componentWillMount or componentDidMount, pass in and call your init function
componentDidMount() {
this.props.initMyApp();
}

Categories