First time working with Redux and I understand the concepts of Actions and Reducers but cannot get them to play nicely in my code and I'm worried it's because I'm using CoffeeScript instead of ES6.
I think the issue is I can't do the export default ... in my /app/reducers/index.coffee but honestly Redux is very new to me and I've been stumbling along with webpack and react for a while and am still not the most comfortable with either, so ¯\_(ツ)_/¯
I keep getting the error:
Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
/app/actions/index.coffee:
{ FETCH_ITEMS, FETCH_SINGLE_ITEM } = require('../constants')
fetch_items = ->
{ type: FETCH_ITEMS }
fetch_single_item = (id) ->
{
type: FETCH_SINGLE_ITEM,
id: id
}
module.exports = { fetch_items, fetch_single_item }
/app/reducers/items.coffee:
$ = require('jquery')
{ FETCH_ITEMS, FETCH_SINGLE_ITEM } = require('../constants')
{ fetch_items } = require('../actions')
INITIAL_STATE = []
items = (state=INITIAL_STATE, action) ->
console.log action.type
switch action.type
when FETCH_ITEMS
console.log 'FETCH_ITEMS'
$.get "http://dosomething.com/items", (resp) ->
console.log resp
when FETCH_SINGLE_ITEM
console.log 'do something'
else
console.log "suckit turk!"
state
module.exports = { items }
/app/reducers/index.coffee:
{ combineReducers } = require('redux')
items = require('./items')
reducers = combineReducers({ items })
module.exports = { reducers }
and finally /app/index.cjsx
React = require("react")
ReactDOM = require("react-dom")
{
createStore,
combineReducers,
appyMiddleware
} = require('redux')
{ reducers } = require("./reducers/")
STORE = createStore combineReducers({ reducers, routing: routerReducer })
HISTORY = syncHistoryWithStore(browserHistory, STORE)
STORE.subscribe () ->
console.log STORE.getState()
...omitted react render: ->...
EDIT
Sorry everyone, yes routeReducer is defined, I just omitted it because I'm pretty sure that it's not the problem.
/app/index.cjsx
React = require("react")
ReactDOM = require("react-dom")
{
createStore,
combineReducers,
appyMiddleware
} = require('redux')
{
syncHistoryWithStore,
routerReducer
} = require('react-router-redux')
{ reducers } = require("./reducers/")
STORE = createStore combineReducers({ reducers, routing: routerReducer })
HISTORY = syncHistoryWithStore(browserHistory, STORE)
STORE.subscribe () ->
console.log STORE.getState()
...omitted react render: ->...
A reducer must:
Receive a state and an action as argument
Always return the same or a new state.
Your "reducer" items only appears to return a state in case none of the cases in the switch statement match, which means it is not a valid reducer. If you are encountering this error when you emit one of those actions, redux could detect that a new state hasn't been returned. See whether you still encounter this issue after making items a proper reducer.
Side Note
You are making an asynchronous API call from your items reducer, and your stub code looks like you are planning to add a second one. This is not the correct use of a reducer; the store should hold data pertaining to your application state, and a reducer should manufacture a new state given an old state and an action type and/or payload.
Instead, call these sorts of asynchronous functions in an "action creator". (See docs.)
An action creator like fetch_items will execute the AJAX request, and on success trigger a dispatch of a set_items action, e.g. { type: 'SET_ITEMS', payload: [...response.items...] }. Your reducer will listen for the SET_ITEMS action and return a new state, updated with the payload.
Related
I have a Redux store configured and I'm trying to access the state from outside of a React component. I'm using store.getState() but it's returning the initial state values for everything in the store even though the store is populated with the correct data (visible in the UI and dev tools). It just seems to be that when the store is imported and the state is accessed via getState() that the initial values are returned instead of what is actually in the store. Code below.
store.ts
import * as _ from "#reduxjs/toolkit/node_modules/redux-thunk";
import { Action, configureStore } from "#reduxjs/toolkit";
import { ThunkAction } from "redux-thunk";
import { useDispatch } from "react-redux";
import reducers from "./reducers";
const store = configureStore({
reducer: reducers,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export type AppDispatch = typeof store.dispatch;
export type IRootState = ReturnType<typeof store.getState>;
export type AppThunk = ThunkAction<void, IRootState, unknown, Action<string>>;
// Export a hook that can be reused to resolve types
export const useAppDispatch = () => useDispatch<AppDispatch>();
export default store;
Service that I'm calling the store from:
import store from "store";
import { LoggingService } from "logger";
const logData(data) {
const user = store.getState().user;
LoggingService.log(user.email, data);
}
In the example above, the store.getState().user returns an object, but the email is null. However, the email is actually populated as it can be seen in the UI and in dev tools. Any help would be much appreciated.
Also to note, this is not an SSR app.
The store.getState() only returns current state from redux store once. Subsequent changes to the state via dispatch action will not call this method again unless use subscribe(listener).
Instead of using store.subscribe directly, we can write a custom observable store utility. Why? see issue#303
index.js:
export function toObservable(store) {
return {
subscribe({ onNext }) {
let dispose = store.subscribe(() => onNext(store.getState()));
onNext(store.getState());
return { dispose };
},
};
}
How to use it? Let's write a test to explain this:
import { combineReducers, createStore } from 'redux';
import { toObservable } from './';
const LoggingService = {
log: console.log,
};
describe('toObservable', () => {
test('should pass', () => {
function userReducer(state = { email: '' }, action) {
switch (action.type) {
case 'GET_USER_FULFILLED':
return { ...state, ...action.payload };
default:
return state;
}
}
const store = createStore(combineReducers({ user: userReducer }));
// create store observable somewhere, may be in the application initialization phase
const store$ = toObservable(store);
store$.subscribe({ onNext: (state) => LoggingService.log(state.user.email) });
// Later, dispatch action in component
store.dispatch({ type: 'GET_USER_FULFILLED', payload: { email: 'example#gmail.com' } });
});
});
Test result:
PASS redux-toolkit-example packages/redux-toolkit-example/examples/observer-store/index.test.ts
toObservable
✓ should pass (23 ms)
console.log
at onNext (packages/redux-toolkit-example/examples/observer-store/index.test.ts:22:58)
console.log
example#gmail.com
at onNext (packages/redux-toolkit-example/examples/observer-store/index.test.ts:22:58)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.937 s
As you can see, every time the state changes, the onNext method will be called.
store.getState() does not update immediately.
You have to use useSelector hook in Functional Component in order to get the updated state from the redux.
like:
const user = useSelector((state) => state.user);
I have generated a project using Create React App and then added Redux.
The redux state is then split into three parts that each has its own reducer and some middleware defined. The reducers are place in files called part1.js part2.js part3.js there is then a common.js file that imports the reducer and the middleware from part1-2-3.js and adds them to combineReducer and applyMiddeware.
My question is if there is anyway to not having to import everything in one place. What I want is to be able to add the reducer and middeware to comineReducer and applyMiddleware from within part1-2-3.js, the reason is to get rid of an explicit common boilerplate code file in common.js. Is this possible or is the only way to import everything into one place?
UPDATE
I have now great examples on how to solve the combineReducer part, however I still need to do something similar for applyMiddleware. I have found an example from the following repo on how to do something similar with applyMiddleware. However its in TypeScript and I have a hard time translating it into what is the minimal way to get this working within a JS React/Redux application. Would be great with some examples.
UPDATE
So I finally found this minimal library doing what I want.
Yes! I have a reducer registry which is similar (almost identical) to this reducer manager: https://redux.js.org/recipes/code-splitting#using-a-reducer-manager:
const DEFAULT_REDUCER = state => state || null;
export class ReducerRegistry {
constructor() {
this._emitChange = null;
this._reducers = {};
}
getReducers() {
// default reducer so redux doesn't complain
// if no reducers have been registered on startup
if (!Object.keys(this._reducers).length) {
return { __: { reducer: DEFAULT_REDUCER } };
}
return { ...this._reducers };
}
register(name, reducer, options = {}) {
if (this._reducers.name && this._reducers.name !== reducer) {
throw new Error(`${name} has already been registered`);
}
this._reducers = { ...this._reducers, [name]: { reducer, options } };
if (this._emitChange) {
this._emitChange(this.getReducers());
}
}
setChangeListener(listener) {
this._emitChange = listener;
}
}
const reducerRegistry = new ReducerRegistry();
export default reducerRegistry;
Then I have my redux domains organized into folders like reducks-style: https://github.com/alexnm/re-ducks
In the index.js of the reducks domain, I import the reducer registry and register the reducer:
import Domain from './name';
import reducer from './reducer';
import { reducerRegistry } from '...wherever';
reducerRegistry.register(Domain, reducer); // register your reducer
export { ... whatever }
Finally, my store uses the reducer registry like this:
export const store = createStore(
combine(reducerRegistry.getReducers()),
initialState,
composeEnhancers(applyMiddleware(...middlewares))
);
// Replace the reducer whenever a new reducer is registered (or unregistered).!!!!
// THIS IS THE MAGIC SAUCE!
reducerRegistry.setChangeListener(reducers =>
store.replaceReducer(combine(reducers))
);
export default store;
This setup has worked magically for us. Allows us to keep all of our redux logic very isolated from the rest of the application (and from other redux domain logic!), works fantastic for code-splitting. Highly recommend it.
Yes you can. We use it with dynamic import and works well. We use with react hooks.
use store.replaceReducer https://redux.js.org/api/store#replacereducernextreducer
in configureStore (or any file when you call createStore from redux)
const store = createStore(/*...*/)
add
store.injectedReducers = {}; // Reducer registry
and create a new file with injectReducer hook.
const useInjectReducer reducer => {
const store = useStore();
const key = Object.keys(reducer)[0];
if (
Reflect.has(store.injectedReducers, key) &&
store.injectedReducers[key] === reducer[key]
) {
return;
}
store.injectedReducers = {
...store.injectedReducers,
...reducer
};
store.replaceReducer(combineReducers(store.injectedReducers));
}
and you can use it in react App:
export const TodoPage = () => {
useInjectReducer({ [REDUCER_NAME]: TodoReducer });
/*...*/
}
if you use server side rendering you need to be sure redux not cleaning up the states for missing reducers before dynamic import. You can create a dummyReducers to prevent that.
const dummyReducers = Object.keys(initialState).reduce((acc, current) => {
acc[current] = (state = null) => state
return acc;
}, {});
and add this for:
const store = createStore(
combineReducers(dummyReducers),
initialState
)
We use the same pattern to Inject Sagas.
so I am trying to refactor some code from my previous question:
React: How to update one component, when something happens on another component
So I started digging deep into the existing code template to see how it was implemented.
I found a reducers.js where I added a new reducer: ActiveTenant
import Auth from './auth/reducer';
import App from './app/reducer';
import ThemeSwitcher from './themeSwitcher/reducer';
import LanguageSwitcher from './languageSwitcher/reducer';
import ActiveTenant from './activetenant/reducer';
export default {
Auth,
App,
LanguageSwitcher,
ThemeSwitcher,
ActiveTenant
};
That new reducer is like this:
import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';
const initState = new Map({
tenantId: ''
});
export default function(state = initState, action) {
switch (action.type) {
case actions.SET_TENANT_ACTIVE:
{
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
.then(response =>{
if(response.status === 200){
console.log("Tenant activated");
}else{
throw "error";
}
})
.catch(error => {
console.error(error);
});
return state.set('tenant', state.Name);
}
default:
return state;
}
}
and actions for that reducer
const actions = {
SET_TENANT_ACTIVE: 'SET_TENANT_ACTIVE',
setTenantActive: () => ({
type: actions.SET_TENANT_ACTIVE
}),
};
export default actions;
Then from the component itself, I need to call the action when a row is selected on the front end, so I have refactored the commented code, into one line.
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';
const { setTenantActive } = actions;
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.id,
TestSiteCollectionUrl: row.TestSiteCollectionUrl,
TenantName: row.TenantName,
Email: row.Email
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'TenantName',
dataIndex: 'TenantName',
key: 'TenantName',
},
{
title: 'TestSiteCollectionUrl',
dataIndex: 'TestSiteCollectionUrl',
key: 'TestSiteCollectionUrl',
},
{
title: 'Email',
dataIndex: 'Email',
key: 'Email',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRows[0].TenantName != undefined){
console.log(selectedRows[0].TenantName);
const options = {
method: 'post'
};
setTenantActive(selectedRows[0].TenantName);
/* adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
.then(response =>{
if(response.status === 200){
Notification(
'success',
'Tenant set to active',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Tenant not activated',
error
);
console.error(error);
}); */
}
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
However, its not clear to me the relationship between the action and the reducer, if I check the debugger the action is executed, and none parameter is received, but the reducer is never executed.
DO i have to put a dispatch somewhere?, what I am missing in this puzzle?
So the first thing to understand is the Redux Cycle:
Action Creator-->Action-->dispatch-->Reducers-->State
Action Creator: An action creator is a function that is going to create or return a plain JavaScript object knowns as an Action with a type property and payload property which describes some change you want to make on your data.
The payload property describes some context around the change we want to make.
The purpose of an Action is to describe some change to the data inside our application.
The Action Creator is to create the Action.
The dispatch function is going to take in an Action and make copies of that object and pass it off to a bunch of different places inside our application which leads us to the Reducers.
In Redux, a reducer is a function responsible for taking in an Action. Its going to process that Action, make some change to the data and return it so it can be centralized in some location.
In Redux, the State property is a central repository of all information produced by our reducers. All the information gets consolidated inside the State object so our React application can easily reach into our Redux side of the app and get access to all the data inside the application.
So this way the app does not have to go around to each separate reducer and ask for the current State.
So digest that for a couple of minutes and then look at your architecture.
Let's skip over to reducers.
Reducers are called with an Action that was created by an Action Creator. The reducer will take a look at that Action and decide whether it needs to modify some data based on that Action.
So in other words, the job of a reducer is not to execute API requests but to process actions sent to it by the action creator.
So instead of this:
import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';
const initState = new Map({
tenantId: ''
});
export default function(state = initState, action) {
switch (action.type) {
case actions.SET_TENANT_ACTIVE:
{
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
.then(response =>{
if(response.status === 200){
console.log("Tenant activated");
}else{
throw "error";
}
})
.catch(error => {
console.error(error);
});
return state.set('tenant', state.Name);
}
default:
return state;
}
}
Your reducer should look something like this:
import { SET_TENANT_ACTIVE } from "../actions/types";
const initialState = {
tenantId: ''
};
export default (state = initialState, action) {
switch (action.type) {
case SET_TENANT_ACTIVE:
return {...state, [action.payload.id]: action.payload };
default:
return state;
}
}
Then inside your action creators file, you should have an action creator that looks something like this:
import axios from 'axios';
import { SET_TENANT_ACTIVE } from "../actions/types";
export const setTenant = id => async (dispatch) => {
const response = await axios.post(`/tenants/${id}`);
dispatch({ type: SET_TENANT_ACTIVE, payload: response.data });
};
You also need to learn about Redux project structure because after the above refactor, you are missing how to wire all this up to your component. In your component file there is no connect() function which also requires the Provider tag and you have none of that.
So for this I recommend first of all your set up your folder and file structure like so:
/src
/actions
/components
/reducers
index.js
So inside your index.js file it should look something like this:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import reduxThunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(reduxThunk))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
So your goal here is to ensure that you get that Provider tag at the very top of your component hierarchy and ensure that you pass it a reference to your Redux store that gets all the reducers loaded up into it.
So above I have created the store and passed it our set of reducers and it will return back to you all your applications State.
Lastly, what you see above is I created an instance of <Provider> and wrapped the <App /> component with it and then you want to pass the <Provider> component is a single prop called store. The store is the result of calling createStore() and calling the reducers.
The <Provider> is what interacts with the Redux store on our behalf.
Notice, I also have wired up Redux-Thunk that J. Hesters mentioned, you are making an ajax request as far as I can see from your code which is why I offered an asynchronous action creator for you, which means you will need Redux-Thunk or some middleware like that, let me not offend the Redux-Saga fans, so you have those two choice at least. You seem relatively new to Redux, just go with Redux-Thunk.
Now you can use the connect() component inside your component file to finish wiring up those action creators and reducers to your component or your React side of the application.
import React, { Component } from 'react';
import { connect } from "react-redux";
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';
After importing connect, you create an instance of it below:
export default connect()(ListTenants);
Please don't argue with me on the above syntax (actually had a former student report me to administrators for using this syntax as evidence of not knowing what I was doing).
Then you need to configure this connect() React component by adding mapStateToProps if you are going to need it, but definitely pass in actions as the second argument to connect(). If you realize you don't need mapStateToProps, then just pass in null as the first argument, but you can't leave it empty.
Hope all this was helpful and welcome to the wonderful world of React-Redux.
You are using reducers wrong. Reducers are supposed to be pure. Yours has side-effects showing that you haven't understood Redux, yet.
Instead of writing down a solution for you (which would take forever anyways since one would have to explain Redux in total), I suggest you invest the 3 hours and go through the Redux docs and follow the tutorials (they are great).
Afterwards you might want to look into Redux Thunk. But, you might not need thunks.
PS: (Small thing to bring up, but I haven't seen anyone using Maps in Redux. Is there a reason you do that? You might want to use plain objects instead.)
Your action is not correct you should pass an active tenant name as parameter.
Ref. https://redux-starter-kit.js.org/api/createaction
We could have written the action types as inline strings in both places.
The action creators are good, but they're not required to use Redux - a component could skip supplying a mapDispatch argument to connect, and just call this.props.dispatch({type : "CREATE_POST", payload : {id : 123, title : "Hello World"}}) itself.
Ref. https://redux-starter-kit.js.org/usage/usage-guide
I'm new to Redux (and React as well) so this is probably a very elementar question, but I was unable to find a straight answer for it over the Internet.
There is a sign up screen on my app that I call SignUp. It is pretty straightforward and have just three inputs (email, password and passwordCheck) and one button (signup).
Thing is, I want to make my user life as simple as possible, so as passwordCheck is changed, I check it against password. If them match, signup is setted enabled and if them don't, signup goes disabled and a message is show to the user.
When I was using only React things were pretty simple - I simply made a this.state:
this.state: {
// ... stuff
password: "",
passwordIsValid: false, //test password against a predicate function
passwordMessage: "", //if the password is not valid there is a message
passwordStyle: "", //if password is wrong i put some classes here
passwordCheck: "",
passwordCheckMessage: "", //the 'passwords do not match' message goes here
passwordCheckStyle: "",
passwordsMatch: false
}
And then I handled the app's state inside SignUp.
Now I'm using Redux as well, so I killed this.state, moved everything to store and started using reducers instead of handlers.
Everything works for password, but passwordCheck is a different story. Since I need to know Store's password to check it against passwordCheck I have been unable (so far?) to do it on passwordCheckReducer. Instead, I removed passwordCheckMessage, passwordCheckStyle and passwordsMatch from passwordCheckReducer, calculating this values on SignUp. It seems to me as a very wrong and ugly way to settle the whole thing down though.
So, instead, I would like a more elegant and Redux-like solution.
If I could get store to be on passwordCheckReducer's state I would be able to set passwordsMatch and the others in the reducer while keeping it pure. Unfortunately, I have been unable to do it (so far?).
So, I would like to know if what I want to do is possible or if there's others, more desirables, ways to do it.
OBS1: Wherever I look on the Internet, I found the Redux official documentation on the subject of initializing state1. I do not think preloadedState is the way to resolve my problem, though, since it is used to iniatilize store, not to make it avaiable on my reducers. Instead, I simply need to have store - even when empty - visible to passwordCheckReducer.
OBS2: I know I could pass store as a key of action, but since the reducer pattern includes a state argument it seems redundant to me to define pass such in action.
Here is my Store:
// store.js
import { applyMiddleware, createStore } from 'redux';
import { createLogger } from 'redux-logger';
import thunkMiddleare from 'redux-thunk';
import { Reducers } from '../reducers/reducers';
const logger = createLogger();
const middleware = applyMiddleware(thunkMiddleare, logger);
export const Store = createStore(Reducers, middleware);
I'm using combineReducers:
// reducers.js
import { combineReducers } from 'redux';
import { emailReducer } from './emailReducer';
import { passwordReducer } from './passwordReducer';
import { passwordCheckReducer } from './passwordCheckReducer';
export const Reducers = combineReducers({
emailChange: emailReducer,
passwordChange: passwordReducer,
passwordCheckChange: passwordCheckReducer
});
And here is passwordCheckReducer:
// passwordCheckReducer.js
import { CHANGE_PASSWORDCHECK } from '../actions/actionTypes';
const initialState = {
passwordCheck: ""
};
export const passwordCheckReducer = (state=initialState, action) => {
const { payload, type } = action;
switch(type) {
case CHANGE_PASSWORDCHECK:
const passwordCheck = payload;
return {
...state,
passwordCheck
};
default:
return state;
}
};
Last, this is the implementation of mapDispatchToProps:
// SignUp.js
const mapDispatchToProps = dispatch => ({
handleEmailChange: e => dispatch(updateEmail(e.target.value)),
handlePasswordChange: e => dispatch(updatePassword(e.target.value)),
handlePasswordCheckChange: e => dispatch(updatePasswordCheck(e.target.value))
});
If I remember correctly, when you pass your reducer to combineReducers, that reducer will receive the redux module state (so, what the initialState was, not the entire app's root state object). You don't have access to state from other reducers.
You have a few options.
I think having separate reducers for password and passwordCheck is somewhat overkill. I would go with one reducer for the entire form:
const initialState = {
password: '',
passwordCheck: '',
// ....
}
export const signupReducer = (state=initialState, action) => {
const { payload, type } = action;
switch(type) {
case CHANGE_PASSWORD:
return { ...state, password: action.password}
case CHANGE_PASSWORDCHECK:
const passwordCheck = payload;
if (state.password != state.passwordCheck)
return { ...state, passwordCheck, error: "..."}; // or something like this
return {
...state,
passwordCheck
};
default:
return state;
}
};
If you want to split things up, you can always define reducers, and call them from a parent reducer, passing the whole parent-reducer state. Something like:
import passwordReducer from './passwordReducer'
import passwordCheckReducer form './passwordCheckReducer'
export const signupReducer(state = initialState, action) => {
state = passwordReducer(state, action)
state = passwordCheckReducer(state, action)
return state
}
If you are combining reducers, there is only one store object which stores all of the state from every reducer.
So you can access multiple reducers from one component like this:
const mapStateToProps = state => ({
password: state.passwordChange.password,
passwordCheck: state.passwordCheckChange.passwordCheck,
passwordsMatch: state.passwordCheckChange.passwordsMatch
})
export default connect(mapStateToProps, mapDispatchToProps)(SignUp)
Every time you update passwordCheck or password, your component's props will be updated.
So the best solution is actually to handle the passwordsMatch as a custom prop derived from the state of the two separate reducers. Something like:
const mapStateToProps = state => {
const passwordsMatch = state.passwordChange.password === state.passwordCheckChange.passwordCheck
return {
password: state.passwordChange.password,
passwordCheck: state.passwordCheckChange.passwordCheck,
passwordsMatch: passwordsMatch
}
}
I just started to work on React Js and Redux-Thunk. Currently, I am trying to fetch data from a url using redux-thunk. I got data successfully but the issue is that it renders undefined data twice, then it gives me the desired data in props.
Here is my code.
In Actions
index.js
function getData() {
return {
type: 'FETCH'
}
}
function getSuccess(data) {
return {
type: 'FETCH_SUCCESS',
payload: data
}
}
function getFailed(err) {
return {
type: 'FAILED',
payload: err
}
}
export function fetchData() {
const thunk = async function thunk(dispatch) {
try {
dispatch(getData());
const body = await fetch("http://jsonplaceholder.typicode.com/users")
const res = await body.json();
console.log("Thunk", res);
dispatch(getSuccess(res));
}
catch(err) {
dispatch(getFailed(err));
}
}
return thunk;
}
In Reducers fetch.js
const initialState = {
state: []
}
export default function(state=initialState , actions) {
switch(actions.type) {
case 'FETCH':
return {
...state
}
case 'FETCH_SUCCESS':
return {
...state,
data: actions.payload
}
case 'FAILED':
return {
...state
}
default:
return state;
}
}
Reducers Index.js
import fetch from './fetch';
import { combineReducers } from 'redux';
const rootReducer = combineReducers ({
fetch
});
export default rootReducer;
App.js
import React, { Component } from 'react';
import './App.css';
import Main from './component/Main';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
console.log("STore", store.getState());
class App extends Component {
render() {
return (
<Provider store={store}>
<Main/>
</Provider>
);
}
}
export default App;
Main.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class Main extends Component {
componentWillMount() {
const { fetchData } = this.props
fetchData();
}
render() {
let mydata = this.props.data.data;
console.log("Data .....<>", mydata);
return(
<div>
Main
</div>
);
}
}
const mapStateToProps =(state)=> {
return {
data: state.fetch
}
}
export default connect(mapStateToProps, {fetchData: actions.fetchData})(Main);
Output Screen Shot
Please let me know what i am doing wrong. Any help will be appreciated.
Thanks
This behavior is correct. You're doing everything in the normal way, except calling async operations in componentWillMount method instead of componentDidMount.
Read more here about it.
You need to know, that when you are using componentDidMount - you are handle this in a safe way due to commit phase in component lifecycle and it means that your request will be guaranteed trigger once instead of possible several times, which can be in render phase.
See here the visual diagram to understand this more.
Regarding several renderings - it can be explained easily.
First time, when your component is mounting you are calling asynchronous operation and it needs more time to load data than for component mounting. Thats why you are accessing data property of your initialState (which is empty array), and getting undefined.
When you response is ready and actions is being dispatched, redux trigger re-render of connected components with new state.
If you want to make your async-await syntax works you should make lifecycle with async keyword as well and await your request inside.
NOTE: It's safe, but in several cases it might cause unexpected bugs, so keep in mind. Nevertheless, I don't recommend to use it in a such way. Read about this topic in the another thread at SO.
I advice you to create some isLoading boolean status in your reducer and keep track whether data is loaded or not.
Good luck! Hope it will helps!
There is nothing wrong with your code, but there is one unnecessary action.
Why do you see two times undefined and then you see your data?
First one is coming from the initial render. You are making an async dispatching in your componentWillMount so render does not wait for it, then try to log your data. At that time it is undefined.
Your fetchData dispatches two actions: getData and getSuccess. You see second undefined because of getData action since it returns current state which props.data.data undefined at that time again.
You see the data since getSuccess updates your state now.
If you want to test this comment out getData in your fetchData function, just use getSuccess and change your console.log in the render like that:
mydata && console.log("Data .....<>", mydata);
I think getData action is unnecessary here, it does not do anything beside returning the current state. Also, try to use componentDidMount instead of componentWillMount since it will be deprecated in version 17.