I want to update my callback function:
const getSchema = React.useCallback(
() => {
const schema = twSchema(
labels,
isInitialWarehouseActive ? initialWarehouse.id : undefined,
operationTypeDisabled ? initialWarehouse.operation_type : undefined
);
schema.addValidator((model, _schema) => {
if (model.dateRangeMode && (!model.timeRangeMode || model.hasRampHours) && !model.dateInpu.to) {
_schema.setModelError('dateInput.to', labels.fieldIsRequired);
...
...
});
return schema;
},
[initialStore]
);
where twSchema:
const twSchema = (labels, initialStoreId, storeOperationType) => new Schema({...
And use case of my getSchema:
<Form
key="time-window-form"
ctx="time-window-form"
eventsEmitter={eventsEmitter}
model={model}
onError={onError}
onSubmit={(data) => {
...
}).then(() => {
...
})
.catch(hint => displayMessageAndHighlightValidatedComponent(hint));
}}
schema={getSchema()}
>
I use this value (getSchema) to my form (I have to set schema to my form).
Depending on the error that can occur I'd like to add some validator to my schema BUT I CAN'T:
const displayMessageAndHighlightValidatedComponent = (hint) => {
getSchema().addValidator((model, schema) => {
//this code is not executed!!!
console.log(schema);
console.log('SCHEMA');
schema.setModelError('dateInputField', labels.createNextTimeWindow);
});
return onFailed();
};
The question is why? Why I can't update my object/function? I have to remove useCallback to be able to add validator dynamically...
Related
I am building a React cryptocurrency project for my portfolio and im just trying to figure out how I can solve this issue where the api call is taking null parameters. As you might see, I am polling for data upon render of the component, however, I am seeing the error in the console that the parameters are null. I would like to have the api request wait until the parameters are actually not null to make the api request. Here is my code and the url that returns a null value for the currencies to be used:
/api/v1/private/quote?pair=null/null&side=BUY&amount=
const baseAsset = transactionType === TRANSACTION_TYPES.BUY ? selectedCurrencyState.selectedToCurrency : selectedCurrencyState.selectedFromCurrency;
const quoteAsset = transactionType === TRANSACTION_TYPES.SELL ? selectedCurrencyState.selectedToCurrency : selectedCurrencyState.selectedFromCurrency;
const handleGetSwapPrice = () => {
const amountValue = TRANSACTION_TYPES.BUY ? amountState.fromCurrencyAmount : amountState.toCurrencyAmount;
getSwapPrice(`${baseAsset}/${quoteAsset}`, transactionType, amountValue)
.then((res) => {
const formattedPrice = formatCurrency('USD', res.price);
setSwapPrice(formattedPrice);
});
};
useEffect(() => {
if (isLoggedIn) {
getSwapPairs()
.then((res) => {
setSwapInfo(res.markets);
if (transactionType === TRANSACTION_TYPES.BUY) {
setSelectedCurrencyState({ ...selectedCurrencyState, selectedFromCurrency: localStorage.getItem('fromCurrency') || 'USD', selectedToCurrency: 'BTC' || localStorage.getItem('toCurrency') });
}
setTransactionType(localStorage.getItem('transactionType', transactionType) || TRANSACTION_TYPES.BUY);
});
const timer = setInterval(handleGetSwapPrice, 6000);
return () => clearInterval(timer);
}
}, []);
Any help is appreciated. Thanks!
You can use useMemo or useCallback (preferred useCallback) for that handleGetSwapPrice function.
Like this:
const handleGetSwapPrice = useCallback(() => {
const amountValue = TRANSACTION_TYPES.BUY ? amountState.fromCurrencyAmount : amountState.toCurrencyAmount;
getSwapPrice(`${baseAsset}/${quoteAsset}`, transactionType, amountValue)
.then((res) => {
const formattedPrice = formatCurrency('USD', res.price);
setSwapPrice(formattedPrice);
});
}, [baseAsset, quoteAsset, transactionType, amountState]);
And since you call that function from useEffect, you need to make those variables dependencies for it:
useEffect(() => {
if (isLoggedIn && baseAsset && quoteAsset) {
getSwapPairs()
.then((res) => {
setSwapInfo(res.markets);
if (transactionType === TRANSACTION_TYPES.BUY) {
setSelectedCurrencyState({ ...selectedCurrencyState, selectedFromCurrency: localStorage.getItem('fromCurrency') || 'USD', selectedToCurrency: 'BTC' || localStorage.getItem('toCurrency') });
}
setTransactionType(localStorage.getItem('transactionType', transactionType) || TRANSACTION_TYPES.BUY);
});
const timer = setInterval(handleGetSwapPrice, 6000);
return () => clearInterval(timer);
}
}, [baseAsset, quoteAsset]);
I have to replace this useResult that is fetching data from graphql
const locationOptions = useResult(
result,
[],
({ getLocations }): Option[] => formatOptions(getLocations)
)
and I want to change it for a computed function like
const locationOptions = computed(() => result.value.getLocations ?? [])
I was trying to use a watch to run the function but it seems not to be working
watch(locationOptions, () => {
formatOptions(locationOptions.value)
})
any suggestions?
You can use the format function already in the computed function:
const locationOptions = computed(() => {
return result.value?.getLocations ? formatOptions(result.value.getLocations) || []
})
There is a problem with deleting several string parameters. Only the last parameter is being deleted now.
upd: I did not specify that I wanted to achieve the ability to remove specific parameter values
this code does not work correctly:
const updateFiltersSearchParams = (paramKey, newValue) => {
const isParamExist = searchParams.getAll(paramKey).includes(newValue);
if (!isParamExist) {
searchParams.append(paramKey, newValue);
setSearchParams(searchParams);
} else {
const updatedSearchParams = new URLSearchParams(
[...searchParams].filter(
([key, value]) => key !== paramKey || value !== newValue
)
);
setSearchParams(updatedSearchParams);
}
};
const handleDeleteParams = () => {
[...checkboxParams].forEach((param) => {
updateFiltersSearchParams("selected", param);
});
};
Sandbox
change your handleDeleteParams function with this
const handleDeleteParams = () => {
setSearchParams([]);
};
If you want to delete *only the selected (or any specific queryString key) queryString parameters you can use the delete method of the URLSearchParams object, then enqueue the params URL update.
const handleDeleteParams = (key) => {
searchParams.delete(key);
setSearchParams(searchParams);
};
...
<button type="button" onClick={() => handleDeleteParams("selected")}>
Clear all "selected" params
</button>
Solved the problem by modifying the function like this
const toggleSearchParams = (params) => {
const newSearchParams = [...searchParams];
for (const prevParam of params) {
const index = newSearchParams.findIndex(
(newParam) =>
prevParam[0] === newParam[0] && prevParam[1] === newParam[1]
);
if (index === -1) {
newSearchParams.push(prevParam);
} else {
newSearchParams.splice(index, 1);
}
}
setSearchParams(new URLSearchParams(newSearchParams));
};
const handleChangeCheckBoxValue = (e) => {
toggleSearchParams([["selected", e.target.value]]);
};
const handleDeleteParams = () => {
toggleSearchParams(checkboxParams.map((param) => ["selected", param]));
};
My FlatList does not update when the props I pass from redux change. Every time I send a message I increase everyones unread message count in both firebase and in my redux store. I made sure to include key extractor and extra data, but neither helps. The only thing that changes the unread message count is a reload of the device. How do I make sure the flatList updates with MapStateToProps. I made sure to create a new object by using Object.Assign:
action:
export const sendMessage = (
message,
currentChannel,
channelType,
messageType
) => {
return dispatch => {
dispatch(chatMessageLoading());
const currentUserID = firebaseService.auth().currentUser.uid;
let createdAt = firebase.database.ServerValue.TIMESTAMP;
let chatMessage = {
text: message,
createdAt: createdAt,
userId: currentUserID,
messageType: messageType
};
FIREBASE_REF_MESSAGES.child(channelType)
.child(currentChannel)
.push(chatMessage, error => {
if (error) {
dispatch(chatMessageError(error.message));
} else {
dispatch(chatMessageSuccess());
}
});
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child(channelType)
.child(currentChannel).child('users')
UNREAD_MESSAGES.once("value")
.then(snapshot => {
snapshot.forEach(user => {
let userKey = user.key;
// update unread messages count
if (userKey !== currentUserID) {
UNREAD_MESSAGES.child(userKey).transaction(function (unreadMessages) {
if (unreadMessages === null) {
dispatch(unreadMessageCount(currentChannel, 1))
return 1;
} else {
alert(unreadMessages)
dispatch(unreadMessageCount(currentChannel, unreadMessages + 1))
return unreadMessages + 1;
}
});
} else {
UNREAD_MESSAGES.child(userKey).transaction(function () {
dispatch(unreadMessageCount(currentChannel, 0))
return 0;
});
}
}
)
})
};
};
export const getUserPublicChannels = () => {
return (dispatch, state) => {
dispatch(loadPublicChannels());
let currentUserID = firebaseService.auth().currentUser.uid;
// get all mountains within distance specified
let mountainsInRange = state().session.mountainsInRange;
// get the user selected mountain
let selectedMountain = state().session.selectedMountain;
// see if the selected mountain is in range to add on additional channels
let currentMountain;
mountainsInRange
? (currentMountain =
mountainsInRange.filter(mountain => mountain.id === selectedMountain)
.length === 1
? true
: false)
: (currentMountain = false);
// mountain public channels (don't need to be within distance)
let currentMountainPublicChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Public");
// mountain private channels- only can see if within range
let currentMountainPrivateChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Private");
// get public channels
return currentMountainPublicChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
let publicChannelsToDownload = [];
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
// add the channel ID to the download list
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
// flag whether you can check in or not
if (currentMountain) {
dispatch(checkInAvailable());
} else {
dispatch(checkInNotAvailable());
}
// if mountain exists then get private channels/ if in range
if (currentMountain) {
currentMountainPrivateChannelsRef
.orderByChild("key")
.on("value", snapshot => {
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",
snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
});
}
return publicChannelsToDownload;
})
.then(data => {
setTimeout(function () {
dispatch(loadPublicChannelsSuccess(data));
}, 150);
});
};
};
Reducer:
case types.UNREAD_MESSAGE_SUCCESS:
const um = Object.assign(state.unreadMessages, {[action.info]: action.unreadMessages});
return {
...state,
unreadMessages: um
};
Container- inside I hook up map state to props with the unread messages and pass to my component as props:
const mapStateToProps = state => {
return {
publicChannels: state.chat.publicChannels,
unreadMessages: state.chat.unreadMessages,
};
}
Component:
render() {
// rendering all public channels
const renderPublicChannels = ({ item, unreadMessages }) => {
return (
<ListItem
title={item.info.Name}
titleStyle={styles.title}
rightTitle={(this.props.unreadMessages || {} )[item.id] > 0 && `${(this.props.unreadMessages || {} )[item.id]}`}
rightTitleStyle={styles.rightTitle}
rightSubtitleStyle={styles.rightSubtitle}
rightSubtitle={(this.props.unreadMessages || {} )[item.id] > 0 && "unread"}
chevron={true}
bottomDivider={true}
id={item.Name}
containerStyle={styles.listItemStyle}
/>
);
};
return (
<View style={styles.channelList}>
<FlatList
data={this.props.publicChannels}
renderItem={renderPublicChannels}
keyExtractor={(item, index) => index.toString()}
extraData={[this.props.publicChannels, this.props.unreadMessages]}
removeClippedSubviews={false}
/>
</View>
);
}
}
Object.assign will merge everything into the first object provided as an argument, and return the same object. In redux, you need to create a new object reference, otherwise change is not guaranteed to be be picked up. Use this
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});
// or
const um = {...state.unreadMessages, [action.info]: action.unreadMessages }
Object.assign() does not return a new object. Due to which in the reducer unreadMessages is pointing to the same object and the component is not getting rerendered.
Use this in your reducer
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});
In my react component I have two functions. handleChangeInput(e) is called on 'OnChange' of input field and checkFields() is called from handleChangeInput(e)
constructor(){
super()
this.state={
email: '',
password:'',
validFields: false
}
}
handleChangeInput(e){
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value},()=>{
this.checkFields();
});
}
checkFields(){
if (this.state.email.length>0 && this.state.password.length>0 ) {
this.setState({validFields: true});
}else {
this.setState({validFields: false});
}
}
And in my index.test.js I have
describe('<Login />', () => {
describe('handleChangeInput', () => {
const component = new Login()
const wrapper = shallow(<Login />);
beforeEach(() => {
component.setState = jest.fn()
})
test('calls setState validFields false when no email/password', () => {
const state = { state : { email: '', password: ''} }
const args = { target : { name: 'name', value: 'value' } }
component.handleChangeInput.call(state, args)
expect(component.setState.mock.calls.length).toBe(1)
expect(wrapper.state().validFields).toEqual(false)
})
test('calls setState validFields true when email/password are ok', () => {
const state = { state : { email: 'email', password: 'password' } }
const args = { target : { name: 'name', value: 'value' } }
component.handleChangeInput.call(state, args)
expect(component.setState.mock.calls.length).toBe(1)
expect(wrapper.state().validFields).toEqual(false)
})
})
});
But my state is not being updated. As a result, 'validFields' is not set to true and my second test is failing. I tried wrapper.update() and wrapper.instance().forceUpdate() but still no success. Any help would be appreciated
I am guessing it might be because you override the setState function with jest.fn()
component.setState = jest.fn()
})
how about removing this?
hope my answer does not come too late, but you are trying to update the state in a wrong way.
First of all, remove these two:
const component = new Login()
beforeEach(() => {
component.setState = jest.fn()
})
And most likely you want to change this:
handleChangeInput(e){
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value},()=>{
this.checkFields();
});
}
handleChangeInput(e){
const name = e.target.name;
const value = e.target.value;
this.setState(()=>{
return { email: name}
});
this.setState(()=>{
return { password: value }
});
this.checkFields();
}
const component = new Login() does not bring any value to this test and you should not mock the setState if you want that it's actually changed.
Instead you should test the actual component (like you partially already do here)
Change the code like this:
test('calls setState validFields true when email/password are ok', () => {
const args = { target : { email: 'email', password: 'password' } }
wrapper.instance().handleChangeInput(args)
expect(wrapper.state('email')).toEqual('email')
expect(wrapper.state('password')).toEqual('password')
expect(wrapper.state('validFields')).toBeTruthy()
})
I found this answer in one of the git forums. It worked for me.
// somewhere in your test setup code
global.flushPromises = () => {
return new Promise(resolve => setImmediate(resolve))
}
test('something with unreachable promises', () => {
expect.hasAssertions()
const component = mount(<Something />)
// do something to your component here that waits for a promise to return
return flushPromises().then(() => {
component.update() // still may be needed depending on your implementation
expect(component.html()).toMatchSnapshot()
})
})