I'm trying to come up with a nicer way to have a default value for a vuex action payload property, right now I'm using an if-else to check if the passed payload object has a delay property, if it doesn't I set the value to default and you can imagine the rest.
Is there a nicer way to do this in fewer lines? I'm sure there must be.
Here is my action:
showModal ( {commit}, modalPayload ) {
let delay;
if(modalPayload.delay == undefined){
delay = 3000;
}
else{
delay = modalPayload.delay
}
commit('SHOW_MODAL', modalPayload);
setTimeout(function(){
commit('HIDE_MODAL');
}, delay);
},
Thanks in advance.
You could set a default value using destructuring assignment:
showModal ({ commit }, modalPayload) {
const { delay = 3000 } = modalPayload
commit('SHOW_MODAL', modalPayload);
setTimeout(() => commit('HIDE_MODAL'), delay);
}
Also, if you don't need passing delay to the commit, you can destructure the second function parameter:
showModal ({ commit }, { delay = 3000, ...modalPayload }) {
commit('SHOW_MODAL', modalPayload);
setTimeout(() => commit('HIDE_MODAL'), delay);
}
Related
I have been trying to figure something out in my React cryptocurrency project. So I have a function that basically polls for a response from the api every 6 seconds. It is supposed to start polling the moment the component renders, so I have it in a useEffect. It has default parameters being passed in for the polling to happen right after render. The polling works in terms that it polls and get the response based on the default values. It also takes in the user inputs and returns a response based on it, however, after the next poll, the arguments being passed to the api for polling are back to their defaults. In other words, the arguments being passed into the polling function on the next polls are all back to the default values I passed in, and not a continuation of the current state of the arguments.
Here is where I define pair, which lives above the useEffect:
const baseAsset = transactionType === TRANSACTION_TYPES.BUY ? selectedCurrencyState.selectedToCurrency : selectedCurrencyState.selectedFromCurrency;
const quoteAsset = transactionType === TRANSACTION_TYPES.SELL ? selectedCurrencyState.selectedToCurrency : selectedCurrencyState.selectedFromCurrency;
const pair = baseAsset && quoteAsset ? `${baseAsset}/${quoteAsset}` : '';
Here is the function that gets polled:
const handleInitPoll = useCallback((baseAndQuote, side, value) => {
setIsLoading(true);
getSwapPrice(baseAndQuote, side, value || 0)
.then((res) => {
if (res.error) {
setErrorMessage(res.error);
} else if (res.price) {
setIsLoading(false);
setSwapPriceInfo(res);
}
});
}, [pair, transactionType, fromCurrencyAmount]);
And here is the useEffect that polls it:
useEffect(() => {
if (isLoggedIn) {
getSwapPairs()
.then((res) => {
setSwapInfo(res.markets);
// Set state of selected currencies on render
if (transactionType === TRANSACTION_TYPES.BUY) {
setSelectedCurrencyState({
...selectedCurrencyState,
selectedToCurrency: (quoteAsset !== symbol) ? symbol : ''
});
}
});
if (symbol === selectedCurrencyState.selectedToCurrency) {
actions.updateChartQuote(selectedCurrencyState.selectedFromCurrency);
}
if (pair && transactionType && symbol === baseAsset) {
handleInitPoll(pair, transactionType, fromCurrencyAmount);
}
const timer = setInterval(handleInitPoll, 6000, pair, transactionType, fromCurrencyAmount);
return () => {
clearInterval(timer);
};
}
setSelectedCurrencyState({ ...selectedCurrencyState, selectedFromCurrency: 'SHIB', selectedToCurrency: 'DASH' });
}, [pair, transactionType, fromCurrencyAmount, symbol]);
Here is the debouncing method:
const debounceOnChange = useCallback(debounce(handleInitPoll, 500, pair, transactionType, fromCurrencyAmount), []);
And here is where the user is entering the input to debounce the api call when a user input is detected onChange:
const handleAssetAmount = (e) => {
const { value } = e.target;
const formattedAmount = handleAssetAmountFormat(value);
setFromCurrencyAmount(formattedAmount);
validateInputAmount(formattedAmount);
debounceOnChange(pair, transactionType, formattedAmount);
};
You should put pair in a useState hook. otherwise, everytime when this component is redenrering. the following part will be executed:
const pair = baseAsset && quoteAsset ? `${baseAsset}/${quoteAsset}` : '';
That's why you got a initial value again.
useState can help you to preserve the value for the whole component lifecircle unless you call set***().
I found this lib https://react-hooks.org/docs/useEffectOnceWhen but I'm confused. Why does it even existed and in what purpose it's used? Isn't I can achieve the same thing using useEffect?
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 3000); // Countdown for 3 sec
}, [])
The hook is useEffectOnceWhen, not just useEffectOnce. Per the source you linked:
Runs a callback effect atmost one time when a condition becomes true
Here's the source code of that hook:
/**
* useEffectOnceWhen hook
*
* It fires a callback once when a condition is true or become true.
* Fires the callback at most one time.
*
* #param callback The callback to fire
* #param when The condition which needs to be true
*/
function useEffectOnceWhen(callback: () => void, when: boolean = true): void {
const hasRunOnceRef = useRef(false);
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
});
useEffect(() => {
if (when && !hasRunOnceRef.current) {
callbackRef.current();
hasRunOnceRef.current = true;
}
}, [when]);
}
As you can see, it does something more advanced than just being called once at the beginning of the hook: it gets called once the condition passed in becomes true for the first time.
You can do the same thing with useEffect:
"If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument"
Docs
You can also supply a list of dependencies that trigger the effect running again if they change.
useEffect( () => { doSomething() } , [ifThisChangesRunFuncAgain, orThis, ...] )
i am making a game but i am having a problem using setState hook on react, it isnt updating my state, my state properties are
const [state,setState] =useState({
score:0,
holes:9,
initGame:false,
lastHole:-1,
minPeepTime: 200,
maxPeepTime: 1000})
i also have 2 useEffect hook just for testing
useEffect(()=>{
console.log("init game");
console.log(state.initGame);
console.log("--------------");
},[state.initGame])
useEffect(()=>{
console.log("last hole");
console.log(state.lastHole);
console.log("##############");
},[state.lastHole])
and i have a function to start the game
const StartGame=()=>{
console.log("here")
setState({...state,initGame: true,score:0,lastHole: 15})
peep();
setTimeout(() => {
console.log("Game Over")
setState({...state,lastHole:-1,initGame: false})
}, 10000)
}
"here" is actually being printed, and when time is over "Game Over" is being printed.
peep() function update the property lastHole and it call useEffect correctly, but property initGame is never being updated by setState and that my actual problem, useEffect for that property is never call (just the first time when my page is loaded, but not on StartGame function) and its value never change
UPD:
this is the peep function.
const randomTime = (min, max) => {
return Math.round(Math.random() * (max - min) + min);
};
const randomHole = () => {
const idx = Math.floor(Math.random() * state.holes);
if (idx === state.lastHole){
return randomHole();
}
setState({...state,lastHole: idx});
}
const peep = () => {
randomHole();
let time = randomTime(state.minPeepTime,state.maxPeepTime)
if(state.initGame){
setTimeout(() => {
peep()
}, time)
}
}
and this is the output of the code
To properly update state in this scenario use:
setState((initialState) => ({
...initialState,
initGame: true,
score: 0,
lastsHole: 15
}))
The setTimeout part becomes
setState((initialState) => ({
...initialState,
lastHole: -1,
initGame: false
}))
I don't know what peek does or the importance of the setTimeout though;
The problem here is with the setState. setState doesn't immediately change the state as you expect.
setState({
...state,
initGame: true,
score: 0,
lastHole: 15,
});
peep() // value of initGame is false
Unexpectedly, the value of initGame will still be false. Change all the setstates like given below.
setState((state) => ({
...state,
initGame: true,
score: 0,
lastHole: 15,
}));
This will successfully set initGame to true before calling peep()
The final output will be as shown below.
i saw several topics talking about this common mistake, but i didn't find nothing related to the use of recompose.
Context
I've this withStateHandler
withStateHandlers(
{
bookingValidation: false,
},
{
setBookingValidation: ({ bookingValidation }) => () => ({
bookingValidation: !bookingValidation,
}),
},
),
and i've this lifeCycle
lifecycle({
componentDidMount() {
const { getFamily } = this.props;
getFamily();
},
componentWillReceiveProps(prevProps) {
const { formValues } = prevProps;
const { setBookingValidation } = this.props;
const {
locationId,
specialityId,
serviceId,
cancellationDetails,
personId,
date,
} = formValues;
const allFormValuesSelected = !!(
locationId &&
specialityId &&
serviceId &&
cancellationDetails &&
personId &&
date
);
if (allFormValuesSelected) setBookingValidation();
},
}),
it's a simple validation, when i've all the selectedValues, the state of bookingValidation will change on true and you will be able to click on a button.
Problem
When you enter in this if if (allFormValuesSelected) setBookingValidation(); the maximum update depth exceeded because of the function setBookingValidation()
Question
How can i avoid this behavior ?
There is a way to maintain this function ?
It happens because setBookingValidation() changes property value and calls componentWillReceiveProps. And you got an infinite loop of calls.
If you want to perform form validation, you should move that functionality into a separate withHandlers() method and call onChange event.
I am trying to test a React component which uses one of the overloads for setState, but am unsure how to assert the call correctly. An example component would be:
class CounterComponent extends React.Component {
updateCounter() {
this.setState((state) => {
return {
counterValue: state.counterValue + 1
};
});
}
}
The assumption here is that this method will be called asyncronously, so cannot rely on the current state, outwith the call to setState (as it may change before setState executes). Can anyone suggest how you would assert this call? The following test fails as it is simply comparing the function names.
it("Should call setState with the expected parameters", () => {
const component = new CounterComponent();
component.setState = jest.fn(() => {});
component.state = { counterValue: 10 };
component.updateCounter();
const anonymous = (state) => {
return {
counterValue: state.counterValue + 1
};
};
//expect(component.setState).toHaveBeenCalledWith({ counterValue: 11 });
expect(component.setState).toHaveBeenCalledWith(anonymous);
});
Edit: Given yohai's response below, i will add some further context as I feel i may have over simplified the problem however i do not want to re-write the entire question for clarity.
In my actual component, the state value being edited is not a simple number, it is an array of objects with the structure:
{ isSaving: false, hasError: false, errorMessage: ''}
and a few other properties. When the user clicks save, an async action is fired for each item in the array, and then the corresponding entry is updated when that action returns or is rejected. As an example, the save method would look like this:
onSave() {
const { myItems } = this.state;
myItems.forEach(item => {
api.DoStuff(item)
.then(response => this.handleSuccess(response, item))
.catch(error => this.handleError(error, item));
});
}
The handle success and error methods just update the object and call replaceItem:
handleSuccess(response, item) {
const updated = Object.assign({}, item, { hasSaved: true });
this.replaceItem(updated);
}
handleError(error, item) {
const updated = Object.assign({}, item, { hasError: true });
this.replaceItem(updated);
}
And replaceItem then replaces the item in the array:
replaceItem(updatedItem) {
this.setState((state) => {
const { myItems } = state;
const working = [...myItems];
const itemToReplace = working.find(x => x.id == updatedItem.id);
if (itemToReplace) {
working.splice(working.indexOf(itemToReplace), 1, updatedItem);
};
return {
myItems: working
};
});
}
replaceItem is the method I am trying to test, and am trying to validate that it calls setState with the correct overload and a function which correctly updated the state.
My answer below details how I have solved this for myself,but comments and answers are welcome =)
#Vallerii: Testing the resulting state does seem a simpler way, however if i do, there is no way for the test to know that the method is not doing this:
replaceItem(updatedItem) {
const { myItems } = state;
const working = [...myItems];
const itemToReplace = working.find(x => x.id == updatedItem.id);
if (itemToReplace) {
working.splice(working.indexOf(itemToReplace), 1, updatedItem);
};
this.setState({ myItems: working });
}
When replaceItem does not use the correct overload for setState, this code fails when called repeatedly as (I assume) react is batching updates and the state this version uses is stale.
I think you should test something a little bit different and it will look somthing like this (I'm using enzyme):
import React from 'react'
import { mount } from 'enzyme'
import CounterComponent from './CounterComponent'
it("Should increase state by one", () => {
const component = mount(<CounterComponent />)
const counter = 10;
component.setState({ counter });
component.instance().updateCounter();
expect(component.state().counter).toEqual(counter + 1);
});
I have come up with a solution to this after some further thought. I am not sure it is the best solution, but given that the updateCounter method in the example above passes a function into the setState call, I can simply get a reference to that function, execute it with a known state and check the return value is correct.
The resulting test looks like this:
it("Should call setState with the expected parameters", () => {
let updateStateFunction = null;
const component = new CounterComponent();
component.setState = jest.fn((func) => { updateStateFunction = func;});
component.updateCounter();
const originalState = { counterValue: 10 };
const expectedState = { counterValue: 11};
expect(component.setState).toHaveBeenCalled();
expect(updateStateFunction(originalState)).toEqual(expectedState);
});