Passing Array into FlatList React Native (Not Rendering) - arrays

So I have this array, as shown below:
const categoryData=[
{
id: 1,
name: "cat1",
icon: icons.one
},
{
id: 2,
name: "cat2",
icon: icons.two
},
]
I'm trying to store it in a useState const called categories, and then display categories in the data parameter within a FlatList:
const [categories, setCategories] = React.useState(categoryData)
<FlatList
data={categories}
keyExtractor={item => `${item.id}`}
renderItem={renderItem}
numColumns={4}
contentContainerStyle={{ padding: SIZES.padding }}
/>
However, nothing renders when within the FlatList (important to note, the FlatList loads when a Modal is triggered, and the FlatList does render the items properly when data={categoryData} instead of just categories).
Thanks for the assistance.
EDIT: Here is an Expo link to depict the issue (https://snack.expo.dev/HsffYfsrc)

In your code example you've used/referenced categoryData prior to it having been declared.
const App = () => {
const [categories, setCategories] = React.useState(categoryData); // <-- used, undefined
const [modalVisible, setModalVisible] = React.useState(false);
const categoryData = [ // <-- declared here
{
id: 1,
name: 'cat1',
icon: 'test'
},
{
id: 2,
name: 'cat2',
icon: 'test'
},
];
function renderList() {
...
}
return <SafeAreaView>{renderList()}</SafeAreaView>;
}
I don't see any internal dependencies (to the component) to require that categoryData be declared within the component. I suggest declaring it outside the component so it's declared prior to the component and in scope.
const categoryData = [ // <-- declared
{
id: 1,
name: 'cat1',
icon: 'test',
},
{
id: 2,
name: 'cat2',
icon: 'test',
},
];
const App = () => {
const [categories, setCategories] = React.useState(categoryData); // <-- defined
const [modalVisible, setModalVisible] = React.useState(false);
function renderList() {
...
}
return <SafeAreaView>{renderList()}</SafeAreaView>;
};
Expo Snack
If categoryData is not known at compile time and is actually fetched later then provide valid initial state and load/update the category state in an useEffect hook.
Example:
const App = () => {
const [categories, setCategories] = React.useState([]); // <-- valid initial state
const [modalVisible, setModalVisible] = React.useState(false);
useEffect(() => {
... fetch/loading logic
setModalVisible(data); // <-- update state with fetched/loaded data
}, []);
function renderList() {
...
}
return <SafeAreaView>{renderList()}</SafeAreaView>;
};

If you say it works when Modal is triggered, then the issue might be that your data is not ready on load.
I suggest you try:
{categories && <FlatList
data={categories}
keyExtractor={item => `${item.id}`}
renderItem={renderItem}
numColumns={4}
contentContainerStyle={{ padding: SIZES.padding }}/>}

Related

I'm having some trouble trying to change specific button's value when clicked and get back to the previous value with setTimeout()

Here's my example code (I'm trying in a separated component with different Data.
import React, { useState } from 'react';
const initialState = [
{ id: 0, text: 'add to cart' },
{ id: 1, text: 'add to cart' },
{ id: 2, text: 'add to cart' },
];
const Test: React.FC = () => {
const [text, setText] = useState(initialState);
const handleClick = (index: number) => {
const newText = [...text];
newText[index].text = 'Added to cart...';
setText(newText);
setTimeout(() => {
setText(initialState);
}, 2000);
};
return (
<div>
{text.map((buttons, index) => (
<button key={index} onClick={() => handleClick(index)}>
{buttons.text}
</button>
))}
</div>
);
};
export default Test;
You should deep clone your array with JSON.parse(JSON.stringify(...)) since you are editing nested properties.
This code should work:
const handleClick = (index: number) => {
const newText = JSON.parse(JSON.stringify(text));
newText[index].text = 'Added to cart...';
setText(newText);
setTimeout(() => {
setText(initialState);
}, 2000);
};

How to make data persist on refresh React JS?

I have a code where I mount a table with some firebase data but for some reason the values disappear and I been struggling for the next 2 weeks trying to solve this issue I haven't found a solution to this and I have asked twice already and I have try everything so far but it keeps disappearing.
Important Update
I just want to clarify the following apparently I was wrong the issue wasn't because it was a nested collection as someone mentioned in another question. The issue is because my "user" is getting lost in the process when I refresh.
I bring the user from the login to the app like this:
<Estudiantes user={user} />
and then I receive it as a props
function ListadoPedidos({user})
but is getting lost and because is getting lost when I try to use my firebase as:
estudiantesRef = db.collection("usuarios").doc(user.uid).collection("estudiantes")
since the user is "lost" then the uid will be null. Since is null it will never reach the collection and the docs.
I have a simple solution for you. Simply raise the parsing of localStorage up one level, passing the preloadedState into your component as a prop, and then using that to initialize your state variable.
const ListadoEstudiantes = (props) => {
const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
return <Listado preloadedState={estData} {...props} />;
};
Then initialize state with the prop
const initialState = props.preloadedState || [];
const [estudiantesData, setEstudiantesData] = useState(initialState);
And finally, update the useEffect hook to persist state any time it changes.
useEffect(() => {
window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
}, [estudiantes]);
Full Code
import React, { useState, useEffect } from 'react';
import { db } from './firebase';
import { useHistory } from 'react-router-dom';
import './ListadoEstudiantes.css';
import {
DataGrid,
GridToolbarContainer,
GridToolbarFilterButton,
GridToolbarDensitySelector,
} from '#mui/x-data-grid';
import { Button, Container } from '#material-ui/core';
import { IconButton } from '#mui/material';
import PersonAddIcon from '#mui/icons-material/PersonAddSharp';
import ShoppingCartSharpIcon from '#mui/icons-material/ShoppingCartSharp';
import DeleteOutlinedIcon from '#mui/icons-material/DeleteOutlined';
import { Box } from '#mui/system';
const ListadoEstudiantes = (props) => {
const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
return <Listado preloadedState={estData} {...props} />;
};
const Listado = ({ user, preloadedState }) => {
const history = useHistory('');
const crearEstudiante = () => {
history.push('/Crear_Estudiante');
};
const initialState = preloadedState || [];
const [estudiantesData, setEstudiantesData] = useState(initialState);
const parseData = {
pathname: '/Crear_Pedidos',
data: estudiantesData,
};
const realizarPedidos = () => {
if (estudiantesData == 0) {
window.alert('Seleccione al menos un estudiante');
} else {
history.push(parseData);
}
};
function CustomToolbar() {
return (
<GridToolbarContainer>
<GridToolbarFilterButton />
<GridToolbarDensitySelector />
</GridToolbarContainer>
);
}
const [estudiantes, setEstudiantes] = useState([]);
const [selectionModel, setSelectionModel] = useState([]);
const columns = [
{ field: 'id', headerName: 'ID', width: 100 },
{ field: 'nombre', headerName: 'Nombre', width: 200 },
{ field: 'colegio', headerName: 'Colegio', width: 250 },
{ field: 'grado', headerName: 'Grado', width: 150 },
{
field: 'delete',
width: 75,
sortable: false,
disableColumnMenu: true,
renderHeader: () => {
return (
<IconButton
onClick={() => {
const selectedIDs = new Set(selectionModel);
estudiantes
.filter((x) => selectedIDs.has(x.id))
.map((x) => {
db.collection('usuarios')
.doc(user.uid)
.collection('estudiantes')
.doc(x.uid)
.delete();
});
}}
>
<DeleteOutlinedIcon />
</IconButton>
);
},
},
];
const deleteProduct = (estudiante) => {
if (window.confirm('Quiere borrar este estudiante ?')) {
db.collection('usuarios').doc(user.uid).collection('estudiantes').doc(estudiante).delete();
}
};
useEffect(() => {}, [estudiantesData]);
const estudiantesRef = db.collection('usuarios').doc(user.uid).collection('estudiantes');
useEffect(() => {
estudiantesRef.onSnapshot((snapshot) => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
console.log(estudiantes);
});
}, []);
useEffect(() => {
window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
}, [estudiantes]);
return (
<Container fixed>
<Box mb={5} pt={2} sx={{ textAlign: 'center' }}>
<Button
startIcon={<PersonAddIcon />}
variant="contained"
color="primary"
size="medium"
onClick={crearEstudiante}
>
Crear Estudiantes
</Button>
<Box pl={25} pt={2} mb={2} sx={{ height: '390px', width: '850px', textAlign: 'center' }}>
<DataGrid
rows={estudiantes}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
components={{
Toolbar: CustomToolbar,
}}
checkboxSelection
//Store Data from the row in another variable
onSelectionModelChange={(id) => {
setSelectionModel(id);
const selectedIDs = new Set(id);
const selectedRowData = estudiantes.filter((row) => selectedIDs.has(row.id));
setEstudiantesData(selectedRowData);
}}
{...estudiantes}
/>
</Box>
<Button
startIcon={<ShoppingCartSharpIcon />}
variant="contained"
color="primary"
size="medium"
onClick={realizarPedidos}
>
Crear pedido
</Button>
</Box>
</Container>
);
};
I suspect that it's because this useEffect does not have a dependency array and is bring run on every render.
useEffect (() => {
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
})
Try adding a dependency array as follows:
useEffect (() => {
if (estudiantes && estudiantes.length>0)
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
},[estudiantes])
This will still set the localStorage to [] when it runs on the first render. But when the data is fetched and estudiantes is set, the localStorage value will be updated. So I've added a check to check if it's not the empty array.
Change the dependency array of this useEffect to []:
estudiantesRef.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
console.log(estudiantes)
})
}, []);
The data flow in your code is somewhat contradictory, so I modify your code, and it works fine.
You can also try delete or add button, it will modify firebase collection, then update local data.
You can click refresh button in codesandbox previewer (not browser) to observe the status of data update.
Here is the code fargment :
// Set value of `localStorage` to component state if it exist.
useEffect(() => {
const localStorageEstData = window.localStorage.getItem("estudiantes");
localStorageEstData && setEstudiantes(JSON.parse(localStorageEstData));
}, []);
// Sync remote data from firebase to local component data state.
useEffect(() => {
// Subscribe onSnapshot
const unSubscribe = onSnapshot(
collection(db, "usuarios", user.id, "estudiantes"),
(snapshot) => {
const remoteDataSource = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}));
console.info(remoteDataSource);
setEstudiantes(remoteDataSource);
}
);
return () => {
//unSubscribe when component unmount.
unSubscribe();
};
}, [user.id]);
// when `estudiantes` state update, `localStorage` will update too.
useEffect(() => {
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes));
}, [estudiantes]);
Here is the full code sample :
Hope to help you :)

