Can I use a selector from a reducer?
I have a code similar to that:
case Action Types.ACTION: {
const loading = getIsLoading(state)
return {
atribute1: false,
attribute2: action.payload,
}
}
default: {
return state;
}
}
}
export const getState = createFeatureSelector<State>('feature');
export const getIsLoading = createSelector(
getState,
state => state.loading
);
When I dispatch a action at first I get a error like that: "can't read 'loading' of undefined.
Where is my mistake? Something wrong with my approach?
Like I've stated in the comment: It's a best practice not to duplicate information in the state. If you need to call a selector in a reducer it looks like you'll do just this.
You can avoid doing this by creating a third, higher order, reducer that will return whatever is needed from the existing ones.
So you have the getIsBusy reducer and you create another one that will just return the new data from your state, the one where you wanted to read isBusy:
export const getAttribute = createSelector(
getState,
state => state.attribute
);
Now you have getIsBusy and getAttribute as selectors and you can create a third one where you can place whatever logic you need:
export const getHigherOrderSelector = createSelector(
getIsBusy,
getAttribute,
(isBusy, data) => {
return { isBusy, data };
// Or whatever other processing you might want to do here
}
);
Related
I'm using React and Redux in my web app.
In the login page, I have multiple fields (inputs).
The login page in composed from multiple components to pass the props to.
I was wondering how should I pass the props and update actions.
For example, lets assume I have 5 inputs in my login page.
LoginPage (container) -> AuthenticationForm (Component) -> SignupForm (Component)
In the LoginPage I map the state and dispatch to props,
and I see 2 options here:
mapStateToProps = (state) => ({
input1: state.input1,
...
input5: state.input5
})
mapDispatchToProps = (dispatch) => ({
changeInput1: (ev) => dispatch(updateInput1(ev.target.value))
...
changeInput5: (ev) => dispatch(updateInput5(ev.target.value))
})
In this solution, I need to pass a lot of props down the path (the dispatch actions and the state data).
Another way to do it is like this:
mapStateToProps = (state) => ({
values: {input1: state.input1, ..., input5: state.input5}
})
mapDispatchToProps = (dispatch) => ({
update: (name) => (ev) => dispatch(update(name, ev.target.value))
})
In this solution, I have to keep track and send the input name I want to update.
How should I engage this problem?
It seems like fundamental question, since a lot of forms have to handle it,
but I couldn't decide yet what would suit me now and for the long run.
What are the best practices?
I think best practice would be to handle all of this logic in the React component itself. You can use component's state to store input's data and use class methods to handle it. There is good explanation in React docs https://reactjs.org/docs/forms.html
You probably should pass data in Redux on submit. Ether storing whole state of the form as an object, or not store at all and just dispatching action with api call.
TL;DR. it's a more 'general' coding practice. But let's put it under a react-redux context.
Say if you go with your first approach, then you will probably have 5 actionCreators as:
function updateInput1({value}) { return {type: 'UPDATE_INPUT1', payload: {value}} }
...
function updateInput5({value}) { return {type: 'UPDATE_INPUT5', payload: {value}} }
Also if you have actionTypes, then:
const UPDATE_INPUT1 = 'UPDATE_INPUT1'
...
const UPDATE_INPUT5 = 'UPDATE_INPUT5'
The reducer will probably look like:
function handleInputUpdate(state = {}, {type, payload: {value}}) {
switch (type) {
case UPDATE_INPUT1: return {..., input1: value}
...
case UPDATE_INPUT5: return {..., input5: value}
default: return state
}
}
What's the problem? I don't think you're spreading too many props in mapStateToProps/mapDispatchToProps, Don't repeat yourself!
So naturally, you want a more generic function to avoid that:
const UPDATE_INPUT = 'UPDATE_INPUT'
function updateInput({name, value}) { return {type: UPDATE_INPUT, payload: {name, value}} }
function handleInputUpdate(state = {inputs: null}, {type, payload: {name, value}}) {
switch (type) {
case UPDATE_INPUT: return {inputs: {...state.inputs, [name]: value}}
default: return state
}
}
Finally, the "selector" part, based upon how the state was designed, get component's props from it would be fairly trivial:
function mapStateToProps(state) { return {inputs: state.inputs} }
function mapDispatchToProps(dispatch) { return {update(name, value) { dispatch(updateInput(name, value)) } }
In summary, it's not necessarily a redux/react problem, it's more how you design app state, redux just offers you utilities and poses some constraints to enable "time traveling" (state transitions are made explicit within a mutation handler based on a separate action).
Best practice to handle this problem is having a local state on your Form Component and managing it locally because I believe it's not a shared state. onSubmit you could dispatch your action passing down the state to the action which is required in making an API call or posting it to your server.
If you try to keep updating your store as the user types, it will keep dispatching the action which might cause problems in future. You read more here Handling multiple form inputs in react
I'm considering using Redux for my app, but there's a common use case that I'm not sure how to handle with it. I have a component that displays some object and allows the user to edit it. Every action will create a shallow copy of the object, but what then? How is the component supposed to know how to update the storage with it? In the samples I see that the component is passed a key instead of the actual object, but doesn't that break the concept of incapsulation, since a component isn't supposed to know where it's state/props come from? I want the component to be fully reusable, so it receives an object and information on how to update it in a more general form, which seems to be awkward to implement with Redux (I'm going to have to pass write callbacks to every component, and then chain them somehow).
Am I using Redux wrong, or is there a more suitable alternative for this use case? I'm thinking of making one myself (where every state object knows it's owner and key via some global WeakMap), but I don't want to be reinventing the wheel.
For instance, if my storage looks like this:
Storage = {
items: {
item1: { ... },
item2: { ... },
...
},
someOtherItems: {
item1: { ... },
...
},
oneMoreItem: { ... },
};
I want to be able to display all item objects with the same component. But the component somehow has to know how to write it's updated item back to the storage, so I can't just pass it item1 as key. I could pass a callback that would replace a specific item in the (cloned) storage, but that doesn't work well if, for instance, I have a component that displays a list of items, since I would have to chain those callbacks somehow.
This is a common use case, and yes - you're missing the point here. react/redux makes this really easy.
I usually structure it as follows: Components receive a modelValue object prop and changeValue function prop. The former is the current value, the latter is the function we call to change the value. These props are going to be supplied by redux.
Now we write a connect hoc (higher order component), a simple example might look like this:
const mapStateToProps = (state, ownProps) => {
return {
modelValue: _.get(state, ownProps.model),
};
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
changeValue: (val) => dispatch({
type: "your/reducer/action",
model: ownProps.model,
value: val,
})
};
};
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
...dispatchProps,
...ownProps,
};
};
const MyConnectedComponent = connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyGenericComponent);
This is an example where we pass in a model string to the hoc, and it wires up modelValue and changeValue for us. So now all we need to do is pass in a model like "some.javascript.path" to our component and that's where it will get stored in the state. MyGenericComponent still doesn't know or care about where it's stored in the state, only MyConnectedComponent does.
Usage would be as follows:
<MyConnectedComponent model="some.path.in.the.state" />
And inside MyGenericComponent just consume modelValue for the current value, and execute changeValue to change the value.
Note that you need to also wire up a redux reducer to handle your/reducer/action and actually do the update to the state, but that's a whole other topic.
Edit
You mentioned that you need sub components to be aware of the parent state, this can be achieved by passing model via context. The following examples are using recompose:
const mapStateToProps = ...
const mapDispatchToProps = ...
const mergeProps = ...
const resolveParentModel = (Component) => {
return (props) => {
// we have access to 'model' and 'parentModel' here.
// parentModel comes from parent context, model comes from props
const { parentModel, model } = props;
let combinedModel = model;
// if our model starts with a '.' then it should be a model relative to parent.
// else, it should be an absolute model.
if (model.startsWith(".")) {
combinedModel = parentModel + model;
}
return <Component {...props} model={combinedModel} />;
}
}
const myCustomHoc = (Component) => (
// retrieve the current parent model as a prop
getContext({ parentModel: React.PropTypes.string })(
// here we map parent model and own model into a single combined model
resolveParentModel(
// here we map that combined model into 'modelValue' and 'changeValue'
connect(mapStateToProps, mapDispatchToProps, mergeProps)(
// we provide this single combined model to any children as parent model so the cycle continues
withContext({ parentModel: React.PropTypes.string }, (props) => props.model)(
Component
)
)
)
)
);
In summary, we pass a context value parentModel to all children. Each object maps parent model into it's own model string conditionally. Usage would then look like this:
const MyConnectedParentComponent = myCustomHoc(MyGenericParentComponent);
const MyConnectedSubComponent = myCustomHoc(MyGenericSubComponent);
<MyConnectedParentComponent model="some.obj">
{/* the following model will be resolved into "some.obj.name" automatically because it starts with a '.' */}
<MyConnectedSubComponent model=".name" />
</MyConnectedParentComponent>
Note that nesting this way could then go to any depth. You can access absolute or relative state values anywhere in the tree. You can also get clever with your model string, maybe starting with ^ instead of . will navigate backwards: so some.obj.path and ^name becomes some.obj.name instead of some.obj.path.name etc.
Regarding your concerns with arrays, when rendering arrays you almost always want to render all items in the array - so it would be easy enough to write an array component that just renders X elements (where X is the length of the array) and pass .0, .1, .2 etc to each item.
const SomeArray = ({ modelValue, changeValue }) => (
<div>
{modelValue.map((v, i) => <SomeChildEl key={i} model={"." + i} />)}
<span onClick={() => changeValue([...modelValue, {}])} >Add New Item</span>
</div>
);
I need to pass props to selectors so that i can fetch the content of the clicked item from the selectors. However i could not pass the props. I tried this way but no success
const mapStateToProps = createStructuredSelector({
features: selectFeatures(),
getFeatureToEditById: selectFeatureToEditById(),
});
handleFeatureEdit = (event, feature) => {
event.preventDefault();
console.log("feature handle", feature);
const dialog = (
<FeatureEditDialog
feature={feature}
featureToEdit={selectFeatureToEditById(feature)}
onClose={() => this.props.hideDialog(null)}
/>
);
this.props.showDialog(dialog);
};
selectors.js
const selectFeatureState = state => state.get("featureReducer");
const selectFeatureById = (_, props) => {
console.log("props", _, props); #if i get the id of feature here
# i could then filter based on that id from below selector and show
# the result in FeatureEditDialog component
};
const selectFeatureToEditById = () =>
createSelector(
selectFeatureState,
selectFeatureById,
(features, featureId) => {
console.log("features", features, featureId);
}
);
Here is the gist for full code
https://gist.github.com/MilanRgm/80fe18e3f25993a27dfd0bbd0ede3c20
Simply pass both state and props from your mapStateToProps to your selectors.
If you use a selector directly as the mapStateToProps function, it will receive the same arguments mapState does: state and ownProps (props set on the connected component).
A simple example:
// simple selector
export const getSomethingFromState = (state, { id }) => state.stuff[id]
// reselect selector
export const getStuff = createSelector(
getSomethingFromState,
(stuff) => stuff
)
// use it as mapDispatchToProps
const mapDispatchToProps = getSomethingFromState
const MyContainer = connect(mapDispatchToProps)(MyComponent)
// and set id as an own prop in the container when rendering
<MyContainer id='foo' />
However you're doing some weird things like mapping a selector to reuse it later. It doesn't work that way, at least it's not intended to be used that way.
You use selectors to retrieve slices of your state and pass it as props to your connected components. Whenever the state changes, your selectors will be re-run (with some caching thanks to reselect). If something the component is actually retrieving from Redux has changed indeed, it will re-render.
So your FeatureEditDialog component should be connected as well, and should be capable of retrieving anything it needs from the Redux state, just by using props (which feature, which id, so on) in its own connect call.
this.props.showDialog(dialog); is a big code smell as well. ;)
I have a component which builds onto the Select component from Ant Design https://ant.design/components/select/
<SomeComponent
onSelect = { this.props.handleSelect }
onDeselect = { this.props.handleDeselect }
selectionList = { valuesList }
value = { values }/>
onSelect triggeres the action this.props.handleSelect
export function handleSelect(value) {
return dispatch => {
dispatch(actionCreator(HANDLE_SELECT, value));
}
}
That actions goes into the reducer
case HANDLE_SELECT: {
const newValues = value_select(state, action);
return {
...state,
find: {
...state.a,
values: newValues
}
}
}
Finally, value_select is called to do all the magic
export const value_select = function(state, action) {
...
const newData = {
XYZ: action.payload
}
return newData
}
This brings me to my question.
Is it possible to send further metadata with the action? Imagine I use the component <SomeComponent.../> several times. I would not know which of the rendered components triggered the action when the onSelect is fired.
If I want to process the information in value_select = function(state, action) {... later, I want to know which component caused the action to process my data properly. I need to set XYZ in value_select() dynamically, depending on which <SomeComponent.../> caused the action. action.payload only gives me what is saved in value in <SomeComponent.../>, nothing more.
Is there a way to send some more information with the onSelect or is that bad practice and I would need an action for each component <SomeComponent.../> anyway?
Absolutely. It's your action and your reducer, you can attach any information you want to it.
The most common approach for structuring an action is the Flux Standard Action approach, which expects your actions to look like {type, payload, meta, error} but it's really up to you what you put into your actions.
For some more ideas, you might want to read through the Structuring Reducers - Reusing Reducer Logic section of the Redux docs.
I have this codepen, where store.subscribe() works and connect() doesn't work. Specifically, the component doesn't get updated with the new props. I suspected state mutation as I thought that connect()'s shallow equality check might be ignoring the change. But, I'm using Immutable.js for the state change in the reducer, and I also did my own ref check in my subscribe method and it is even shallowly different for every update. I feel like something obvious must be missing here...
Component:
class App extends React.Component{
...
}
const mapStateToProps = state => ({
alerts: state.alerts
});
const mapDispatchToProps = dispatch => ({
onAlert: (type, autoHide, message) =>
dispatch({ type: 'SHOW_ALERT', payload: { message, type, autoHide } })
});
const ConnectedApp = connect(mapStateToProps, mapDispatchToProps)(App);
Reducer:
const alertsReducer = (alerts=Immutable.List(), { type, payload }) => {
switch (type){
case 'SHOW_ALERT':
if (!payload.message || R.trim(payload.message).length === 0){
throw new Error('Message cannot be empty.');
}
return alerts.push(payload);
default:
return alerts;
}
};
Store:
const store = createStore(combineReducers({ alerts: alertsReducer }), applyMiddleware(ReduxThunk.default));
Render:
//** THIS DOESN'T WORK
// ReactDOM.render(<Provider store={store}><ConnectedApp /></Provider>, document.getElementById('main'));
//** THIS WORKS
store.subscribe(()=>{
render();
});
const render = () => {
ReactDOM.render(<App {...store.getState()} onAlert={
(type, autoHide, message) => store.dispatch({ type: 'SHOW_ALERT', payload: { message, type, autoHide } })
}/>, document.getElementById('main'));
};
render();
Is this because the top level state object still has the same reference? I tried removing Immutable.js and made the entire state the array with the reducer returning a new array every time. That still didn't work.
Versions:
react-redux#4.4.5
redux#3.5.2
react#15.3.1
if you open the console , there will be the error
addComponentAsRefTo(...): Only a ReactOwner can have refs. You might
be adding a ref to a component that was not created inside a
component's render method, or you have multiple copies of React
loaded
to solve it you should choose between https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-with-addons.min.js
and
https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.js, because React should be added to page once.
after that you need to add Provider from react-redux. Also you need to add key props in your lists.
changed pen http://codepen.io/anon/pen/dXLQLv?editors=0010