initial values from state of material-ui dropdown in redux-form - reactjs

I have a material-ui dropdown menu inside a redux-form, and I want to initialize its value.
I am getting both the value that I want [exercise.exercise_type_id and exercise.exercise_type_name] and the list of available options [types, array of objects with id and name properties, among others] by dispatching two actions:
componentWillMount(){
this.props.actions.exercise.fetchInfo(this.props.params.exerciseId);
this.props.actions.exercise.fetchAllTypes();
};
Ideally I would like to have somewhere something like:
_.map(this.props.types, (type) =>{
if (type.id == this.props.exercise.exercise_type_id){
this.setState({
dropDownValue: type.name
});
}
});
but only for the initial state, since we want to handle changes with handleDropDownChange.
[Of course I would appreciate something like
state = {
dropDownValue: {this.props.exercise.exercise_type_name}
};
but I know this is an anti-pattern. But anyway it doesn't work, props are still empty.]
My dropdown is like this:
<DropDownMenu {...exercise_type_name} //does nothing
value={dropDownValue}
onChange={onDropDownChange}>
{_.map(types, (type) => {
return <MenuItem key={type.id} value={type.name} primaryText={type.name}/>;
})}
More code:
//...
state = {
dropDownValue: ''
};
//...
handleDropDownChange = (event, index, value) => {
this.setState({
dropDownValue: value
});
};
//...
function mapStateToProps(state){
return {
exercise: getExerciseInfo(state),
initialValues: getExerciseInfo(state),
request: getExRequest(state),
types: getExTypes(state)
};
}
//....
export default reduxForm({
form: 'EditExerciseForm',
fields: ['exercise_type_name', 'max_errors_percentage', 'min_speed']
}, mapStateToProps, mapDispatchToProps)(ExerciseEdit);

Ah, turned out simpler than what I expected.
This fixed my issue:
In the DropDownMenu component its value should be like:
value={dropDownValue || exercise_type_name.value}

Related

Dropdown select component state one step behind

I have no clue why the selected dropdown value is one step behind in the URL search params string. My url is like this: http://localhost/?dropdownsel=. Below is my code:
//App.js
//update params value
function setParams({ dropdownsel }) {
const searchParams = new URLSearchParams();
searchParams.set("dropdownsel", dropdownsel);
return searchParams.toString();
}
class App extends Component {
state = {
dropdownsel: ""
};
//update url params
updateURL = () => {
const url = setParams({
dropdownsel: this.state.dropdownsel
});
//do not forget the "?" !
this.props.history.push(`?${url}`);
};
onDropdownChange = dropdownsel => {
this.setState({ dropdwonsel: dropdownsel });
this.updateURL();
};
render() {
return (
<Dropdownsel
onChange={this.onDropdownselChange}
value={this.state.dropdownsel}
/>
);
}
}
Below is dropdownsel component code:
//Dropdownsel.js
const attrData = [{ id: 1, value: AA }, { id: 2, value: BB }];
class Dropdownsel extends Component {
onDropdownChange = event => {
this.props.onChange(event.target.value);
};
render() {
return (
<div>
<select value={this.props.value} onChange={this.onDropdownChange}>
<option value="">Select</option>
{attrData.map(item => (
<option key={item.id} value={item.value}>
{" "}
{item.name}
</option>
))}
</select>
</div>
);
}
}
export default Dropdownsel;
Thanks for formatting my code. I don't know how to do it every time when I post question. I figured it out myself. I need to make a call back function for updateURL() because the setState() is not executed immediately. so my code should be revised like below:
onDropdownChange = (dropdownsel) => {
this.setState({ dropdwonsel:dropdownsel }, ()=>{this.updateURL();
});
};
The problem occurs because this.setState is asynchronous (like a Promise or setTimeout are)
So there are two workarounds for your specific case.
Workaround #1 - using a callback
Use the callback option of this.setState.
When you take a look at the declaration of setState, it accepts an "optional" callback method, which is called when the state has been updated.
setState(updater[, callback])
What it means is that, within the callback, you have an access to the updated state, which was called asynchronously behind the scene by React.
So if you call this.updateURL() within the callback, this.state.dropdownsel value will be the one you are expecting.
Instead of,
this.setState({ dropdwonsel: dropdownsel });
this.updateURL();
Call this.updateURL in the callback.
// Note: '{ dropdwonsel }' is equal to '{ dropdwonsel: dropdwonsel }'
// If your value is same as the state, you can simplify this way
this.setState({ dropdwonsel }, () => {
this.updateURL()
});
Workaround #2 - passing the new value directly
You can also pass the new value directly as an argument of this.updateURL() (which might make testing easier as it makes you method depend on a value, which you can fake).
this.setState({ dropdwonsel });
this.updateURL(dropdownsel );
Now your this.updateURL doesn't depend on the this.state.dropdownsel, thus you can can use the arg to push the history.
//update url params
updateURL = dropdownsel => {
const url = setParams({
dropdownsel
});
//do not forget the "?" !
this.props.history.push(`?${url}`);
};

Is there a way to reuse a reducer logic in redux?

I am using react/redux to build my app. There are multiple buttons and the app needs to keep track of states of whether these buttons were clicked. These buttons are all using the same reducer logic, which is to alternate the state from true to false whenever the button is clicked. The only differentiator is the name of the state. Is there a way to reduce my code by defining a "global" reducer that can be applied to multiple states? Please refer to this image for an example
Thank you!
This is how I would do it. Instead of dispatching unique action for every button type like MENU_BTN, SEARCH_BTN, etc.. I would dispatch { type: 'TOGGLE_BUTTON', payload: 'menu' } and in reducer
case TOGGLE_BUTTON:
return {
[payload]: !state[payload]
...state
}
You can then toggle buttons like this
const toggleButton = key => ({
type: 'TOGGLE_BUTTON',
payload: key
})
dispatch(toggleButton('menu'))
dispatch(toggleButton('search'))
That way you can keep track of as many buttons as you want. Your state will then look something like this
...
buttons: {
menu: true,
search: false
}
...
and you can easily write selector for every button
// Component displaying state of menu button //
let MyComponent = ({ menuButtonState }) => (
<div>
Menu button is {menuButtonState ? 'on' : 'off'}
</div>
)
// Helper function for creating selectors //
const createGetIsButtonToggledSelector = key => state => !!state.buttons[key]
// Selector getting state of a menu button from store //
const getIsMenuButtonToggled = createGetIsButtonToggledSelector('menu')
// Connecting MyComponent to menu button state //
MyComponent = connect(state => ({
menuButtonState: getIsMenuButtonToggled(state)
})(MyComponent)
You can create a generic higher-order reducer that accepts both a given reducer function and a name or identifier.
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const { name } = action
const isInitializationCall = state === undefined
if (name !== reducerName && !isInitializationCall) return state
return reducerFunction(state, action)
}
}
const rootReducer = combineReducers({
counterA: createNamedWrapperReducer(counter, 'A'),
counterB: createNamedWrapperReducer(counter, 'B'),
counterC: createNamedWrapperReducer(counter, 'C')
})
The above createdNamedWrapperReducer() method should work out of the box.
See Redux's recipes for Reusing Reducer Logic for a more detailed explanation or more examples.