Best way to use useMemo/React.memo for an array of objects to prevent rerender after edit one of it?

I'm struggling with s performance issue with my React application.
For example, I have a list of cards which you can add a like like facebook.
Everything, all list is rerendering once one of the child is updated so here I'm trying to make use of useMemo or React.memo.
I thought I could use React.memo for card component but didn't work out.
Not sure if I'm missing some important part..
Parent.js
const Parent = () => {
const postLike= usePostLike()
const listData = useRecoilValue(getCardList)
// listData looks like this ->
//[{ id:1, name: "Rose", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc },
// { id:2, name: "Helena", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc },
// { id: 3, name: "Gutsy", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc }]
const memoizedListData = useMemo(() => {
return listData.map(data => {
return data
})
}, [listData])
return (
<Wrapper>
{memoizedListData.map(data => {
return (
<Child
key={data.id}
data={data}
postLike={postLike}
/>
)
})}
</Wrapper>
)
}
export default Parent
usePostLike.js
export const usePressLike = () => {
const toggleIsSending = useSetRecoilState(isSendingLike)
const setList = useSetRecoilState(getCardList)
const asyncCurrentData = useRecoilCallback(
({ snapshot }) =>
async () => {
const data = await snapshot.getPromise(getCardList)
return data
}
)
const pressLike = useCallback(
async (id) => {
toggleIsSending(true)
const currentList = await asyncCurrentData()
...some api calls but ignore now
const response = await fetch(url, {
...blabla
})
if (currentList.length !== 0) {
const newList = currentList.map(list => {
if (id === list.id) {
return {
...list,
liked: true,
likedNum: list.likedNum + 1,
}
}
return list
})
setList(newList)
}
toggleIsSending(false)
}
},
[setList, sendYell]
)
return pressLike
}
Child.js
const Child = ({
postLike,
data
}) => {
const { id, name, avatarImg, image, bodyText, likedNum, liked } = data;
const onClickPostLike = useCallback(() => {
postLike(id)
})
return (
<Card>
// This is Material UI component
<CardHeader
avatar={<StyledAvatar src={avatarImg} />}
title={name}
subheader={<SomeImage />}
/>
<Image drc={image} />
<div>{bodyText}</div>
<LikeButton
onClickPostLike={onClickPostLike}
liked={liked}
likedNum={likedNum}
/>
</Card>
)
}
export default Child
LikeButton.js
const LikeButton = ({ onClickPostLike, like, likedNum }) => {
const isSending = useRecoilValue(isSendingLike)
return (
<Button
onClick={() => {
if (isSending) return;
onClickPostLike()
}}
>
{liked ? <ColoredLikeIcon /> : <UnColoredLikeIcon />}
<span> {likedNum} </span>
</Button>
)
}
export default LikeButton
The main question here is, what is the best way to use Memos when one of the lists is updated. Memorizing the whole list or each child list in the Parent component, or use React.memo in a child component...(But imagine other things could change too if a user edits them. e.g.text, image...)
Always I see the Parent component is highlighted with React dev tool.
use React.memo in a child component
You can do this and provide a custom comparator function:
const Child = React.memo(
({
postLike,
data
}) => {...},
(prevProps, nextProps) => prevProps.data.liked === nextProps.data.liked
);
Your current use of useMemo doesn't do anything. You can use useMemo as a performance optimization when your component has other state updates and you need to compute an expensive value. Say you have a collapsible panel that displays a list:
const [expand, setExpand] = useState(true);
const serverData = useData();
const transformedData = useMemo(() =>
transformData(serverData),
[serverData]);
return (...);
useMemo makes it so you don't re-transform the serverData every time the user expands/collapses the panel.
Note, this is sort of a contrived example if you are doing the fetching yourself in an effect, but it does apply for some common libraries like React Apollo.

