I'm having trouble with creating multiple tabs form in React. Example image:
Every new tab mounts a new component. Example code:
const handleAddTab = tabIndex => {
const exampleTab = {
name: `${Date.now()} / 16.02.2022 г.`,
jsx: <Document />,
deletable: true
}
const updatedTabs = state.tabs.map((t, i) => {
if (tabIndex === i) t.subtabs.push(exampleTab)
return t
})
setState(prev => ({
...prev,
tabs: updatedTabs
}))
}
And I'm rendering those components. Example code:
{state.activeSubtabIndex === 0 ?
<Documents />
:
getScreen().subtabs.map((s, i) =>
i === 0 ?
<>
</>
:
<div
style={state.activeSubtabIndex != i ? { display: 'none' } : {}}
>
{s.jsx}
</div>
)
}
I use getScreen() to fetch the current tab and get the subtabs. Example code:
const getScreen = () => {
const typeId = query.get('type_id')
const screen = state.tabs.find(t => t.typeId == typeId)
return screen
}
The way I remove a tab is like so:
const handleCloseTab = (tabIndex, subtabIndex) => {
const updatedTabs = state.tabs.filter((t, i) => {
if (tabIndex === i) {
t.subtabs = t.subtabs.filter((ts, i) => {
return subtabIndex != i
})
}
return t
})
setState(prev => ({
...prev,
tabs: updatedTabs
}))
}
The problem is that every time I delete (for example) the first tab, the second one gets the state from the first one (based on the index it was mapped).
I solved that problem by adding an extra key-value pair (deleted: false) to the exampleTab object
const handleAddTab = tabIndex => {
const exampleTab = {
name: `${Date.now()} / 16.02.2022 г.`,
jsx: <Document />,
deletable: true,
deleted: false <====
}
const updatedTabs = state.tabs.map((t, i) => {
if (tabIndex === i) t.subtabs.push(exampleTab)
return t
})
setState(prev => ({
...prev,
tabs: updatedTabs
}))
}
Whenever I close a tab I'm not unmounting It and removing its data from the array. I'm simply checking if deleted === true and applying style={{ display: 'none' }} to both the tab and component. Example code:
{state.activeSubtabIndex === 0 ?
<Documents />
:
getScreen().subtabs.map((s, i) =>
i === 0 ?
<>
</>
:
<div
style={state.activeSubtabIndex != i || s.deleted ? { display: 'none' } : {}}
key={s.typeId}
>
{s.jsx}
</div>
)}
Related
Hello I made a custom hook that goes hand in hand with a component for generic forms, however, I notice that it is slow when the state changes.
#customHook
export const useFormController = (meta) => {
const { setVisible, setLoading } = useContext(FeedBackContext);
const itemsRef = useRef([]);
const {
control,
setValue,
handleSubmit,
formState: { errors },
} = useForm<Partial<any>>({
mode: "onBlur",
shouldUnregister: true,
resolver: yupResolver(meta.validation),
});
const onRef = function (input) {
this.itemsRef.current[this.index] = input;
};
const onSubmit = (data: any) => {
if(meta.onSubmit){
meta.onSubmit(data);
}else{
setVisible(true);
setLoading(true);
meta.service.submit(data);
}
};
const isJsonEmpty = (val = {}) => {
return Object.keys(val).length == 0;
};
const onSubmitIditing = function () {
let index = ++this.index;
if (isJsonEmpty(errors) && this.end) {
handleSubmit(onSubmit)();
} else if (!this.end) {
this.itemsRef.current[index]._root.focus();
}
};
const setFields = (json) => {
const items = Object.keys(json);
const values = Object.values(json)
console.log('Cambiando fields en formControllser...', json)
for (let i = 0; i < items.length; i++) {
//console.log('Cambiando valores...', items[i], values[i])
setValue(items[i], values[i], { shouldValidate: true })
}
}
const getItems = () => {
console.log('Meta namess', meta.names, meta);
if (!meta && !meta.names) return [];
return meta.names.map(function (item, index) {
const isEnd =
meta.options && meta.options[item] && meta.options[item].end
? true
: false;
const isSecure =
meta.options && meta.options[item] && meta.options[item].secure
? true
: false;
const label = meta.alias ? meta.alias[item] : item;
const visible = meta.invisibles ? (meta.invisibles[item] ? false : true) : true;
const def = meta.defaults ? meta.defaults[item] : "";
const disabled = (val) => {
const b = meta.disableds ? (meta.disableds[item] ? true : false) : false;
return b;
}
return {
name: item,
label: label,
disabled: disabled,
onRef: onRef.bind({ itemsRef: itemsRef, index: index }),
onSubmitEditing: onSubmitIditing.bind({
itemsRef: itemsRef,
index: index,
end: isEnd,
errors: errors,
}),
visible: visible,
setFields,
defaultValue: def,
errors: errors,
secureTextEntry: isSecure,
styles: styles,
control: control,
options: meta.options[item] ? meta.options[item] : null,
};
});
}
const getData = useMemo(() => {
console.log('Get data calback v2', meta);
return {
handleSubmit,
items: getItems(),
onSubmit,
errors,
setFields
};
}, [meta])
return getData;
};
export const Client: React.FC<any> = React.memo(({ navigation, route }) => {
const {
alias,
defaults,
ubigeoSeleccionado,
setUbigeoSeleccionado,
editable,
inputLabel,
search,
getDisabled,
getInvisibles,
getAlias,
getDefaults,
disableds,
invisibles,
searchVisible,
idTypeDocument,
currentTypeDocument,
allTypeDocuments,
onValueChange,
onChangeText } = useContext(CreateClientContext);
const [mode, setMode] = useState(() => {
return route?.params?.mode;
})
const [client, setClient] = useState(() => {
return route?.params?.client;
})
const { dispatchClient } = useContext(GlobalContext);
const clientService = useClientService();
const ref = useRef(0);
const options = useMemo(() => {
return {
names: ["ane_numdoc", "ane_razsoc", "ane_alias", "ane_email", "ane_tel", "ane_tel2", "ane_dir"],
validation: clientValidation,
alias: alias,
defaults: defaults,
disableds: disableds,
service: {
submit: (data) => {
const parse = { ...data, ubigeo_id: ubigeoSeleccionado.ubi_id, ane_tipo_cp: 2, ane_tipdoc: currentTypeDocument.id }
if (mode == "update") {
//console.log('Actualizando...', client.id, parse);
clientService.updateById(client.id, parse)
.then(ok => {
Alert.alert('Actualizaciòn de cliente', "Cliente Actualizado")
dispatchClient({
type: 'create',
payload: ok
});
setTimeout(() => {
navigation.navigate('App', {
screen: "Clients"
})
}, 500)
}).catch(e => {
Alert.alert('Actualizaciòn de cliente', "No se pudo actualizar")
})
} else {
clientService.create(parse)
.then(ok => {
dispatchClient({
type: 'create',
payload: ok
});
Alert.alert('Cliente', "Cliente creado")
setTimeout(() => {
navigation.navigate('App', {
screen: "Clients"
})
}, 500)
})
.catch(e => {
(e);
Alert.alert('Error', "No se pudo crear el cliente")
})
}
}
},
invisibles: invisibles,
options: {
ane_dir: {
end: true
},
}
}
}, [getDisabled,
getInvisibles,
getAlias,
getDefaults])
const { items, handleSubmit, onSubmit, errors, setFields } = useFormController(options);
useEffect(() => {
ref.current++;
})
useEffect(() => {
if (route.params) {
console.log('Ref current', ref.current);
setMode(route.params.mode);
setClient(route.params.client);
}
}, [route.params])
useEffect(() => {
// console.log('Mode', mode, client.id);
if (mode == "update" && client) {
console.log('cambiando fields'), ref;
setFields(client)
}
}, [mode, client])
// useEffect(()=>{
// },[instanceDocument])
useEffect(() => {
console.log('Cambiando cliente...', mode, client);
console.log(ref.current);
}, [client])
useEffect(() => {
//Creación
console.log('set defaults..', ref.current);
if (Object.keys(defaults).length > 0) {
setFields(defaults)
}
}, [getDefaults])
console.log('Current', ref.current);
return (
<StyleProvider style={getTheme(material)}>
<Container style={{ backgroundColor: "#FAF9FE" }}>
<Content style={GlobalStyles.mainContainer}>
<Text style={GlobalStyles.subTitle}>Cliente</Text>
<PickerSearch
search={search}
editable={editable}
style={styles}
searchVisible={searchVisible}
placeholder={inputLabel}
pickerItems={allTypeDocuments}
onValueChange={onValueChange}
selectedValue={idTypeDocument}
onChangeText={onChangeText}
></PickerSearch>
<FormListController
// top={<Top />}
items={items}
style={GlobalStyles}
></FormListController>
<Bottom
ubigeoSeleccionado={ubigeoSeleccionado}
setUbigeoSeleccionado={setUbigeoSeleccionado}
onSubmit={handleSubmit(onSubmit)}
/>
</Content>
<AppFooter2 navigation={navigation} />
</Container>
</StyleProvider>
);
});
export const FormListController: React.FC<any> = React.memo(({ children, items = [], style, top = null, bottom = null }) => {
console.log('%c Form list controlllser...', "background-color:#ccc");
console.log('items', items)
return (
<>
<Form style={!!style.form ? style.form : style.formContainer}>
{top}
{items.map((item: any, index) => {
return <FormItemController {...item} key={index} />;
})}
{bottom}
</Form>
</>
);
});
export const FormItemController: React.FC<any> = React.memo((props: any) => {
console.log('Form item controller print', props)
if (props.visible) {
return (
<>
<Controller
control={props.control}
render={
({ field: { onChange, onBlur, value } }) => {
return (
<Item regular style={props.styles.item}>
<Label style={props.styles.label}>{props.label}</Label>
<Input
onBlur={onBlur}
disabled={props.disabled(value)}
onChangeText={(value) => onChange(value)}
secureTextEntry={props.secureTextEntry}
onSubmitEditing={props.onSubmitEditing}
value={value}
ref={props.onRef}
/>
</Item>
)
}
}
defaultValue={props.defaultValue}
name={props.name}
/>
{props.errors && props.errors[props.name] && (
<TextError value={props.errors[props.name].message} />
)}
{/* {props.options && props.options.errorEmpty && props.errors[""] && (
<TextError value={props.errors[""].message} />
)} */}
</>
);
}
else {
return <></>
}
});
I use the same component to create and edit a client, but when editing and viewing the FormItemController the logs time span is less than 1 second, however it is not rendered until after 8 or 10 seconds.
This is the output of my logs.
Update cliente... 500ms
set defaults.. 77
Client num render 77
Client num render 78
Client num render 79
Client num render 80
Client num render 81
Client num render 82
Client num render 83
Client num render 84
Client num render 85
Client num render 86
Client num render 87
Client num render 88
Client num render 89
Client num render 90
Client num render 91
Client num render 92
Client num render 93
Client num render 94
Client num render 95
Client num render 96
Client num render 97
Client num render 98
Client num render 99
Client num render 100 (6-8 seg)
the problem I have is when I edit, when I use the forms to create I have no problems, I do not find the bottleneck to improve and prevent it from being slow.
After trying several options, I realized that when passing the setValue, I was sending several nulls and objects that did not go with the form, filtering this data, made the final rendering pass from 8 seconds to less than 1 second
const setFields = (json) => {
const items = Object.keys(json);
const values = Object.values(json)
for (let i = 0; i < items.length; i++) {
if (!!values[i] && typeof values[i] != 'object') {
setValue(items[i], values[i])
}
}
}
Still with my react project, now I'm learning the hooks, perhaps I have an issue with the 'infinite loop' (Maximum update depth exceeded) and I can't figure out how to handle this. I have redux to handle the states. I used useEffect, because when I clicked on a div, it was showing, or did what I wanted, always one step late
function OffersWhatTypeOfWebsiteComponent(props) {
const dispatch = useDispatch()
const [active, setActive] = useState(1)
const [typeWebSite, setTypeWebSite] = useState('withCMS')
const updateTypeWebSite = () => {
dispatch({
type: "TYPE_WEBSITE",
payload: typeWebSite
})
}
useEffect(() => {
updateTypeWebSite();
}, [updateTypeWebSite()]);
const renderElements = (props) => {
switch (active) {
case 1 :
return (
<>
<OffersChooseYourPackageCMSComponent
/>
</>
);
break
case 2 :
return (
<>
<OffersChooseYourPackageLikeAProComponent/>
</>
)
default:
return 'error'
}
}
return (
<div>
<OffersTitleCardComponent
titleNumber='2'
titleSection='What type of Website'
/>
<div className='chooseYourProject'>
<OffersCardsWithCheckComponent
titleCard='With CMS'
subtitleCard='xxxx'
active={active === 1 ? 'listing-active' : 'listing'}
onClick={() => {
setActive(1);
setTypeWebSite('withCMS');
updateTypeWebSite()
}}
/>
<OffersCardsWithCheckComponent
titleCard='Like a pro'
subtitleCard='xxx'
active={active === 2 ? 'listing-active' : 'listing'}
onClick={() => {
setActive(2);
setTypeWebSite('like a pro');
updateTypeWebSite()
}}
/>
</div>
{
renderElements({})
}
</div>
);
}
export default OffersWhatTypeOfWebsiteComponent;
This is the sub-component :
function OffersChooseYourPackageCMSComponent(props) {
const dispatch = useDispatch()
const [active, setActive] = useState(1)
const [packageWebSite, setPackageWebSite] = useState('Shopify')
const [pricePackageWebSite, setPricePackageWebSite] = useState(300)
const updatePackageWebSite = () => {
dispatch({
type: "PACKAGE_WEBSITE",
payload: {packageWebSite, pricePackageWebSite}
})
}
useEffect(() => {
updatePackageWebSite();
}, [updatePackageWebSite()]);
const renderElements = () => {
switch (active) {
case 1 :
return (
<>
<OffersNumbersOfProductsComponent/>
</>
);
break
case 2 :
return (
<>
<OffersNumbersOfPagesComponent/>
<OffersWoocommerceComponent/>
</>
);
break
default :
return 'error'
}
}
return (
<div>
<OffersTitleCardComponent
titleNumber='3'
titleSection='Choose your package'
/>
<div className="shopify">
<OffersCardsWithCheckComponent
onClick={() => {
setActive(1);
setPackageWebSite('Shopify');
setPricePackageWebSite(300);
updatePackageWebSite()
}}
active={active === 1 ? "listing-active" : "listing"}
titleCard='Shopify'
subtitleCard='xxx'
pricePackage='$54349'
detailPrice='(1 landing page + up to 5 products)'
/>
<OffersCardsWithCheckComponent
onClick={() => {
setActive(2);
setPackageWebSite('WordPress');
setPricePackageWebSite(900);
updatePackageWebSite()
}}
active={active === 2 ? "listing-active" : "listing"}
titleCard='WordPress'
subtitleCard='xxxx'
pricePackage='$23349'
detailPrice='(1 landing page)'
/>
</div>
{renderElements()}
</div>
);
}
export default OffersChooseYourPackageCMSComponent;
Don't hesitate to tell me some good practices too, on what I should arrange also if needed.
Thanks for your help
You should replicate this into your sub-component as well
const updateTypeWebSite = useCallback(() => {
dispatch({
type: "TYPE_WEBSITE",
payload: typeWebSite
})
}, [typeWebSite])
useEffect(() => updateTypeWebSite(), [updateTypeWebSite]);
Read this at reactjs documentation
Found something that worked, don't know if it's the best solutuion :
const [typeWebSite, setTypeWebSite] = useState('withCMS')
const updateTypeWebSite = () => {
dispatch({
type: "TYPE_WEBSITE",
payload: typeWebSite
})
}
useEffect(() => {
updateTypeWebSite()
},[typeWebSite,setTypeWebSite]);
I have a screen that represents a schedule which lets the user give each week a name. Once the user finishes editing all the weeks he will presses the checkmark and the app should update the backend.
I am getting a very weird bug where the first time I click the checkmark edit data gets updated (the log says "editData changed" and the ui changes) but when I print the state inside updateTheDB it has not been updated. If I try to enter edit mode again and save without making any new changes the previous changes are updated inside updateTheDB.
I thought this was a copy by reference not but value problem but I am using JSON.parse(JSON.stringify to copy so that can't be it.
The call to setState (setEditData) is inside onSavePressed which is in a modal that opens when the user tries to name a week.
Does anyone know what could have caused this?
EDIT
I want the to cause a render when setEditToServer is called
This is my code:
const Schedule = (props: IPorps) => {
const { week_names_props, navigation } = props;
const days = ['s', 'm', 't', 'w', 't', 's', 'w'];
//bottom Modal
const [active_modal, setActiveModal] = useState<MProps>(null)
//data
const [serverData, setServerData] = useState<IData>({ weekNames: week_names_props, weekEvents: {} })
//edit_mode data
const [editData, setEditData] = useState<IData>(null)
//other
const [isLoading, setIsLoading] = useState<boolean>(false)
const [editable, setEditable] = useState<boolean>(false)
const doTheLoadingThingy = (): void => {
Axios.get(`api/weeks/getWeekNameByCompanyId`, {
params: {
company_id: 1,
},
}).then((response) => {
setServerData({ ...serverData, weekNames: response.data })
//useEffect will hide the loading and the modal
})
.catch(() => { setIsLoading(false); errorToast("get") })
}
const setEditToServer = () => {
console.log("setEditToServer"
setEditData({
weekNames: JSON.parse(JSON.stringify(serverData.weekNames)),
weekEvents: {}
})
}}
useEffect(() => {
setEditToServer()
setIsLoading(false)
}, [serverData])
useEffect(() => {
console.log("editData changed")
}, [editData])
const updateTheDB = () => {
setIsLoading(true)
console.log(editData)
//send to backend
}
useEffect(() => {
navigation.setOptions({
headerLeft: () => (
<View style={{ flexDirection: "row", justifyContent: "space-around", alignItems: "center", paddingHorizontal: 30 }}>
{editable ? (
<>
<Icon
name={"check"}
onPress={() => {
updateTheDB()
setEditable(false)
}}/>
<Icon
name={"cancel"}
onPress={() => {
console.log("cancel")
setEditToServer()
setEditable(false)
}}/>
</>
) : (
<Icon
name={'edit'}
onPress={() => setEditable(true)}/>
)}
</View>
)
})
}, [editable])
return (
<>
{isLoading ?
(<ActivityIndicator size="large" />) :
(
<>
<BottomModal
innerComponent={active_modal ? active_modal.modal : null}
innerComponentProps={active_modal ? active_modal.props : null}
modalStyle={active_modal ? active_modal.style : null}
modalVisible={(active_modal != null)}
onClose={() => {
setActiveModal(null);
}}
/>
<WeekDaysHeader />
{editData ?
(<FlatList
data={editData.weekNames}
keyExtractor={(item) => item.week.toString()}
style={styles.flatListStyle}
// extraData={editData?editData.weekEvents:null}
renderItem={({ item }) => {
return (
<Week
days={days}
events={editData.weekEvents[item.week.toString()]}
dayComponents={ScheduleScreenDay}
week_name={item.week_name ? item.week_name : null}
week_number={Number(item.week)}
onHeaderPressed={editable ?
(week_number, week_title) => {
console.log("Pressed", week_number, week_title)
setActiveModal({
props: {
week_number_props: week_number,
week_title_props: week_title,
onSavePressedProps: (new_name) => {
if (new_name) {
let tmp = JSON.parse(JSON.stringify(editData.weekNames))
const i = tmp.findIndex((item) => item.week.toString() === week_number.toString())
tmp[i].week_name = new_name
setEditData((prev) => ({ weekNames: tmp, weekEvents: prev.weekEvents }))
}
}
}, modal: NameWeekModalComponet,
style: styles.weekNameModalStyle
});
} : undefined}
/>
);
}}
/>) : null}
</>
)
}
</>)
};
export default Schedule;
I thought this was a copy by reference not but value problem but I am using JSON.parse(JSON.stringify) to copy so that can't be it.
Thats exactly the problem:
// Always true
JSON.parse(JSON.stringify(['a'])) !== JSON.parse(JSON.stringify(['a']))
Therefore whenever setEditToServer is called the component will re-render, that's because React makes a shallow comparison with the previous state when deciding for render.
const setEditToServer = () => {
// Always re-render
setEditData({
weekNames: JSON.parse(JSON.stringify(serverData.weekNames)),
weekEvents: {}
})
}}
The problem was in this useEffect:
useEffect(() => {
navigation.setOptions({
headerLeft: () => (
//...
)
})
}, [editable])
The functions inside it used the editData state but the useEffect didn't have it in the deps list so when the onPress was clicked it got the old state of editData. The solution was to add editData to the deps list. Like this:
useEffect(() => {
navigation.setOptions({
headerLeft: () => (
//...
)
})
}, [editable, editData])
I'm getting the data from the database and show it in a FlatList. Whenever I add or remove something from the data the data isn't showing correctly in the FlatList.
Whenever I remove something it shows an empty list.
Whenever I add something it only shows the newly added data - nothing else.
I'm using firebase realtime database and use the data I get as follows:
firebase.database().ref(`/wordlists/${editKey}`).on('value', snap => {
if (snap.val() !== null) {
setIsLoading(false);
const val = snap.val().words;
const data = [];
Object.keys(val).forEach(key => {
data.push({ key, word: val[key].word });
})
setWords(data);
// setWords([...data]) doesn't work either.
}
})
My Flatlist looks like this:
<FlatList
data={words}
renderItem={renderItem}
keyExtractor={item => item.key}
extraData={words}
/>
When I console.log() the data I always get the data I want to show but the FlatList just won't show it correctly.
It also doesn't work when I use the spread-operator and/or extraData.
Because someone asked for it here is the entire file (I left out the styling and the imports)
const EditList = ({ editKey }) => {
const [wordlist, setWordlist] = useState(0);
const [refresh, setRefresh] = useState(false);
const [words, setWords] = useState([]);
const [wordLoading, setWordLoading] = useState({ loading: false });
const [loading, setIsLoading] = useState(false);
const [btnLoading, setBtnLoading] = useState(false);
const [word, setWord] = useState('');
useEffect(() => {
if (editKey !== 0) {
setIsLoading(true);
firebase.database().ref(`/wordlists/${editKey}`).on('value', snap => {
if (snap.val() !== null) {
setIsLoading(false);
setWordlist({...snap.val()});
const val = snap.val().words;
const data = [];
Object.keys(val).forEach(key => {
data.push({ key, word: val[key].word });
})
setWords([...data]);
setRefresh(!refresh);
console.log(data, 'DATA');
}
})
}
}, [editKey])
const onAdd = () => {
setBtnLoading(true);
firebase.database().ref(`/wordlists/${editKey}/words`).push({ word })
.then(() => {
setBtnLoading(false);
setWord('');
setRefresh(!refresh);
})
}
const onDelete = (key) => {
setWordLoading({ key, loading: true });
firebase.database().ref(`/wordlists/${editKey}/words/${key}`).remove().then(() => {
setWordLoading({ loading: false });
setRefresh(!refresh);
});
}
const renderItem = ({ item }) => (
<ItemWrapper>
<ItemWord>{ item.word }</ItemWord>
<DeleteView onPress={() => onDelete(item.key)}>
{ wordLoading.loading && wordLoading.key === item.key ?
<ActivityIndicator size="small" /> :
<DIcon name="trash-2" size={24} />
}
</DeleteView>
</ItemWrapper>
)
const createData = (words) => {
const data = [];
if (typeof words !== 'undefined') {
Object.keys(words).forEach(key => {
const obj = { key, word: words[key].word };
data.push(obj);
})
}
console.log(data, 'DATADATADATA');
return data;
}
if (editKey === 0) {
return (
<NokeyWrapper>
<NoKeyText>No list selected...</NoKeyText>
</NokeyWrapper>
)
}
if (loading) {
return (
<NokeyWrapper>
<ActivityIndicator size="large" />
</NokeyWrapper>
)
}
return (
<Wrapper
behavior={Platform.OS == "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === 'ios' && 180}
>
<WordListName>{wordlist.listName}</WordListName>
<FlatListWrapper>
<FlatList
data={words}
renderItem={renderItem}
keyExtractor={item => item.key}
//extraData={refresh}
extraData={words}
/>
</FlatListWrapper>
<AddWordWrapper>
<SInput value={word} onChangeText={(text) => setWord(text)} />
<Button onPress={() => onAdd()} loading={btnLoading}>
<Feather name="plus" size={24} color="black" />
</Button>
</AddWordWrapper>
</Wrapper>
)
};
export default EditList;
u need to useRef for this instance because the new 'words' is not inside the .on('value') call.
const [words, _setWords] = useState([]);
const wordRef = useRef(words)
//function to update both wordRef and words state
const setWords = (word) => {
wordRef = word
_setWords(word)
}
useEffect(() => {
if (editKey !== 0) {
setIsLoading(true);
let data = wordRef //create a temp data variable
firebase.database().ref(`/wordlists/${editKey}`).on('value', snap => {
if (snap.val() !== null) {
setIsLoading(false);
setWordlist({...snap.val()});
const val = snap.val().words;
Object.keys(val).forEach(key => {
data.push({ key, word: val[key].word });
})
setWords(data);
setRefresh(!refresh);
console.log(data, 'DATA');
}
})
return () => firebase.database().ref(`/wordlists/${editKey}`).off('value') // <-- need to turn it off.
}
}, [editKey, wordRef])
You probably need to change setRefresh etc with the same method if they are not refreshing.
After a lot more tries I found out the problem was somewhere else. Somehow using 'flex: 1' on my in my renderItem() was causing this issue. I actually found this issue also on github: GitHub React Native issues
So after removing 'flex: 1' from the element everything was showing as expected.
// before
const renderItem = ({ item }) => (
<ItemWrapper style={{ flex: 1, flexDirection: row }}>
<ItemWord>{ item.word }</ItemWord>
</ItemWrapper>
)
// after
const renderItem = ({ item }) => (
<ItemWrapper style={{ width: '100%' }}>
<ItemWord>{ item.word }</ItemWord>
</ItemWrapper>
)
I have an array of HTML elements that I want to render on a page, but depending on the element I'd like to adjust how they get wrapped.
const sections = [
{
id: 'top',
},
{
id: 'left',
},
{
id: 'right',
},
{
id: 'bottom',
}
]
const Element = (props) => {
return <div id={props.id}>hello</div>
}
const ArticleRightRail = (props) =>
<div>
<header>
</header>
<article>
{sections.map((section, i) => <Element key={i} {...section} >hello!</Element> )}
</article>
</div>
In the example above I want any id which is not top or bottom to be rendered within <article>, and anything that is top or bottom to be rendered within <header> tags. What is the best way of handling a situation like this with React?
Use ES6 array filter method to filter sections array as below:
const listForHeader = sections.filter((section, i) => {
return section === "top" || section === "bottom";
});
const listForArticle = sections.filter((section, i) => {
return section != "top" || section != "bottom";
})
Then use above 2 lists in HEADER and ARTICLE tags respectively using array map method.
check this
const listHeader = sections.filter((section, i) => {
return section === "top" || section === "bottom";
});
const listArticle = sections.filter((section, i) => {
return section != "top" || section != "bottom";
})
#JamesIves, the react code is:
const listForHeader = sections.filter((section, i) => {
return section === "top" || section === "bottom";
});
const listForArticle = sections.filter((section, i) => {
return section != "top" || section != "bottom";
})
const ArticleRightRail = (props) =>
<div>
<header>
{listForHeader.map((section, i) => <Element key={i} {...section} >hello!</Element> )}
</header>
<article>
{listForArticle.map((section, i) => <Element key={i} {...section} >hello!</Element> )}
</article>
</div>