In a container, I've been using the "object shorthand" form of mapDispatchToProps to make available a single redux action (initialized as createRoutine from redux-actions) in an event handler:
const mapDispatchToProps = {
validateAddress,
}
In the handler, the action appears in this form:
function () { return dispatch(actionCreator.apply(this, arguments));}
All good. But when instead I define mapDispatchToProps as a function, so that I can add other actions that need access to dispatch, like so...
const mapDispatchToProps = (dispatch) => {
return {
validateAddress: () => dispatch(validateAddress()),
newAction1: .......,
newAction2: .......,
}
}
...my original action, validateAddress, ceases to work and appears now in this form instead:
function validateAddress() {
return dispatch(Object(_core_address_module__WEBPACK_IMPORTED_MODULE_9__["validateAddress"])());
}
I'm not sure why this is happening or how to restore the functionality of my original action. Any ideas? Thanks.
Your action creators such as validateAddress should return an object or a function that receives dispatch and getState functions for thunk actions.
Here is a working example:
const { Provider } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const initialState = {
validateAddress: 0,
};
//action types
const VALIDATE_ADDRESS = 'VALIDATE_ADDRESS';
//action creators
function validateAddress() {
return { type: VALIDATE_ADDRESS };
}
const reducer = (state, { type }) => {
if (type === VALIDATE_ADDRESS) {
return {
...state,
validateAddress: state.validateAddress + 1,
};
}
return state;
};
//selectors
const selectValidateAddress = (state) =>
state.validateAddress;
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const App = ({ validateAddress, actionCalled }) => {
return (
<button onClick={validateAddress}>
call action, called {actionCalled} times
</button>
);
};
const mapDispatchToProps = (dispatch) => {
return {
validateAddress: () => dispatch(validateAddress()),
};
};
const mapStateToProps = (state) => ({
actionCalled: selectValidateAddress(state),
});
const AppContainer = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(App);
ReactDOM.render(
<Provider store={store}>
<AppContainer />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
Related
I have a variable const foo: Array<ObjectExt> = useSelector(fooSelector); in a functional component. I want a copy of this variable from the first time the component is loaded that does not change when foo does.
When working with class components, I could simply have const fooCopy = foo.slice(); but that does not work here since the component reloads every time and fooCopy changes.
How do I achieve this in a functional component?
Just useState with the initial value as a copy of foo.
const foo : Array<ObjectExt> = useSelector(fooSelector);
const [origFoo] = useState(foo.slice());
Once origFoo has been initialized, it won't be re-initialized on rerender. You can destructure the setter out if you need to update its value later:
const [origFoo, setOrigFoo] = useState(foo);
// ...
if(someCondition) setOrigFoo(foo.slice())
const {useState} = React;
function App() {
const foo = [new Date().getTime()];
const [origFoo] = useState(foo.slice());
// Just so we have a way to force a rerender
const [count, setCount] = React.useState(0);
return (
<div>
<p>{JSON.stringify(foo)} </p>
<p>{JSON.stringify(origFoo)}</p>
<button onClick={() => setCount(count + 1)}>Update</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<App />,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
One solution is to set a flag in local component state. If that flag is fals, then make a copy of the value. Otherwise, don't.
The solution I'd use to accomplish this functionality, is to make a copy of foo, something like initialFoo in the store, and pick it in needed components.
I want a copy of this variable from the first time the component is loaded that does not change when foo does.
When you use useSelector(selector) then react-redux will run selector every time the state changes, if the return value is different than then last time it ran then react-redux will re render the component.
The easiest way of doing this is using a selector that returns only the value it got when called the first time:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const initialState = { count: 0 };
//action types
const ADD = 'ADD';
//action creators
const add = () => ({
type: ADD,
});
const reducer = (state, { type, payload }) => {
if (type === ADD) {
return { ...state, count: state.count + 1 };
}
return state;
};
//selectors
const selectCount = (state) => state.count;
//function returning a function
const createSelectInitialCount = () => {
//initialize NONE when createSelectInitialCount is called
const NONE = {};
let last = NONE;
//return the selector
return (state) => {
//check if last value was set
if (last === NONE) {
//set last value (only when called the first time)
last = selectCount(state);
}
return last;
};
};
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const InitialFoo = React.memo(function InitialFoo(props) {
const selectInitialCount = React.useMemo(//can also use useCallback
createSelectInitialCount,//createSelectInitialCount() if you use useCallback
[]
);
const foo = useSelector(selectInitialCount);
return (
<div>
<h3>initialfoo</h3>
<pre>
{JSON.stringify({ ...props, foo }, undefined, 2)}
</pre>
</div>
);
});
const App = () => {
const foo = useSelector(selectCount);
const dispatch = useDispatch();
const [other, setOther] = React.useState(0);
const [showFoo, setShowFoo] = React.useState(true);
const remountFoo = () => {
setShowFoo(false);
Promise.resolve().then(() => setShowFoo(true));
};
return (
<div>
<button onClick={() => dispatch(add())}>
foo:{foo}
</button>
<button onClick={() => setOther((o) => o + 1)}>
other{other}
</button>
<button onClick={remountFoo}>remount Foo</button>
{showFoo && <InitialFoo other={other} />}
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
Another way is to create a pure component using React.memo with a custom compare function that ignores foo:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const initialState = { count: 0 };
//action types
const ADD = 'ADD';
//action creators
const add = () => ({
type: ADD,
});
const reducer = (state, { type, payload }) => {
if (type === ADD) {
return { ...state, count: state.count + 1 };
}
return state;
};
//selectors
const selectCount = (state) => state.count;
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const InitialFoo = React.memo(
function InitialFoo(props) {
return (
<div>
<h3>initialfoo</h3>
<pre>{JSON.stringify(props, undefined, 2)}</pre>
</div>
);
},
//custom compare function when returning false
// component will re render
({ other }, { other: newOther }) => {
return other === newOther;
}
);
const InitialFooContainer = (props) => {
const foo = useSelector(selectCount);
//store foo in ref, will never change after mount
const fooRef = React.useRef(foo);
const newProps = { ...props, foo: fooRef.current };
return <InitialFoo {...newProps} />;
};
const App = () => {
const foo = useSelector(selectCount);
const dispatch = useDispatch();
const [other, setOther] = React.useState(0);
const [showFoo, setShowFoo] = React.useState(true);
const remountFoo = () => {
setShowFoo(false);
Promise.resolve().then(() => setShowFoo(true));
};
return (
<div>
<button onClick={() => dispatch(add())}>
foo:{foo}
</button>
<button onClick={() => setOther((o) => o + 1)}>
other{other}
</button>
<button onClick={remountFoo}>remount Foo</button>
{showFoo && (
<InitialFooContainer foo={foo} other={other} />
)}
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
I am using react, with redux for state management.
In my UI I have a button which when clicked it calls a function and that function internally calls 2 redux actions that perform the specific task.
The problem is my function when I make a call after a click of the button it immediately calls both the functions.
This is my on button click function
const handleDecrement = (productId) => {
props.minusQuantity(productId, props.guestIdData); // call make to redux action to perform API action
props.quantityData.forEach((item) => {
if (item.productQuantity < 1) { // value of state. Which is instantly changed
props.removeWholeItem(productId); // Another API call to redux action
}
});
};
I want to call this function first
props.minusQuantity(productId, props.guestIdData);
and thnen later the following code
props.quantityData.forEach((item) => {
if (item.productQuantity < 1) { // value of state. Which is instantly changed
props.removeWholeItem(productId); // Another API call to redux action
}
});
Assuming that minusQuantity is a thunk action that returns a promise you can await it:
const minusQuantity = (productId, guestIdData) => (
dispatch,
getState
) => {
return Promise.resolve('async value');
};
Now both of the other answers would work:
const handleDecrement = async (productId) => {
await props.minusQuantity(productId, props.guestIdData);
or
const handleDecrement = (productId) => {
props.minusQuantity(productId, props.guestIdData).then(
()=>{
//do other stuff here
}
)
Working example:
const { Provider, connect } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const initialState = {};
//action types
const BEFORE_ASYNC = 'BEFORE_ASYNC';
const AFTER_ASYNC = 'AFTER_ASYNC';
//action creators
const minusQuantity = () => (dispatch) => {
dispatch({ type: BEFORE_ASYNC });
//note it is returning a promise
return new Promise((resolve) =>
setTimeout(
() => resolve(dispatch({ type: AFTER_ASYNC })),
2000
)
);
};
const reducer = (state, { type }) => {
console.log('reducer called with action:', type);
return state;
};
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware((store) => (next) => (action) =>
typeof action === 'function' //DIY thunk middleware
? action(store.dispatch)
: next(action)
)
)
);
const App = (props) => {
const handleDecrement = (productId) => {
console.log('in handeDecrement starting async action');
props
.minusQuantity(productId)
.then(() =>
console.log('in handleDecrement after async action')
);
};
return (
<button onClick={handleDecrement}>
start async action
</button>
);
};
const AppContainer = connect(undefined, {
minusQuantity,
})(App);
ReactDOM.render(
<Provider store={store}>
<AppContainer />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
Use redux-thunk and bind your button onClick function as async.
handleClick = async () => {
await props.actions.someReduxThunkFn();
await props.actions.otherReduxThunkFn();
}
You can use .then and .catch and makes function calls one after another.
props.minusQuantity(productId, props.guestIdData).then(response=>{ props.removeWholeItem(productId) })
I have to use useDispatch() for my toggle buttons so I have to refractor them from react to redux state. I was following the tutorial of basics of Redux and I think I have done that properly but when I try to at least useSelector to display the redux'state of button it doesnt show anything.
So here is my code:
// types.js in actions folder
export const TOGGLE = "TOGGLE";
// buttonActions in actions folder
export const toggle = () => {
return {
type: 'TOGGLE'
};
};
// buttonReducer in reducers folder
const buttonReducer = (state = true, action) => {
switch(action.type) {
case 'TOGGLE':
return !state;
default:
return state;
};
};
export default buttonReducer;
And the buttonReducer is imported into combineReducers which go to store.
The component code:
import React, { useState, useEffect } from 'react'
import isloff from './mainpage_imgs/isloff.png'
import islon from './mainpage_imgs/islon.png'
import PropTypes from "prop-types";
import { connect, useDispatch, useSelector } from "react-redux";
import { toggle } from '../../actions/buttonActions'
const Islbutton = props => {
const [open, setOpen] = useState(true);
const [role, setRole] = useState('');
useEffect(() => {
if (props.auth.user)
{
setRole(props.auth.user.role);
}
}, []);
const test = useSelector(state => state.button);
const checkRole = (role) => {
if (role === 'Menager' || role === 'Technolog')
{
return true }
else
{
return false
};
}
const toggleImage = () => {
if(checkRole(role)) {
setOpen(!open)
};
}
const getImageName = () => open ? 'islOnn' : 'islOfff'
const dispatch = useDispatch();
return(
<div>
<img style={islplace} src={open ? islon : isloff }
onClick={()=> dispatch(toggle())} />
</div>
);
}
Islbutton.propTypes = {
button: PropTypes.func.isRequired,
auth: PropTypes.obj.isRequired
};
const mapStateToProps = state => ({
button: state.button,
auth: state.auth
});
export default connect(mapStateToProps, {}), (Islbutton);
Based on your latest comments and my understanding of your use case I may suggest following distilled approach:
//dependencies
const { render } = ReactDOM,
{ createStore } = Redux,
{ connect, Provider } = ReactRedux
//action creators
const SET_ROLE = 'SET_ROLE',
MANAGER_APPROVED = 'MANAGER_APPROVED',
setRole = role => ({type:SET_ROLE, role}),
mngAppr = () => ({type:MANAGER_APPROVED})
//initial state, reducer, store
const initialState = {role:'Technolog', approved:false},
appReducer = (state=initialState, action) => {
switch(action.type){
case SET_ROLE : {
const {role} = state,
{role: newRole} = action
return {...state, role: newRole}
}
case MANAGER_APPROVED : {
const {approved} = state
return {...state, approved: !approved}
}
default: return state
}
},
store = createStore(appReducer)
//ui component to emulate toggling roles
const SwitchRoles = ({currentRole, switchRole}) => (
<div>
<label><input type="radio" name="role" value="Manager" onChange={e => switchRole(e.target.value)} />Manager</label>
<label><input type="radio" name="role" value="Technolog" onChange={e => switchRole(e.target.value)} />Technolog</label>
</div>
)
//connect radio buttons click to togling roles action
const mapDispatch = dispatch => ({switchRole: role => dispatch(setRole(role))}),
SwitchRolesContainer = connect(null,mapDispatch)(SwitchRoles)
//ui component to toggle 'approved' within global state
const ToggleApprove = ({onApprove,isManager}) => (
<button onClick={onApprove} disabled={!isManager}>Toggle</button>
)
//connect onToggle handler to dispatching 'toggle' action
const mapStateToProps = ({role}) => ({isManager: role == 'Manager'}),
mapDispatchToProps = dispatch => ({onApprove: () => dispatch(mngAppr())}),
ToggleApproveContainer = connect(mapStateToProps, mapDispatchToProps)(ToggleApprove)
//ui component to display current state of 'open'
const IsApproved = ({isApproved}) => <div>{isApproved ? 'Approved by manager' : 'Not approved by manager'}</div>
//attach isOpen prop to global 'open' variable
const mapState = ({approved}) => ({isApproved: approved}),
IsApprovedContainer = connect(mapState)(IsApproved)
//render the app
render (
<Provider store={store}>
<SwitchRolesContainer />
<IsApprovedContainer />
<ToggleApproveContainer />
</Provider>,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.1.3/react-redux.min.js"></script><div id="root"></div>
Hopefully, it gives a piece of mind about toggling global variables and mapping their values onto local components state.
I am practicing reactjs and redux course. I am understanding the react part and also redux but could not grasp the knowledge of selector and binding done in below code.
Here is the code
const reducer = (state = 1, action) => {
switch (action.type) {
case 'INCREASE':
return state + 1;
default:
return state;
}
}
const selectCounter = state => state;
const AppPresentation = ({ text, onClick }) => (
<button onClick={onClick}>{text}</button>
);
const App = connect(
(state, { bindings: { selectText } }) => ({ text: selectText(state) }),
dispatch => ({ onClick() { dispatch({ type: 'BUTTON_CLICKED' }); }})
)(AppPresentation)
const onClickIncrease = function*(){
while (yield take('BUTTON_CLICKED'))
yield put({ type: 'INCREASE' });
}
const saga = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(saga)));
saga.run(onClickIncrease);
ReactDOM.render(
<Provider store={store}>
<App bindings={{ selectText: selectCounter }} />
</Provider>,
document.querySelector('#app'));
what is the advantage of above code over below code where selector and bindings has not done?
const reducer = (state = 1, action) => {
switch (action.type) {
case 'INCREASE':
return state + 1;
default:
return state;
}
}
const selectCounter = state => state;
const AppPresentation = ({ text, onClick }) => (
<button onClick={onClick}>{text}</button>
);
const App = connect(
state => ({ text: selectCounter(state) }),
dispatch => ({ onClick() { dispatch({ type: 'BUTTON_CLICKED' }); }})
)(AppPresentation)
const onClickIncrease = function*(){
while (yield take('BUTTON_CLICKED'))
yield put({ type: 'INCREASE' });
}
const saga = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(saga)));
saga.run(onClickIncrease);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector('#app'));
Can anyone please make me understand with simple English explanation? :) Sorry my English is poor.
mapStateToProps has the follwoing syntax mapStateToProps(state, [ownProps])
If ownProps is specified as a second argument, its value will be the props passed to your component, and mapStateToProps will be additionally re-invoked whenever the component receives new props (e.g. if props received from a parent component have shallowly changed, and you use the ownProps argument, mapStateToProps is re-evaluated)
In your case { bindings: { selectText } } will receive the props passed from the parent component
In the first code you are sending a prop to the App like <App bindings={{ selectText: selectCounter }} /> which is received in you mapStateToProps component while in the second code you are not passing any such prop to the component. Hence the first has an advantage over the second to allow you to pass props to the child and update the value in the child when the prop changes.
I am new to redux - why doesn't mapStateToProps get called and the component update to show 'hello world'?
http://codepen.io/anon/pen/QyXyvW?editors=0011
const helloReducer = (state= {message:'none'}, action) => {
switch (action.type) {
case 'HELLO':
return Object.assign(state,{message:"hello world"});
default:
return state;
}
};
const myApp = Redux.combineReducers({
helloReducer
});
const App = ({onClick,message}) => (
<div>
<a href="#" onClick={onClick}>click</a><b>{message}</b>
</div>
);
const mapStateToProps = (state, ownProps) => {
return {message: state.message}
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch({type: 'HELLO'})
}
}
}
const ConnectedApp = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(App);
let Provider = ReactRedux.Provider;
let store = Redux.createStore(myApp)
let e = React.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
document.getElementById('root')
);
You're assigning directly to "state" in your reducer, which is mutating it directly. You need to return Object.assign({}, state, {message:"hello world"}); instead.
Also note that React-Redux does a lot of work to make sure that a component's mapStateToProps function only runs when it absolutely has to.
replace line : return Object.assign(state,{message:"hello world"});
with this: return {...state, message:"hello world"};
It is ES6 spread operator.