How to make dynamic tab view screen in react native

I am trying to add tabs in my react native app. Here on tab i want to show all the data coming from an api. This gives a array of string. And when user click on any tab it should show respective data. Here is an example image.
Here below header I want to display the array of string coming from the ap.
Below the search field I want to display the data which is coming from different api.
I am using a package https://www.npmjs.com/package/react-native-tab-view . I am not sure how to achieve this with this.
Here is the code I have
import { TabView, SceneMap } from "react-native-tab-view";
import { connect } from "react-redux";
import { getAllState } from "../../actions/hubActions";
interface CommunityMemberProps {
getStates: () => void;
allStates: [];
}
const styles = StyleSheet.create({
scene: {
flex: 1,
},
});
const FirstRoute = () => (
<View style={[styles.scene, { backgroundColor: "#ff4081" }]} />
);
const SecondRoute = () => (
<View style={[styles.scene, { backgroundColor: "#673ab7" }]} />
);
const initialLayout = { width: Dimensions.get("window").width };
const CommunityMember = ({ getStates, allStates }: CommunityMemberProps) => {
useEffect(() => {
getStates();
}, []);
const [searchText, setSearchText] = useState<string>("");
const handleChangeText = (text: string) => {
setSearchText(text);
};
console.log("allStates", allStates); <-- this gives data ["India", "newDelhi"]
const [index, setIndex] = React.useState(0);
const [routes] = React.useState([
{ key: "First", title: "First" },
{ key: "Second", title: "Second" },
]);
const renderScene = SceneMap({
first: FirstRoute,
second: SecondRoute,
});
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={initialLayout}
/>
);
};
function mapStateToProps(state: any) {
return {
allStates: state.hub.allStates,
};
}
const mapDispatchToProps = (dispatch: any) => ({
getStates: () => dispatch(getAllState()),
});
export default connect(mapStateToProps, mapDispatchToProps)(CommunityMember);
Since you are using redux this can be easily done. Whenever you select a tab update the redux state with the tab selected. Keep the component same for all tabs and retrieve the tab from redux state using mapStateToProps and fetch data dynamically and useEffect or componentDidMount() hook.