Filtering an icon from an array of icon strings for re-render

I'm trying to take an e.target.value which is an icon and filter it out from an array in state, and re-render the new state minus the matching icons. I can't seem to stringify it to make a match. I tried pushing to an array and toString(). CodeSandbox
✈ ["✈", "♘", "✈", "♫", "♫", "☆", "♘", "☆"]
Here is the code snippet (Parent)
removeMatches(icon) {
const item = icon;
const iconsArray = this.props.cardTypes;
const newIconsArray =iconsArray.filter(function(item) {
item !== icon
})
this.setState({ cardTypes: newIconsArray });
}
This is a function in the parent component Cards, when the child component is clicked I pass a value into an onClick. Below is a click handler in the Child component
handleVis(e) {
const item = e.target.value
this.props.removeMatches(item)
}
First of all, there's nothing really different about filtering an "icon" string array from any other strings. Your example works like this:
const icons = ["✈", "♘", "✈", "♫", "♫", "☆", "♘", "☆"]
const icon = "✈";
const filteredIcons = icons.filter(i => i !== icon);
filteredIcons // ["♘", "♫", "♫", "☆", "♘", "☆"]
Your CodeSandbox example has some other issues, though:
Your Card.js component invokes this.props.removeMatches([item]) but the removeMatches function treats the argument like a single item, not an array.
Your Cards.js removeMatches() function filters this.props.cardTypes (with the previously mentioned error about treating the argument as a single item not an array) but does not assign the result to anything. Array.filter() returns a new array, it does not modify the original array.
Your Cards.js is rendering <Card> components from props.cardTypes, this means that Cards.js is only rendering the cards from the props it is given, so it cannot filter that prop from inside the component. You have a few options:
Pass the removeMatches higher up to where the cards are stored in state, in Game.js as this.state.currentCards, and filter it in Game.js which will pass the filtered currentCards back down to Cards.js.
// Game.js
removeMatches = (items) => {
this.setState(prevState => ({
currentCards: prevState.currentCards.filter(card => items.indexOf(card) == -1)
}));
}
// ...
<Cards cardTypes={this.state.currentCards} removeMatches={this.removeMatches} />
// Cards.js
<Card removeMatches={this.props.removeMatches}/>
// Card.js -- same as it is now
Move Cards.js props.cardTypes into state (ex state.currentCards) within Cards.js, then you can filter it out in Cards.js and render from state.currentCards instead of props.cardTypes. To do this you would also need to hook into componentWillReceiveProps() to make sure that when the currentCards are passed in as prop.cardTypes from Game.js that you update state.currentCards in Cards.js. That kind of keeping state in sync with props can get messy and hard to follow, so option 1 is probably better.
// Cards.js
state = { currentCards: [] }
componentWillReceiveProps(nextProps) {
if (this.props.cardTypes !== nextProps.cardTypes) {
this.setState({ currentCards: nextProps.cardTypes });
}
}
removeMatches = (items) => {
this.setState(prevState => ({
currentCards: prevState.currentCards.filter(card => items.indexOf(card) == -1)
}));
}
render() {
return (
<div>
{ this.state.currentCards.map(card => {
// return rendered card
}) }
</div>
);
}
Store all the removed cards in state in Cards.js and filter cardTypes against removedCards before you render them (you will also need to reset removedCards from componentWillReceiveProps whenever the current cards are changed):
// Cards.js
state = { removedCards: [] }
componentWillReceiveProps(nextProps) {
if (this.props.cardTypes !== nextProps.cardTypes) {
this.setState({ removedCards: [] });
}
}
removeMatches = (items) => {
this.setState(prevState => ({
removedCards: [...prevState.removedCards, ...items]
}));
}
render() {
const remainingCards = this.props.cardTypes.filter(card => {
return this.state.removedCards.indexOf(card) < 0;
});
return (
<div>
{ remainingCards.map(card => {
// return rendered card
})}
</div>
);
}
As you can see, keeping state in one place in Game.js is probably your cleanest solution.
You can see all 3 examples in this forked CodeSandbox (the second 2 solutions are commented out): https://codesandbox.io/s/6yo42623p3

