How can I generalize these two functions to use in identical divs?
showNotifyText = () => {
const { showNotifyText } = this.state;
this.setState({ showNotifyText: !showNotifyText });
};
showRoutineAvailabilityText = () => {
const { showRoutineAvailabilityText } = this.state;
this.setState({ showRoutineAvailabilityText: !showRoutineAvailabilityText });
};
Using this.state in setState is antipattern because setState is asynchronous. If new state derives from previous one, setState function should be used.
It could be either generalized method:
toggleStateProperty = key => {
this.setState(state => ({ [key]: !state[key] }));
};
Or higher-order function:
toggleStatePropertyFactory = key => function () {
this.setState(state => ({ [key]: !state[key] }));
};
...
toggleShowNotifyText = toggleStatePropertyFactory('showNotifyText');
toggleRoutineAvailabilityText = toggleStatePropertyFactory('showRoutineAvailabilityText');
In case the method is supposed to be passed as a callback, the second option is preferable because it already binds the method to specific key:
<div onclick={this.toggleShowNotifyText}/>
vs
<div onclick={() => this.toggleStateProperty('showNotifyText')}/>
To generalize the method you could try adding the state key in the template as data, then read it inside the event handler:
onClick = (event) => {
const key = event.target.dataset.key;
const value = this.state[key];
this.setState({ [key]: !value });
}
render() {
return (
<div>
<div onClick={this.onClick} data-key="showRoutineAvailabilityText">click</div>
</div>
);
}
Related
I am using react usestate() and I want to update device state(It is an object)
my problem is when ShowRelays component renders for the first time device is an empty object and It does not get updated during first rendering, but for the next renders everything is fine
How can I update device state for the first time rendering?
(sorry for my bad english)
.
function ShowRelays(props) {
const [device, setDevice] = useState({})
let reduxDevices = useSelector(state => state.devicesReducer.devices)
let findDevice = () => {
let myDevice = reduxDevices.find(x => x._id === props.id)
setDevice(prevState => {
return {
...prevState,
...myDevice
}
})
}
useEffect(() => {
if (props.show) {
findDevice()
}
}, [props.show])
return
(
<div>
test
</div>
)
}
myDevice object is like:
{active: true, name: "device1", id: "deviceId"}
You can pass a function to your useState-hook which will calculate your initial value for device.
see lazy init state
function ShowRelays(props) {
let reduxDevices = useSelector(state => state.devicesReducer.devices);
const [device, setDevice] = useState(() => {
return reduxDevices.find(x => x._id === props.id)
});
return <div>test</div>;
}
An other possible solution for your problem without using a separate state could be the following (directly select the right device from your selector function):
function ShowRelays(props) {
const device = useSelector(state => {
return state.devicesReducer.devices.find(x => x._id === props.id);
});
return <div>test</div>;
}
What is the difference between defining this counterHandler like this
counterHandler = () => {
this.setState(() => {
return { times: this.state.times + 1 }
});
}
And this?
counterHandler = () => {
this.setState((prevState) => {
return { times: prevState.times + 1 }
});
}
Does the state from a component always gets passed to setState automatically?
If you only use One data inside state, you dont need to make callback of prevState,
but if your state more than one, you need to callback of prevstate because this will make your other and previous data will not be lost.
for example
const [state, setState] = useState({
loading: false,
data: []
})
const handleLoading = () => {
setState({
loading: true
})
}
const handleData = () => {
setState({
data: [a,b,c] // you will lost your loading = true
})}
const handleData = () => {
setState((prevState) => {
...prevState,
data: [a,b,c] // you still have loading = true
})
}
This is how my Categories react functional component looks like. For easier testing, I split up the handleClick and the react component itself - but that shouldn't be an issue for this question.
How do I pass the component string value from the map() to the handleClick()? handleClick already passes some operator parameter, which gets me struggling with this simple issue...
export const useCategories = () => {
const handleClick = (operator) => {
updateCategory({
variables: {
id: '123',
operator,
category // <-- this value is missing
}
})
}
return {
icon: {
onClick: handleClick('$pull') // <-- Here I add some operator value
}
}
}
export const Categories = () => {
const { icon } = useCategories()
return (
<div>
{categories.map((category) => <Icon onClick={icon.onClick} />)} {/* <-- how to pass category value to handleClick...? */}
</div>
)
}
In order to "add" a variable, you can use currying.
Hence, return a function that receives the new argument and use it internally:
export const useCategories = () => {
return (category) => {
const handleClick = (operator) => {
updateCategory({
variables: {
id: '123',
operator,
category // now this is the argument of the wrapper function
}
})
};
return () => handleClick('$pull');
};
}
And you can use it like this:
export const Categories = () => {
const getOnClick = useCategories();
return (
<div>
{categories.map((category) => {
const onClick = getOnClick(category);
return <Icon onClick={onClick} />;
})}
</div>
)
}
So, I define 2 states with the same array, it's listRasioUom and showListRasioUom. but when I setstate to listRasioUom state, the showListRasioUom state also changed with the same value as listRasioUom. any help? this is my code
handleChange = (e) => {
if (["rasio", "uom"].includes(e.target.name)) {
let listRasioUom = this.state.listRasioUom
if(listRasioUom.some(el => el.uom === e.target.value))
{
alert('Duplicate uom')
}
else
listRasioUom[e.target.dataset.id][e.target.name] = e.target.value;
this.setState({ listRasioUom })
}
}
}
showListRasioUom state is used for fetching the data on my datatables, while listRasioUom state is used to modified the state, so i only want to change listrasiouom state, but when i use setstate on listRasioUom state, the showListRasioUom also change.
<MDBDataTable
info={false}
paging={false}
searching={false}
striped
bordered
small
data={{columns: this.state.columns, rows: this.state.showListRasioUom}}
noBottomColumns
/>
Object and arrayss in javascript are assigned by reference and if you mutate the original array the other array which also holds the same reference is also updated
You should update your data in an immutable manner
handleChange = (e) => {
if (["rasio", "uom"].includes(e.target.name)) {
let listRasioUom = this.state.listRasioUom
if(listRasioUom.some(el => el.uom === e.target.value))
{
alert('Duplicate uom')
}
else {
const id = e.target.dataset.id;
const {name,value} = e.target;
this.setState(prev => ({
listRasioUom: prev.listRasioUom.map((item, i) => {
if(i == id) return {...item, [name]: value};
})
return item;
}))
}
}
}
I am actually studying about enzyme integrated with jest to test stuff on applications.
Other things I am using is react and moment.
My question is probably very noob, but I just want to know why there is two argument stuff on this line:
wrapper.find('SingleDatePicker').prop('onFocusChange')({ focused });
is the 'formula' of this, this: objectExample.methodExample('argument1')('argument2'); ?
I will provide down below the whole code divided in two: the test code and the code tested.
Code tested:
export default class ExpenseForm extends React.Component {
constructor(props) {
super(props);
this.state = {
calendarFocused: false,
};
}
onFocusChange = ({ focused }) => {
this.setState(() => ({ calendarFocused: focused }));
};
render() {
return (
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit={this.onSubmit}>
<SingleDatePicker
date={this.state.createdAt}
onDateChange={this.onDateChange}
focused={this.state.calendarFocused}
onFocusChange={this.onFocusChange}
numberOfMonths={1}
isOutsideRange={() => false}
/>
</form>
</div>
)
}
}
test code:
test('should set calendar focus on change', () => {
const focused = false;
const wrapper = shallow(<ExpenseForm />);
wrapper.find('SingleDatePicker').prop('onFocusChange')({ focused });
expect(wrapper.state('calendarFocused')).toBe(focused);
});
So basically, I can understand all the functionality of the code itself and everything.
I just do not understand this second argument ({ focused }).
I don't even know if this is called 'argument'.
I tweak a bit and took this thing out of the code and it worked the same way.
I am kind of confused if this is vanilla javascript or something of one of these libraries I am using.
...
What I expect:
answer for what is this thing and why use it like this.
some source of name or something I can browse about it and learn that.
wrapper.find('SingleDatePicker').prop('onFocusChange') returns a function.
This function is actually:
onFocusChange = ({ focused }) => {
this.setState(() => ({ calendarFocused: focused }));
};
i.e. the one that you created in your component.
Now to execute this function you can do:
wrapper.find('SingleDatePicker').prop('onFocusChange')({ focused });
or
const onFocusChangeFn = wrapper.find('SingleDatePicker').prop('onFocusChange');
onFocusChangeFn({ focused });
In general if you have something like:
const myObject = {
getFunction: function(y) {
return function(x) {
console.log(y, x);
}
}
}
Then you can do:
myObject.getFunction(10)(20) // prints (10,20)
where myObject.getfunction(10) will return the inner function that you can call with any argument like 20 in the example.
Returning a function is useful for various purposes like currying and partial functions
so i understood that I can call the inner function as you mentioned on the last example. and that this example:
const onFocusChangeFn = wrapper.find('SingleDatePicker').prop('onFocusChange');
onFocusChangeFn({ focused });
is as the jest documentation suggest
const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();
and that is pretty much clear on my mind.
problem is that when i get a piece of code like this:
test('should handle date changes', () => {
const startDate = moment(0).add(2, 'days');
const endDate = moment(0).add(3, 'days');
wrapper.find('DateRangePicker').prop('onDatesChange')({ startDate, endDate });
expect(setStartDate).toHaveBeenLastCalledWith(startDate);
expect(setEndDate).toHaveBeenLastCalledWith(endDate);
});
would this proceed like this?:
(...)
const wrapperFn = wrapper.find('DateRangePicker').prop('onDatesChange');
wrapperFn({ startDate, endDate });
I was proceeding on the studies and i found another thing that is about the same subject but a bit different.
test('should sort by date', () => {
const value = 'date';
wrapper.find('select').simulate('change', { target: { value } });
expect(sortByDate).toHaveBeenCalled();
});
he defined the target value for the 'e' event inside the simulate() arguments. this could be defined separately too ? Like this:
test('should sort by date', () => {
const value = 'date';
const wrapperFn = wrapper.find('select').simulate('change')
wrapperFn({ target: { value } });
expect(sortByDate).toHaveBeenCalled();
});
or they are completely different things?
below, i will provide the full code I am testing
import React from 'react';
import { connect } from 'react-redux';
import { DateRangePicker } from 'react-dates';
import { setTextFilter, sortByDate, sortByAmount, setStartDate, setEndDate } from '../actions/filters';
export class ExpenseListFilters extends React.Component {
state = {
calendarFocused: null
};
onDatesChange = ({ startDate, endDate }) => {
this.props.setStartDate(startDate);
this.props.setEndDate(endDate);
};
onFocusChange = (calendarFocused) => {
this.setState(() => ({ calendarFocused }));
}
onTextChange = (e) => {
this.props.setTextFilter(e.target.value);
};
onSortChange = (e) => {
if (e.target.value === 'date') {
this.props.sortByDate();
} else if (e.target.value === 'amount') {
this.props.sortByAmount();
}
};
render() {
return (
<div>
<input
type="text"
value={this.props.filters.text}
onChange={this.onTextChange}
/>
<select
value={this.props.filters.sortBy}
onChange={this.onSortChange}
>
<option value="date">Date</option>
<option value="amount">Amount</option>
</select>
<DateRangePicker
startDate={this.props.filters.startDate}
endDate={this.props.filters.endDate}
onDatesChange={this.onDatesChange}
focusedInput={this.state.calendarFocused}
onFocusChange={this.onFocusChange}
showClearDates={true}
numberOfMonths={1}
isOutsideRange={() => false}
/>
</div>
);
}
};
const mapStateToProps = (state) => ({
filters: state.filters
});
const mapDispatchToProps = (dispatch) => ({
setTextFilter: (text) => dispatch(setTextFilter(text)),
sortByDate: () => dispatch(sortByDate()),
sortByAmount: () => dispatch(sortByAmount()),
setStartDate: (startDate) => dispatch(setStartDate(startDate)),
setEndDate: (endDate) => dispatch(setEndDate(endDate))
});
export default connect(mapStateToProps, mapDispatchToProps)(ExpenseListFilters);
fixtures:
import moment from 'moment';
const filters = {
text: '',
sortBy: 'date',
startDate: undefined,
endDate: undefined
};
const altFilters = {
text: 'bills',
sortBy: 'amount',
startDate: moment(0),
endDate: moment(0).add(3, 'days')
};
export { filters, altFilters };
thanks for helping.