i'm trying to create a custom color picker with formik form
the probleme her is that parent component color are not changed :
import {SketchPicker} from "react-color";
export const MyColorPicker = ({label, ...props}) => {
// with useField is should not use onChange but i get an error without defining it myself
const [field] = useField(props);
const [color, setColor] = useState("#333");
const handleChange = color => {
setColor(color.hex);
field.onChange(color.hex);
};
const onComplete = color => {
setColor(color.hex);
field.onChange(color.hex);
};
return (
<div style={{padding: 10}}>
<label>{label}</label>
<SketchPicker {...props} {...field} color={color} onChange={handleChange} onChangeComplete={onComplete} />
</div>
);
};
as exemple this work :
export const MyTextAreaField = ({label, ...props}) => {
const [field, meta] = useField(props);
if (field && field.value === null) {
field.value = "";
}
return (
<div style={{display: "flex", flexDirection: "column"}}>
<label className="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated MuiInputLabel-shrink MuiFormLabel-filled">
{label}
</label>
<TextareaAutosize
rows={10}
{...field}
{...props}
style={{marginTop: 10, fontFamily: "Helvetica Neue", fontSize: 15}}
/>
{meta.touched && meta.error ? <div className="error">{meta.error}</div> : null}
</div>
);
};
and parent code :
<Formik
initialValues={{
data: { title :'', shortDescription:'', description:'', color:'')
}}
onSubmit={values => {
console.log(values.data) ; // data.color stay null
}}>
<Form>
<MyTextAreaField id="data.description" name="data.description" label={t("PROJECT.DESCRIPTION")} />
<MyColorPicker id="data.color" label={t("PROJET.COLOR")} name="data.color" />
</Form>
</Formik>
finally i ended doing something like this :
In parent Component :
<MyColorPicker
label={t("PROJECT.COLOR")}
onChange={color => {
data.project.color = color;
}}
/>
Component definition
export const MyColorPicker = ({label, onChange}) => {
const [color, setColor] = useState("#333");
const handleChange = color => {
setColor(color.hex);
};
return (
<div
style={{display: "flex", flexDirection: "row", justifyContent: "flex-start", alignItems: "center", padding: 10}}>
<label>{label}</label>
<ChromePicker color={color} onChange={handleChange} onChangeComplete={color=> onChange(color.hex) } />
</div>
)
})
Related
i stumbled into an issue i cant solve, i have an object 'customerDraft' which has nested object in it. i want to render every field plus the fields which are inside of 'customerDraft.metadata'.
my component looks like this:
const CustomerDetailEditModal = (props) => {
const {
open,
setOpen,
customerDraft,
customerProfileDraft,
setDraftCustomer,
setDraftProfile,
onUpdate
} = props;
const classes = useStyles();
const dispatch = useDispatch();
const [isPasswordHidden, setIsPasswordHidden] = useState(true);
// const [attributes, setAttributes] = useState({});
const projectId = useSelector(({ project }) => project.currentProject._id);
const generatedPassword = useSelector(({ customer }) => customer.password);
const isCurrentProjectCapstone = projectId === '4387564328756435';
const onModalCancel = () => {
setOpen(false);
if (isCurrentProjectCapstone) {
dispatch(removeItemFromCustomerDraftAction('password'));
}
};
const generatePassword = () => {
dispatch(getGeneratedPassword());
};
useEffect(() => {
if (!generatedPassword) return;
setDraftCustomer({
...customerDraft,
password: generatedPassword
});
// eslint-disable-next-line
}, [generatedPassword]);
console.log(customerDraft);
return (
<div>
<Modal
bodyStyle={{
fontSize: '12px',
height: 500,
margin: '0 auto'
}}
centered
footer={
<div
style={{
display: 'flex',
justifyContent: 'flex-end'
}}>
<CButton
htmlType="submit"
onClick={(e) => {
setOpen(false);
e.preventDefault();
}}
size="large"
type="secondary">
Cancel
</CButton>
<CButton
htmlType="submit"
onClick={onUpdate}
size="large"
type="primary"
// disabled={!isSaveEnabled}
>
Save
</CButton>
</div>
}
onCancel={onModalCancel}
title={
<span
style={{
fontSize: '24px',
fontWeight: 700,
lineHeight: '24px'
}}>
Edit User
</span>
}
visible={open}
width={customerProfileDraft ? 770 : 385}>
<form className={classes.form} id="customer-edit-form">
<div className={classes.wrapperDiv}>
{Object.entries(customerDraft).map((item, i) => {
if (customerDraft.fullName) {
if (restrictedData.includes(item[0] || item[0].toLowerCase().includes('id'))) {
return false;
}
}
if (restrictedData.includes(item[0]) || item[0].toLowerCase().includes('id')) {
return false;
}
return (
<CStandardInput
key={i}
allowClear
defaultValue={item[1]}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor={`standard-customer-edit-${item[0]}`}
id={`standard-customer-edit-${item[0]}`}
label={item[0]}
onChange={(event) => {
setDraftCustomer({
...customerDraft,
fullName: event.target.value
});
setDraftProfile({
...customerProfileDraft,
fullName: event.target.value
});
}}
size="large"
/>
);
})}
{isCurrentProjectCapstone && (
<div className={classes.passwordWrapper}>
<CStandardInput
adornment={
<>
<button
className={classes.buttonSvg}
onClick={() => {
navigator.clipboard.writeText(customerDraft.password || '');
}}
style={{
marginRight: '5px'
}}
type="button">
<img alt="copy password" src={copyIcon} />
</button>
<button
className={classes.buttonSvg}
onClick={() => setIsPasswordHidden(!isPasswordHidden)}
type="button">
<img
alt="toggle password visibility"
src={isPasswordHidden ? crossedEyeIcon : eyeIcon}
/>
</button>
</>
}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor="standard-input-user-password"
id="standard-input-user-password"
label="Password"
onChange={(e) => setDraftCustomer({ ...customerDraft, password: e.target.value })}
size="large"
type={isPasswordHidden ? 'password' : 'text'}
value={customerDraft.password || ''}
width="true"
/>
<CButton
onClick={generatePassword}
type="primary"
xstyle={{
borderRadius: '12px',
margin: '16px 0px 0px 16px'
}}>
Generate
</CButton>
</div>
)}
</div>
</form>
</Modal>
</div>
);
};
export default CustomerDetailEditModal;
notice how metdata field is rendered? i want to use recursion to output every field which metadata contains,
i know recursion but what i cant seem to figure out is where should this component call itself to do it.
any help with explanation so that i can understand the answer would be much appreciated!
this is the object im iterating on:
const customerData = {
createdAt: "2022-10-28T08:42:08.015Z",
email: "company#gmail.com",
firstName: "$$$$$$$",
fullName: "$$$$$$",
idNumber: "2813921321",
isEmailVerified: true,
isPhoneVerified: true,
lastName: "$$$$$",
metadata: {
birthDate: "2000-08-19 00:00:00.000",
gender: "Male",,
region: "",
status: "Adult",
statusExtra: "Student",
},
phone: "######",
project: "hlkjhkljhkjhk",
updatedAt: "2022-11-01T10:26:32.677Z",
username: null,
_id: "hlkjhlkjhlkjhlkjhlkjh",
};
see metadata? currently im outputting only the fields of the main(parent) object, but i also want to output the data which is contained in the 'metadata' key using recursion.
A solution to this could be to check if the key item[0] is "metadata". Then you could do the same as you did with the customerDraft object. Get the entries an map over them.
Note that I destructured the array you get from the .entries to make it more explicit what the variables are.
if (item[0] === "metadata") {
const inputs = Object.entries(item[1]).map(([metaKey, metaValue]) => (
<CStandardInput
key={metaKey}
allowClear
defaultValue={metaValue}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor={`standard-customer-edit-${metaKey}`}
id={`standard-customer-edit-${metaKey}`}
label={metaKey}
onChange={(event) => {
setDraftCustomer({
...customerDraft,
fullName: event.target.value,
});
setDraftProfile({
...customerProfileDraft,
fullName: event.target.value,
});
}}
size="large"
/>
));
return <>{inputs}</>;
}
return (
<CStandardInput
...
EDIT:
To support the nested data with recursion, I've created a function with returns an input for every key, value pair in the data.
You can add your extra if statements as desired
const renderInputs = (data) => {
const inputs = Object.entries(data).map(([key, value]) => {
if (
typeof value === "object" &&
!Array.isArray(value) &&
value !== null
) {
return renderInputs(value);
}
return (
<CStandardInput
key={key}
allowClear
defaultValue={value}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor={`standard-customer-edit-${key}`}
id={`standard-customer-edit-${key}`}
label={key}
onChange={(event) => {
setDraftCustomer({
...customerDraft,
fullName: event.target.value,
});
setDraftProfile({
...customerProfileDraft,
fullName: event.target.value,
});
}}
size="large"
/>
);
});
return inputs;
};
return <>{renderInputs(customerData)}</>;
Hope this helps you with your project!
i have a problem i have the famous react error : Uncaught (in promise) Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
But i'm looking for the issue but i didnt find because i think my hooks order are well but its not the case.... Do you have an idea where is the mistake?
Thanks
import { useNavigate, Link, useParams } from 'react-router-dom';
import { useEffect, useState, useCallback, useMemo } from 'react';
import { Formik } from 'formik';
import { useIntl, FormattedMessage } from 'react-intl';
import { useQuery, gql, useMutation } from '#apollo/client';
import { sub } from 'date-fns';
import {
Text,
Button,
TextField,
DateField,
Banner,
Loader,
Icon,
} from '#customer-portal/components';
import { StandardField } from '../../components/FormikFields';
import { useError } from '../../hooks/useError';
import { toISODate, toStartOfDay } from '../../utils/date';
import { useFilteredContracts } from '../../components/ContractsFilter';
export function MeterReadingToEnterForm() {
const [date, setDate] = useState();
const [forceResult, setForceResult] = useState(false);
const [successBanner, setSuccessBanner] = useState(false);
let navigate = useNavigate();
let { serial } = useParams();
const intl = useIntl();
const contract = useFilteredContracts();
const initialValues = {};
const getError = useError(error);
useEffect(() => {
return () => {
navigate('/meter-reading');
};
}, [contract.id]);
const {
loading,
data: queryData,
error,
refetch,
} = useQuery(QUERY, {
notifyOnNetworkStatusChange: true,
fetchPolicy: 'network-only',
variables: {
date: toStartOfDay(Date.now()),
contractId: contract.id,
},
});
const [enterMeterReading, { loading: enterLoading, error: enterError }] =
useMutation(MUTATION_QUERY, {
onCompleted: () => setSuccessBanner(true),
});
useEffect(() => {
refetch({ variables: { date: toStartOfDay(date) } });
}, [date]);
if (error) {
return (
<Banner data-test="BannerNoContracts" type="error" iconName="Alert">
<Text>{getError()}</Text>
</Banner>
);
}
if (enterError) {
return (
<Banner type="error" iconName="Alert">
<Text>{getError()}</Text>
</Banner>
);
}
const formattedValues = (valeur) => {
let array = Object.keys(valeur).map((key) => ({
id: key,
result: valeur[key],
}));
return array;
};
const handle = useCallback((values) => {
mutationQuery({
variables: {
data: {
serial: serial,
results: formattedValues(values),
},
Id: id,
},
refetchQueries: ['newQuery'],
});
});
return (
<div sx={{ display: 'flex', flexDirection: 'column', width: '100%', p: 2 }}>
<div sx={{ display: 'flex', pt: 6, width: '100%' }}>
<Icon
sx={{ display: 'flex', alignItems: 'center', m: 3 }}
color="primary"
name="Counter"
size="large"
/>
<Text weight="bold" sx={{ display: 'flex', alignItems: 'center' }}>
<FormattedMessage
defaultMessage="N° {number}"
values={{
number: `${serial}`,
}}
/>
</Text>
</div>
{successBanner && (
<div sx={{ mb: 4 }}>
<Banner
iconName="SuccessOutline"
type="success"
>
<Text>
<FormattedMessage
defaultMessage="Done"
/>
</Text>
</Banner>
</div>
)}
<div sx={{ display: 'flex', justifyContent: 'flex-start', py: 2 }}>
<DateField
name="datefoield"
value={date || Date.now()}
onChange={(date) => setDate(date)}
required={true}
label={intl.formatMessage({
defaultMessage: 'Date du relevé',
})}
defaultSelected={date}
disabledDays={[
{
before: sub(new Date(toISODate(Date.now())), {
days: 60,
}),
},
{
after: new Date(Date.now()),
},
]}
/>
</div>
{enterLoading && <Loader type="radiance" overlay={true} />}
{loading ? (
<Loader sx={{ mx: [0, 11] }} />
) : (
<Formik
onSubmit={handleEnterMeterReading}
initialValues={initialValues}
>
{({ values, handleSubmit }) => {
const query = useMemo(() => {
return queryDatafind(
(serialNumber) => serialNumber === serial
);
}, [queryData]);
return (
<form
noValidate={true}
onSubmit={handleSubmit}
>
<div
sx={{
display: 'flex',
flexWrap: 'wrap',
width: '100%',
py: 2,
}}
>
{query.map((value) => (
<div sx={{ mr: 4 }}>
<StandardField
as={TextField}
id={id}
label={label}
required
name={id}
size="standard"
type="number"
variant="standard"
/>
</div>
))}
</div>
<div sx={{ display: 'flex', py: 6 }}>
<Link to="/">
<Button
sx={{ my: 4, mr: 4 }}
startIcon="ErrorOutline"
size="standard"
variant="outlined"
>
{intl.formatMessage({
defaultMessage: 'Annuler',
})}
</Button>
</Link>
<Button
sx={{ whiteSpace: 'nowrap', my: 4 }}
startIcon="SuccessOutline"
size="standard"
type="submit"
disabled={loading}
>
{loading
? intl.formatMessage({
defaultMessage: 'loading',
})
: intl.formatMessage({
defaultMessage: 'Validate',
})}
</Button>
</div>
</form>
);
}}
</Formik>
)}
</div>
);
}
-- GRAPHQL REQUEST---
The issue is that you call useCallback after some if conditionals that may return prematurely. You could either remove the useCallback call (just set handle to a new function closure each rendering), or move the useCallback call to above the if ... returns.
I have two components the first where user can add a place to the favorites and the second is favorites component where user may see all his favorite places. When the user for the first time opens the favorites component everything works as expected: all the favorite places that user has already added to the favorites rendered. But if user go to the first component and add one more place and then go to the second component new place will not appear because component has already rendered and the state didn't changed because useEffect not triggered. Help me please what should I use in my FavouritePlaces component instead of useEffect to rerender this component every time when user open FavouritePlaces?
Component where user can add to favorites:
const ModalWindow = ({navigateToPlace, sendDataToParent, visible, marker}: HomeNavigationProps<"ModalWindow">) => {
const regex = /(<([^>]+)>)|( )|(&nbps)/ig;
const result = marker.description.replace(regex, '');
const [favKeys, setFavKeys] = useState([]);
const onDismiss = () => {
sendDataToParent(false)
}
const onNavigationTap = () => {
onDismiss();
navigateToPlace(true, marker.coordinates);
}
const getFavourites = async () => {
let keys = []
keys = await AsyncStorage.getAllKeys()
setFavKeys(keys);
}
const onHeartPress = async () => {
const jsonValue = JSON.stringify(marker)
try {
if (favKeys.includes(marker.id.toString())){
await AsyncStorage.removeItem(marker.id.toString())
await getFavourites();
} else {
await AsyncStorage.setItem(marker.id.toString(), jsonValue)
await getFavourites();
}
} catch (e) {
console.log('error in onHeartPress', e)
}
console.log('Done.')
//remove after test
try {
await AsyncStorage.removeItem('__react_native_storage_test')
} catch(e) {
// remove error
}
console.log('Done.')
}
return (
<Modal visible={visible} onDismiss={onDismiss} contentContainerStyle={styles.container}>
<IconButton
style={
styles.iconButton
}
icon="close"
color={Colors.black}
size={30}
onPress={() => onDismiss()}
/>
<Text
style={{fontStyle: "italic", fontSize: 20, alignSelf: "center", maxWidth: '75%'}}>{marker.title}
</Text>
<CustomCarousel {...{marker}} />
<ScrollView showsVerticalScrollIndicator={false} style={{marginTop: '3%', marginLeft: '3%', marginRight: '3%'}}>
<Text>{result}</Text>
</ScrollView>
<View style={{flexDirection: "row", justifyContent: "space-around", marginLeft: "3%", marginRight: "3%", marginBottom: "15%"}}>
<TouchableOpacity onPress={() => onNavigationTap()}>
<View style={{flexDirection: "row", alignItems: "center"}}>
<Ionicons size={height/20} name={'navigate-circle-outline'} />
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => onHeartPress()}>
{marker.id ?
<View style={{flexDirection: "row", alignItems: "center"}}>
{favKeys.includes(marker.id.toString()) ? <Ionicons size={height/20} name={'heart-dislike'} /> : <Ionicons size={height/20} name={'heart'} />}
</View> : undefined}
</TouchableOpacity>
</View>
</Modal>
);
}
export default ModalWindow;
My Favorite Places component:
const FavouritePlaces = ({navigation}: HomeNavigationProps<"FavouritePlaces">) => {
const [markers, setMarkers] = useState([]);
useEffect(() => {
const getFavourites = async () => {
let keys = []
try {
keys = await AsyncStorage.getAllKeys()
} catch (e) {
// read key error
}
let values
try {
let forDeletion = ['__react_native_storage_test', 'NAVIGATION_STATE_KEY-40.0.0'];
keys = keys.filter(item => !forDeletion.includes(item))
values = await AsyncStorage.multiGet(keys)
setMarkers(values)
} catch (e) {
// read error
}
}
getFavourites();
}, [])
const transition = (
<Transition.Together>
<Transition.Out type='fade'/>
<Transition.In type='fade'/>
</Transition.Together>
);
const list = useRef<TransitioningView>(null);
const theme = useTheme()
const width = (wWidth - theme.spacing.m * 3) / 2;
const [footerHeight, setFooterHeight] = useState(0);
return (
<Box flex={1} backgroundColor="background">
<StatusBar style="black" />
<Header
title="Избранные места"
left={{icon: 'menu', onPress: () => navigation.openDrawer()}}
right={{icon: 'shopping-bag', onPress: () => true}}
/>
<Box flex={1}>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{
paddingBottom: footerHeight,
}}>
<Transitioning.View ref={list} transition={transition} style={{}}>
{markers ?
<Box flexDirection='row' style={{justifyContent: "space-around"}}>
<Box>
{markers
.filter((_, i) => i % 2 === 0).map((currentMarker) => <Picture
key={currentMarker}
place={currentMarker}
width={width}
height={height}
/>)}
</Box>
<Box>
{markers
.filter((_, i) => i % 2 !== 0).map((currentMarker) => <Picture
key={currentMarker}
place={currentMarker}
width={width}
height={height}/>)}
</Box>
</Box> : undefined}
</Transitioning.View>
</ScrollView>
{/*<TopCurve footerHeight={footerHeight}/>*/}
<Box position='absolute' bottom={0} left={0} right={0} onLayout={({
nativeEvent: {
layout: {height},
}
}) => setFooterHeight(height)}>
</Box>
</Box>
</Box>
)
}
export default FavouritePlaces
Try this
useEffect(() => {
// ... Your code goes here
}, [navigation]);
this will render whenever update in navigate
I've found the solution. React navigation has hook useIsFocused, so what can we do is:
import { useIsFocused } from "#react-navigation/native";
const isFocused = useIsFocused();
useEffect(() => {
// ... Your code goes here
}, [isFocused]);
You can use React Context API to share the state across the screens.
Check out this Expo Snack I created.
import {
CompositeNavigationProp,
NavigationContainer,
NavigatorScreenParams,
} from '#react-navigation/native';
import {
createStackNavigator,
StackNavigationProp,
} from '#react-navigation/stack';
import * as React from 'react';
import {
Button,
FlatList,
ListRenderItem,
Text,
TextInput,
View,
} from 'react-native';
type MainStackParamsList = {
FavoritePlacesScreen: undefined;
};
type ModalStackParamsList = {
MainStack: NavigatorScreenParams<MainStackParamsList>;
AddFavoritePlacesModal: undefined;
};
type FavoritePlace = {
id: number;
name: string;
};
type FavoritePlacesContextValue = {
favoritePlaces: FavoritePlace[];
addNewFavoritePlace: (favoritePlace: FavoritePlace) => void;
removeFavoritePlace: (id: number) => void;
};
const FavoritePlacesContext = React.createContext<FavoritePlacesContextValue>({
favoritePlaces: [],
addNewFavoritePlace: () => {},
removeFavoritePlace: () => {},
});
const MainStack = createStackNavigator<MainStackParamsList>();
type FavoritePlacesScreenProps = {
navigation: CompositeNavigationProp<
StackNavigationProp<MainStackParamsList, 'FavoritePlacesScreen'>,
StackNavigationProp<ModalStackParamsList>
>;
};
const FavoritePlacesScreen = ({navigation}: FavoritePlacesScreenProps) => {
const {favoritePlaces, removeFavoritePlace} = React.useContext(
FavoritePlacesContext,
);
const renderItem = React.useCallback<ListRenderItem<FavoritePlace>>(
({item}) => {
return (
<View style={{height: 50, padding: 10, flexDirection: 'row'}}>
<Text style={{fontSize: 16}}>{item.name}</Text>
<Button onPress={() => removeFavoritePlace(item.id)} title="Remove" />
</View>
);
},
[removeFavoritePlace],
);
return (
<View style={{flex: 1}}>
<FlatList
data={favoritePlaces}
keyExtractor={(item) => String(item.id)}
renderItem={renderItem}
/>
<Button
onPress={() => {
navigation.navigate('AddFavoritePlacesModal');
}}
title="Add new favorite"
/>
</View>
);
};
const MainStackNavigator = () => {
return (
<MainStack.Navigator>
<MainStack.Screen
component={FavoritePlacesScreen}
name="FavoritePlacesScreen"
/>
</MainStack.Navigator>
);
};
type AddFavoritePlacesModalProps = {
navigation: StackNavigationProp<
ModalStackParamsList,
'AddFavoritePlacesModal'
>;
};
const AddFavoritePlacesModal = ({navigation}: AddFavoritePlacesModalProps) => {
const {addNewFavoritePlace} = React.useContext(FavoritePlacesContext);
const [favoritePlaceName, setFavoritePlaceName] = React.useState('');
const handleOnSave = React.useCallback(() => {
addNewFavoritePlace({
id: Date.now(),
name: favoritePlaceName,
});
navigation.goBack();
}, [addNewFavoritePlace, favoritePlaceName, navigation]);
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<View style={{borderRadius: 6, borderWidth: 1, borderColor: '#333'}}>
<TextInput
onChangeText={setFavoritePlaceName}
placeholder="Name your favorite place"
/>
</View>
<Button onPress={handleOnSave} title="Save" />
</View>
);
};
// Put the favorite places list screen and the add favorite place modal here.
// Then use FavoritePlacesContext.Provider to wrap ModalStack.Navigator in order
// for the context to be available on MainStack
const ModalStack = createStackNavigator<ModalStackParamsList>();
const ModalNavigator = () => {
const [favoritePlaces, setFavoritePlaces] = React.useState<FavoritePlace[]>(
[],
);
const addNewFavoritePlace = React.useCallback(
(favoritePlace: FavoritePlace) => {
setFavoritePlaces((prev) => [...prev, favoritePlace]);
},
[],
);
const removeFavoritePlace = React.useCallback((id: number) => {
setFavoritePlaces((prev) =>
prev.filter((favoritePlace) => favoritePlace.id !== id),
);
}, []);
return (
<FavoritePlacesContext.Provider
value={{
favoritePlaces,
addNewFavoritePlace,
removeFavoritePlace,
}}
>
<ModalStack.Navigator headerMode="none">
<ModalStack.Screen component={MainStackNavigator} name="MainStack" />
<ModalStack.Screen
component={AddFavoritePlacesModal}
name="AddFavoritePlacesModal"
options={{headerShown: false}}
/>
</ModalStack.Navigator>
</FavoritePlacesContext.Provider>
);
};
const App = () => {
return (
<NavigationContainer>
<ModalNavigator />
</NavigationContainer>
);
};
export default App;
We have this component. How to get notified when component unmounts or disappears?
function EventTime(props) {
const match = useRouteMatch();
const eventTime = findEventTime(match.params.eventTimeId);
const eventTimeId = match.params.eventTimeId;
const pageId = getId(undefined, eventTimeId, true, false);
const eventId = getId(undefined, eventTimeId, false, true);
const [
enableSellingTicketForEventTime,
setEnableSellingTicketForEventTime
] = useState(eventTime.enableSellingTicketForEventTime);
const [additionalNotesOnMyTicket, setAdditionalNotesOnMyTicket] = useState(
""
);
const changeAdditionalNotesOnMyTicket = event => {
setAdditionalNotesOnMyTicket(event.target.value);
};
const handleSwitchItem = () => {
setEnableSellingTicketForEventTime(!enableSellingTicketForEventTime);
};
const updateAdditionalNotesOnMyTicket = () => {
update({
pageId,
eventId,
eventTimeId,
additionalNotesOnMyTicket
}).then(data => {
const managedPages = JSON.parse(localStorage.getItem("managedPages"));
const index = managedPages.findIndex(
managedPage => managedPage.id === pageId
);
managedPages[index].events[eventId].eventTimes[
eventTimeId
].additionalNotesOnMyTicket = additionalNotesOnMyTicket;
localStorage.setItem("managedPages", JSON.stringify(managedPages));
console.log(JSON.parse(localStorage.getItem("managedPages")));
});
};
console.log(eventTime);
return (
<div>
<List>
{page().permission === "admin" && (
<Link
to={`/eventTimeLocationSelector/${match.params.eventTimeId}`}
style={{ textDecoration: "none", color: "black" }}
>
<NavigationItem
primary="Location"
secondary={
eventTime.locationId
? getLocationName(eventTime.locationId)
: ""
}
/>
</Link>
)}
{page().permission === "admin" && page().auditoriums && (
<Link
to={`/eventTimeAuditoriumSelector/${match.params.eventTimeId}`}
style={{ textDecoration: "none", color: "black" }}
>
<NavigationItem
primary="Auditorium"
secondary={
eventTime.auditoriumId
? getAuditoriumName(eventTime.auditoriumId)
: ""
}
/>
</Link>
)}
{page().permission !== "validateTicket" && (
<SwitchItem
primary="Enable selling ticket for event time"
checked={enableSellingTicketForEventTime}
change={handleSwitchItem}
default={false}
/>
)}
<Link
to={`/eventTimeTransactions/${match.params.eventTimeId}`}
style={{ textDecoration: "none", color: "black" }}
>
<NavigationItem primary="Sold tickets" />
</Link>
<TextFieldItem
primary="Additional notes on my ticket"
value={additionalNotesOnMyTicket}
onChange={changeAdditionalNotesOnMyTicket}
onBlur={updateAdditionalNotesOnMyTicket}
/>
</List>
{page().permission !== "validateTicket" &&
page().permission !== "teacher" && (
<Box style={{ display: "flex", justifyContent: "flex-end" }} mr={2}>
<Button variant="contained" color="primary">
Add person
</Button>
</Box>
)}
As you are using hooks you can use useEffect hook.
import {useEffect} from 'react';
Now, inside you component;
useEffect(() => {
return () => {
// Do something here when component unmounts
}
}, [])
Check more about useEffect here: https://reactjs.org/docs/hooks-effect.html
You can use like this.
You can declare a function to be called when the component unmounts as the useEffect()'s hook return value. Check out this: useEffect hook official docs
I am testing my component using Jest and Enzyme which has Input component from React Native Elements but I am getting error
Method “simulate” is meant to be run on 1 node. 0 found instead
Here is my code:
import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';
import { shallow } from 'enzyme';
import TransactionDetails from '../../app/BankingFeatures/components/transactionDetails';
const userProfile = {
data: {
user_profile: {
name: 'ashwith',
payment_status_id: '1234556'
}
}
};
const state = {
name: 'ashwith',
amount: null,
transaction_id: null,
transaction_date: '2019/06/18',
transaction_type: null,
name_status: undefined,
amount_status: undefined,
transaction_id_status: undefined,
transaction_date_status: undefined,
transaction_type_status: undefined,
};
describe('Transaction Test', () => {
const wrapper = shallow(<TransactionDetails
userProfile={userProfile}
/>);
const input = wrapper.find('Input').at(0);
input.simulate('changeText', 'eths');
});
transactionDetails.js
import moment from 'moment';
import React, { Component } from 'react';
import DatePicker from 'react-native-datepicker';
import Spinner from 'react-native-loading-spinner-overlay';
import { Icon, Input, Button } from 'react-native-elements';
import {
View,
Text,
ScrollView,
Platform,
KeyboardAvoidingView,
} from 'react-native';
import { Colors } from '../../Metrics';
import { styles } from '../styles/transactionDetails';
import MainContainer from '../../Utilities/mainContainer';
import * as Errmsg from '../../../config/Contents/errMessages';
import * as Placeholdermsg from '../../../config/Contents/placeHolders';
class TransactionDetails extends Component {
// State
constructor(props) {
super(props);
const { userProfile } = this.props;
const userData = userProfile && userProfile.data && userProfile.data.user_profile;
const dateNow = moment(new Date()).format('YYYY/MM/DD');
this.state = {
name: userData ? userData.name : null,
amount: null,
transaction_id: null,
transaction_date: dateNow,
transaction_type: null,
name_status: undefined,
amount_status: undefined,
transaction_id_status: undefined,
transaction_date_status: undefined,
transaction_type_status: undefined,
};
}
getState = () => this.state;
change = (transaction_id, amount) => this.setState({ transaction_id, amount });
// Validating Transaction
async nameValidation() {
const { name } = this.state;
const name_status = name ? undefined : Errmsg.name;
if (name_status === undefined) {
// this.tranIdInput.focus();
} else {
await this.setState({
name_status
});
}
}
// Validating Transaction Date
async transactionDateValidation() {
const { transaction_date } = this.state;
const transaction_date_status = await transaction_date ? undefined : 'Transaction date required';
if (transaction_date_status === undefined) {
this.tranIdInput.focus();
} else {
await this.setState({
transaction_date_status
});
}
}
// Validating Transaction ID
async transIdValidation() {
const { transaction_id } = this.state;
const transaction_id_status = transaction_id ? undefined : 'Transaction id required';
if (transaction_id_status === undefined) {
// this.transTypenput.focus();
} else {
await this.setState({
transaction_id_status
});
}
}
// Validating Transaction Type
async transTypeValidation() {
const { transaction_type } = this.state;
const transaction_type_status = transaction_type ? undefined : 'Transaction type required';
if (transaction_type_status === undefined) {
this.amountInput.focus();
} else {
await this.setState({
transaction_type_status
});
}
}
// Validating Amount
async amountValidation() {
const { amount } = this.state;
const amount_status = !isNaN(amount) ? amount && amount.length >= 5 ? undefined : 'min you can add 10000' : 'Amount required';
if (amount_status === undefined) {
// this.btnPress();
} else {
await this.setState({
amount_status
});
}
}
// Submitting Transaction details
btnPress() {
const { actions } = this.props;
const {
name,
amount,
transaction_id,
transaction_date,
transaction_type,
} = this.state;
actions.addBankTransactionDetails({
name,
amount,
transaction_id,
transaction_date,
transaction_type,
});
}
render() {
const {
name_status,
amount_status,
transaction_id_status,
transaction_date_status,
transaction_type_status,
} = this.state;
const { addBankTransactionDetails } = this.props;
return (
<MainContainer style={styles.container}>
<Spinner
cancelable
visible={addBankTransactionDetails && addBankTransactionDetails.isLoading}
textContent={'Loading...'}
/>
<KeyboardAvoidingView style={styles.container} behavior={Platform.OS === 'ios' && 'height'}>
<ScrollView
bounces={false}
showsVerticalScrollIndicator={false}
>
<View style={styles.formContainer}>
<Text style={styles.title}>Add Transaction Details</Text>
<Input
placeholder={Placeholdermsg.name}
ref={this.nameInput}
leftIcon={
<Icon
name='user'
size={24}
color={Colors.grey}
type='font-awesome'
/>
}
autoFocus
value={this.state.name}
autoCapitalize='characters'
returnKeyType='next'
onSubmitEditing={() => { this.nameValidation(); }}
onChangeText={(name) => this.setState({ name, name_status: undefined })}
inputStyle={styles.textinput}
containerStyle={styles.textFeildContainer}
inputContainerStyle={styles.textFeild}
errorMessage={(name_status) || null}
/>
<DatePicker
style={styles.dateContainer}
date={this.state.transaction_date}
mode="date"
placeholder="Select transaction date"
format="YYYY/MM/DD"
minDate="2019-01-01"
confirmBtnText="Confirm"
cancelBtnText="Cancel"
iconComponent={
<Icon
name="calendar"
type="entypo"
size={24}
color={Colors.grey}
containerStyle={{
position: 'absolute',
left: 10,
top: 8,
marginLeft: 0
}}
/>
}
customStyles={{
dateIcon: {
position: 'absolute',
left: 0,
top: 4,
marginLeft: 0
},
dateInput: {
height: 50,
marginLeft: 50,
borderWidth: 0,
alignItems: 'flex-start',
}
}}
onCloseModal={() => this.transactionDateValidation()}
onDateChange={async (transaction_date) => { this.setState({ transaction_date, transaction_date_status: undefined }, () => { console.log(transaction_date); }); this.transactionDateValidation(); }}
/>
{
(transaction_date_status !== undefined) &&
<View style={styles.errorContainer}>
<Text style={styles.errText}>{transaction_date_status}</Text>
</View>
}
<Input
ref={(input) => { this.tranIdInput = input; }}
placeholder='Enter transaction id'
leftIcon={
<Icon
name='key'
size={24}
color={Colors.grey}
type='entypo'
/>
}
autoCapitalize='characters'
returnKeyType='next'
onSubmitEditing={() => { this.transIdValidation(); }}
onChangeText={(transaction_id) => this.setState({ transaction_id, transaction_id_status: undefined })}
inputStyle={styles.textinput}
containerStyle={styles.textFeildContainer}
inputContainerStyle={styles.textFeild}
errorMessage={(transaction_id_status) || null}
/>
<Input
ref={(input) => { this.transTypenput = input; }}
placeholder='Enter transaction type'
leftIcon={
<Icon
name='bank'
size={20}
color={Colors.grey}
type='font-awesome'
/>
}
autoCapitalize='characters'
returnKeyType='next'
onSubmitEditing={() => { this.transTypeValidation(); }}
onChangeText={(transaction_type) => this.setState({ transaction_type, transaction_type_status: undefined })}
inputStyle={styles.textinput}
containerStyle={styles.textFeildContainer}
inputContainerStyle={styles.textFeild}
errorMessage={(transaction_type_status) || null}
/>
<Input
ref={(input) => { this.amountInput = input; }}
placeholder='Enter Amount'
leftIcon={
<Icon
name='rupee'
size={24}
color={Colors.grey}
type='font-awesome'
/>
}
keyboardType='numeric'
returnKeyType='done'
onSubmitEditing={() => { this.amountValidation(); }}
onChangeText={(amount) => this.setState({ amount, amount_status: undefined })}
inputStyle={styles.textinput}
containerStyle={styles.textFeildContainer}
inputContainerStyle={styles.textFeild}
errorMessage={(amount_status) || null}
/>
</View>
</ScrollView>
</KeyboardAvoidingView>
<View style={styles.bottomContainer}>
<Button
title="Submit"
buttonStyle={styles.Button}
onPress={() => this.btnPress()}
containerStyle={{ alignSelf: 'stretch' }}
/>
</View>
</MainContainer>
);
}
}
export default TransactionDetails;
debug
<MainContainer style={{...}}>
<Spinner cancelable={true} visible={false} textContent="Loading..." animation="none" color="white" size="large" overlayColor="rgba(0, 0, 0, 0.25)" />
<KeyboardAvoidingView style={{...}} behavior="height" enabled={true} keyboardVerticalOffset={0}>
<ScrollViewMock bounces={false} showsVerticalScrollIndicator={false}>
<View style={{...}}>
<Text style={{...}}>
Add Transaction Details
</Text>
<ForwardRef(Themed.Input) placeholder="Enter name" leftIcon={{...}} autoFocus={true} value="ashwith" autoCapitalize="characters" returnKeyType="next" onSubmitEditing={[Function: onSubmitEditing]} onChangeText={[Function: onChangeText]} inputStyle={{...}} containerStyle={{...}} inputContainerStyle={{...}} errorMessage={{...}} />
<DatePicker style={{...}} date="2019/06/24" mode="date" placeholder="Select transaction date" format="YYYY/MM/DD" minDate="2019-01-01" confirmBtnText="Confirm" cancelBtnText="Cancel" iconComponent={{...}} customStyles={{...}} onCloseModal={[Function: onCloseModal]} onDateChange={[Function: _callee]} androidMode="default" height={259} duration={300} iconSource={{...}} showIcon={true} disabled={false} allowFontScaling={true} hideText={false} TouchableComponent={[Function]} modalOnResponderTerminationRequest={[Function: modalOnResponderTerminationRequest]} />
<ForwardRef(Themed.Input) placeholder="Enter transaction id" leftIcon={{...}} autoCapitalize="characters" returnKeyType="next" onSubmitEditing={[Function: onSubmitEditing]} onChangeText={[Function: onChangeText]} inputStyle={{...}} containerStyle={{...}} inputContainerStyle={{...}} errorMessage={{...}} />
<ForwardRef(Themed.Input) placeholder="Enter transaction type" leftIcon={{...}} autoCapitalize="characters" returnKeyType="next" onSubmitEditing={[Function: onSubmitEditing]} onChangeText={[Function: onChangeText]} inputStyle={{...}} containerStyle={{...}} inputContainerStyle={{...}} errorMessage={{...}} />
<ForwardRef(Themed.Input) placeholder="Enter Amount" leftIcon={{...}} keyboardType="numeric" returnKeyType="done" onSubmitEditing={[Function: onSubmitEditing]} onChangeText={[Function: onChangeText]} inputStyle={{...}} containerStyle={{...}} inputContainerStyle={{...}} errorMessage={{...}} />
</View>
</ScrollViewMock>
</KeyboardAvoidingView>
<View style={{...}}>
<ForwardRef(Themed.Button) title="Submit" buttonStyle={{...}} onPress={[Function: onPress]} containerStyle={{...}} />
</View>
</MainContainer>