React/Redux controlled input with validation

Lets imagine we want an input for a "product" (stored in redux) price value.
I'm struggle to come up with the best way to handle input constraints. For simplicity, lets just focus on the constraint that product.price cannot be empty.
It seems like the 2 options are:
1: Controlled
Implementation: The input value is bound to product.price. On change dispatches the changePrice() action.
The main issue here is that if we want to prevent an empty price from entering the product store, we essentially block the user from clearing the input field. This isn't ideal as it makes it very hard to change the first digit of the number (you have to select it and replace it)!
2: Using defaultValue
Implementation: We set the price initially using input defaultValue, that allows us to control when we want to actually dispatch changePrice() actions and we can do validation handling in the onChange handler.
This works well, unless the product.price is ever updated from somewhere other than the input change event (for example, an applyDiscount action). Since defaultValue doesn't cause rerenders, the product.price and the input are now out of sync!
So what am I missing?
There must be a simple & elegant solution to this problem but I just can't seem to find it!
What I have done in the past is to use redux-thunk and joi to solve input constraints/validation using controlled inputs.
In general I like to have one update action that will handle all the field updating. So for example if you have two inputs for a form, it would looks something like this:
render() {
const { product, updateProduct } = this.props;
return (
<div>
<input
value={product.name}
onChange={() => updateProduct({...product, name: e.target.value})}
/>
<input
value={product.price}
onChange={() => updateProduct({...product, price: e.target.value})}
/>
</div>
)
}
Having one function/action here simplifies my forms a great deal. The updateProject action would then be a thunk action that handles side effects. Here is our Joi Schema(based off your one requirement) and updateProduct Action mentioned above. As a side note, I also tend to just let the user make the mistake. So if they don't enter anything for price I would just make the submit button inactive or something, but still store away null/empty string in the redux store.
const projectSchema = Joi.object().keys({
name: Joi.number().string(),
price: Joi.integer().required(), // price is a required integer. so null, "", and undefined would throw an error.
});
const updateProduct = (product) => {
return (dispatch, getState) {
Joi.validate(product, productSchema, {}, (err, product) => {
if (err) {
// flip/dispatch some view state related flag and pass error message to view and disable form submission;
}
});
dispatch(update(product)); // go ahead and let the user make the mistake, but disable submission
}
}
I stopped using uncontrolled inputs, simply because I like to capture the entire state of an application. I have very little local component state in my projects. Keep in mind this is sudo code and probably won't work if directly copy pasted. Hope it helps.
So I think I've figure out a decent solution. Basically I needed to:
Create separate component that can control the input with local state.
Pass an onChange handler into the props that I can use to dispatch my changePrice action conditionally
Use componentWillReceiveProps to keep the local value state in sync with the redux store
Code (simplified and in typescript):
interface INumberInputProps {
value: number;
onChange: (val: number) => void;
}
interface INumberInputState {
value: number;
}
export class NumberInput extends React.Component<INumberInputProps, INumberInputState> {
constructor(props) {
super(props);
this.state = {value: props.value};
}
public handleChange = (value: number) => {
this.setState({value});
this.props.onChange(value);
}
//keeps local state in sync with redux store
public componentWillReceiveProps(props: INumberInputProps){
if (props.value !== this.state.value) {
this.setState({value: props.value});
}
}
public render() {
return <input value={this.state.value} onChange={this.handleChange} />
}
}
In my Product Component:
...
//conditionally dispatch action if meets valadations
public handlePriceChange = (price: number) => {
if (price < this.props.product.standardPrice &&
price > this.props.product.preferredPrice &&
!isNaN(price) &&
lineItem.price !== price){
this.props.dispatch(updatePrice(this.props.product, price));
}
}
public render() {
return <NumberInput value={this.props.product.price} onChange={this.handlePriceChange} />
}
...
What i would do in this case is to validate the input onBlur instead of onChange.
For example consider these validations in the flowing snippet:
The input can't be empty.
The input should not contain "foo".
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myVal: '',
error: ''
}
}
setError = error => {
this.setState({ error });
}
onChange = ({ target: { value } }) => {
this.setState({ myVal: value })
}
validateInput = ({ target: { value } }) => {
let nextError = '';
if (!value.trim() || value.length < 1) {
nextError = ("Input cannot be empty!")
} else if (~value.indexOf("foo")) {
nextError = ('foo is not alowed!');
}
this.setError(nextError);
}
render() {
const { myVal, error } = this.state;
return (
<div>
<input value={myVal} onChange={this.onChange} onBlur={this.validateInput} />
{error && <div>{error}</div>}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Edit
As a followup to your comments.
To make this solution more generic, i would pass the component a predicate function as a prop, only when the function will return a valid result i would call the onChange that passed from the parent or whatever method you pass that updating the store.
This way you can reuse this pattern in other components and places on your app (or even other projects).

Using checkbox with Redux

I'm having problems with a checkbox in my code in two areas, The first one, in my reducer, I want to see if the current state of the checkbox is "true" or "false" but I keep getting syntax errors on the if.
const initialState = {
viewCheckbox: false
}
export default (state = initialState, action) => {
switch (action.type){
case 'VIEW_CHECKBOX':
return {
...state
if (viewCheckbox == false) {
viewCheckbox: true
} else {
viewCheckbox: false
}
}
default:
return: state
}
}
My second problem is with the mapDispatchToProps, I'm using a table to create multiple checkboxes and I want to be able to differentiate each one of them by ID, and when I do it like this, it checks every checkbox on the table.
const mapDispatchToProps = (dispatch) => ({
handleViewCheckbox: id => ev => {
dispatch(viewCheckboxSubmit(id, ev.target.checked))
}
})
And when I create the checkbox I do it like this:
<FormControlLabel
control={
<Checkbox
checked={checkedView}
onChange={handleViewCheckbox(n.id,checkedView)}
/>
}
label='See'
/>
You can't do an if inside an object like that. You can use the spread operator as you have done, add solo variables in which the key matches the variable you want to use, or you can add keys directly. try something like this:
case 'VIEW_CHECKBOX':
return {
...state,
viewCheckbox: !viewCheckbox
}
They're all going to toggle on and off because you're using the same bit of state to manage whether or not it is checked. If you want to go about it this way, you could try storing whether each one is checked in an object and just keep updating that.
{
checkboxid1: false,
checkboxid2: false,
checkboxid3: true // checked
}
then the checked prop for your checkbox component would look like..
checked={this.props.isChecked[n.id]}
assuming you pulled that object out in mapStateToProps and called it isChecked

Resources