Reactjs - Make the get request when loading the page

I know that in Angular there is ngOnInit. In Reactjs is there something similar to do the get request when loading the page?
I have a table and I need to get the request when I load the page for me to display the data in the table.
export default function ListAdverts() {
const columns = [
{
label: "Título",
accessor: "title",
width: "194px"
},
{
label: "Valor",
accessor: "price_cents",
width: "131px"
},
{
label: "Anunciante",
accessor: "title",
width: "203px"
},
{
label: "Categoria",
accessor: "title",
width: "158px"
}
];
const [dataAdverts, setdDataAdverts] = React.useState([]);
return (
<Table
rows={dataAdverts}
columns={columns}
/>
)
}
Data fetching for components is usually done inside the useEffect hook
export default function ListAdverts() {
const columns = ...
const [dataAdverts, setdDataAdverts] = React.useState([]);
// fetch data here
// runs only once because of empty dependency array
React.useEffect(() => {
let isCancelled = false
const fetchSomeData = async () => {
const data = await someApiRequest()
// only update state if component isn't unmounted
// if you try to update state on an unmounted component,
// React will throw an error
if (!isCancelled) {
setdDataAdverts(data)
}
}
fetchSomeData()
// cleanup
return () => {
isCancelled = true
}
}, [])
return (
<Table
rows={dataAdverts}
columns={columns}
/>
)
}

Resources