How do I invalidate data after refreshing the page? It doesn't seem to invalidate while it is supposed to. It still displays the old data even though something changed on the server-side.
I have this same problem when I use useMutation when posting data to the backend, the UI doesn't update even after using the QueryClient.
Below is my code:
const IncomeManager: React.FC<any> = (props) => {
const queryClient = new QueryClient();
const {isLoading, isError, isFetching, data}: QueryObserverResult = useQuery('typeIncomes', () => typesApi.getAllTypes());
const [refresh, setRefresh] = useState<boolean>(false);
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
//#ts-ignore
const handleClose = (): void => {
setIsModalVisible(false);
}
const refreshContent = async () => {
await queryClient.invalidateQueries('typeIncomes');
console.log("Content has been refreshed!!!");
}
return (
<View style={style.container}>
<View>
<AppText style={style.title}>{data ? data.length : 0} income types available</AppText>
</View>
<FixedButton
title={"plus"}
onPress={() => props.navigation.navigate(navConstants.ADDTYPE, {type: "incomes"})}
/>
{
isLoading || isFetching ? <PageActivityIndicator visible={isLoading || isFetching}/> :
<FlatList style={{width: "100%"}}
data={data}
renderItem={
({item}) => <CategoryItem
id={item.type_id}
title={item.title}
subTitle={item.description}
onLongPress={() => console.log("Very long press!")}
onPress={() => props.navigation.navigate(navConstants.EDITTYPE,
{
item: {
id: item.type_id,
title: item.title,
description: item.description
}
})
}
/>
}
keyExtractor={item => item.type_id}
refreshing={refresh}
onRefresh={async () => refreshContent()}
/>
}
</View>
);
}
export default IncomeManager;
const style = StyleSheet.create({
container: {
flex: 1,
width: "100%",
backgroundColor: constants.COLORS.secondary,
alignItems: "center"
},
title: {
color: constant.COLORS.lightGray,
paddingVertical: 10,
fontSize: 17,
marginBottom: 0
},
});
You are creating a new QueryClient every time your component renders by doing:
const queryClient = new QueryClient()
The queryClient holds your cache, which holds your data. There should be only one (like a redux store) - the one you create initially and then pass to the QueryClientProvider. To retrieve this Singleton instance, you can do:
const queryClient = useQueryClient()
it will give you the instance via React context. Invalidation on that queryClient should work. This is also how everything in the docs and all the example are set up.
Related
i'm using the tab view react native library to build a user account screen. my request is simple, how can i update the tab view content after an api call that fetches the user data?
function UserStoreScreen({ navigation, route }) {
const layout = useWindowDimensions();
const [index, setIndex] = React.useState(0);
const [userStore, setUserStore] = React.useState({});
const [routes] = React.useState([
{ key: "first", title: "Dressing" },
{ key: "second", title: "À propos" },
]);
const user = route.params;
// renders undefined
const FirstRoute = () => (
<>
<View style={styles.userContainer}>
<ListItem
image={`${IMAGES_BASE_URL}${userStore.photo}`}
title={`${userStore.username}`}
subTitle={`${userStore.store.length} Articles`}
/>
</View>
</>
);
const SecondRoute = () => (
<>
<View style={{ flex: 1, backgroundColor: "#ff4081" }} />
</>
);
const renderScene = SceneMap({
first: FirstRoute,
second: SecondRoute,
});
const getUser = async () => {
await axiosApi
.post("/getUserProducts", { user_id: user.user_id })
.then((response) => {
// didn't work since set state is async
setUserStore(response.data);
})
.catch((err) => {
console.log(err);
});
};
// Get store products
useEffect(() => {
getUser();
}, []);
return (
<Screen style={styles.screen}>
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={{ width: layout.width }}
/>
</Screen>
);
}
is there a way to make the content of the tab view updated after i receive the data from the api call?
Yes, there is a way to forcefully re-mount a component. To do that, we can use key props like this:
return (
<Screen style={styles.screen}>
<TabView
key={JSON.stringify(userStore)}
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={{ width: layout.width }}
/>
</Screen>
);
How does key props work? Every time a component is re-rendering, it will check whether the key value is the same or not. If it's not the same, then force a component to re-render.
In this case we will always check if userStore value has changed or not.
I have a functional component where I am using the MUI progress bar that I want to display but when the progress bar loads its still at the progress I set at the first step.
Also I am calling an API and processing the results in one of the functions. What am I doing wrong ?
function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) {
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant="determinate" {...props} />
</Box>
<Box sx={{ minWidth: 35 }}>
<Typography variant="body2" color="text.secondary">{`${Math.round(
props.value,
)}%`}</Typography>
</Box>
</Box>
);
}
export const Search = (props) => {
const { onSearchComplete } = props;
const [msgBox, setMsgBox] = useState(null);
const [loading, setLoading] = useState(false);
const [progress, setProgress] = useState(10);
const onSearch = async () => {
setLoading(true);
const emails = contacts
.filter(x => x.isChecked)
.map(item => item.emailAddress);
setProgress(30); //this is where I am manually setting the progress.
try {
const searchResults = await AppApi.search(emails);
let userList = [];
setProgress(70); // I want to manually set the percentage here
for (let i = 0; i < searchResults.length; i++) {
//processing the list here
}
onSearchComplete(userList); //passing on the results to another component
} catch (err) {
console.log({ err });
setMsgBox({ message: `${err.message}`, type: 'error' });
}
setLoading(false);
}
useEffect(() => {
onSearch();
}, [progress]);
return (
<Box>
{loading ? <LinearProgressWithLabel value={progress} />:
<Box>{msgBox && (<a style={{ cursor: 'pointer' }} onClick={() => setMsgBox(null)} title="Click to dismiss"><MessageBox type={msgBox.type || 'info'}>{msgBox.message}</MessageBox></a>)}</Box>}
</Box>
);
}
At the moment, your useEffect hook has the wrong dependencies. onSearch looks like it has two dependencies that could change - contacts and onSearchComplete, so the effect hook should actually be written as:
useEffect(() => {
onSearch();
}, [contacts, onSearchComplete]);
Depending on how onSearchComplete is defined, you might find that your effect re-runs more frequently than it should; you can either solve this by making onSearchComplete a callback:
const OtherComponent = () => {
const onSearchComplete = useCallback(userList => {
// ----- 8< -----
}, [...]);
}
Or wrapping the callback in a ref -
const Search = ({ onSearchComplete }) => {
const onSearchCompleteRef = useRef();
onSearchCompleteRef.current = onSearchComplete;
const onSearch = async () => {
// ----- 8< -----
onSearchCompleteRef.current(userList);
}
// Now you don't need onSearchComplete as a dependency
useEffect(() => {
onSearch();
}, [contacts]);
};
Edit
The reason you're not seeing the "updated" progress is because the processing of the results happens on the same render cycle as you updating the progress bar. The only way to get around that would be to introduce an artificial delay when you're processing the results:
setTimeout(() => {
onSearchCompleteRef.current();
setLoading(false);
}, 100);
I created a CodeSandbox demo to show this.
I have a useEffect() which should be called once but continuously it is calling the getStories method.
const [pageNo, setPageNo] = useState(1);
const [recordsPerPage, setrecordsPerPage] = useState(5);
const { jwt } = useSelector((state: StateParams) => state.account);
useEffect(() => {
if (jwt) {
dispatch(
getStories({ token: jwt, pageNo: pageNo, recordsPerPage: recordsPerPage })
);
}
}, []);
UPDATED: I feel flatlist is causing
<FlatList
style={{
marginTop: 14,
alignSelf: "stretch"
}}
data={Items}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
const renderItem = useCallback(({ item } : {id: string}) => {
console.log({item})
const Section = Components[item.id]
return (<Section {...props}/>)
}, [Items])
one place I seem to be stuck is on is being able to populate an array of objects, which are used for a FlatList later on.
I get this data from my FireStore – for each document, it will push the objects into ‘const DATA = []’
But when I run the ‘getUsers()’ inside of UseEffect, it only updates ‘DATA’ inside of the method, it doesn’t update the global variable.
Im new to react native, so im probably missing something important within my structure. Thanks for any assistance tho!
I need the format of DATA to look like this example:
My Code:
const MainScreen = () => {
const DATA = [];
const navigation = useNavigation()
const [selectedId, setSelectedId] = useState(null);
const usersCollectionRef = collection(db, "Posts");
const [Data, setData]= useState([]);
LogBox.ignoreLogs(['Setting a timer']);
LogBox.ignoreLogs(['AsyncStorage has been extracted']);
LogBox.ignoreAllLogs(true);
useEffect(() => {
getUsers();
console.log(DATA);
}, []);
const getUsers = async () => {
const data = await getDocs(usersCollectionRef);
data.forEach(doc =>{
const dataG = (doc.id, "=>", doc.data());
DATA.push({
id: doc.id,
title: dataG.status +" "+ dataG.Type,
status: dataG.status,
location: dataG.Location,
type: dataG.Type,
injured: dataG.Injured,
collar: dataG.Collar,
color: dataG.Colour,
username: dataG.username,
description: dataG.Description,
imagesrc: dataG.picture });
})
};
const Item = ({ item, onPress, backgroundColor, textColor }) => (
<View style={styles.ContentBox}>
<TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}>
<Text style={[styles.title, textColor]}>{item.title}</Text>
<Text style={styles.ContentText}>By: {item.username}</Text>
</TouchableOpacity>
<Image source = {{uri: item.imagesrc}}
style = {{ width: 200, height: 200, alignSelf:'center' }}/>
<Text style={styles.ContentText}>Animal: {item.type}</Text>
<Text style={styles.ContentText}>Location: {item.location}</Text>
<Text style={styles.ContentText}>Injured: {item.injured}</Text>
<Text style={styles.ContentText}>Colour: {item.color}</Text>
<Text style={styles.ContentText}>Has a Collar: {item.collar}</Text>
<Text style={styles.ContentText}>Description: {item.description}</Text>
</View>
);
const renderItem = ({ item }) => {
const backgroundColor = item.status === "lost" ? '#b80909' : '#098231';
const color = item.id === selectedId ? 'white' : 'white';
return (
<Item
item={item}
onPress={() => setSelectedId(item.id)}
backgroundColor={{ backgroundColor }}
textColor={{ color }}
/>
);
};
const PostScreen = () =>{
navigation.navigate('PostScreen');
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.MainFeed}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
extraData={selectedId}
/>
</View>
)
instead of pushing data in a variable and then updating the state, you can do it like this directly -
setData([...Data,{
id: doc.id,
title: dataG.status +" "+ dataG.Type,
status: dataG.status,
location: dataG.Location,
type: dataG.Type,
injured: dataG.Injured,
collar: dataG.Collar,
color: dataG.Colour,
username: dataG.username,
description: dataG.Description,
imagesrc: dataG.picture
}])
The answer is that after doing DATA.push(...), I needed to setData(DATA).
I don't know why I can't make it work but I just want to edit the todo from the list and save it onBlur (when I press outside the box). I've made this work before but I think I got brain freeze or something. I deleted my attempts so the functions are empty now. Can someone nudge me in the right direction or just fill in the blanks? Thank you
UPDATE: so I want to press the todo that I've added to the list (the textinput) and then EDIT the already present todo, THEN save it to the list again!
const TEST = () => {
const [todo, setTodo] = useState("")
const [todoList, setTodoList] = useState([])
const onChangeHandler = (text) =>{
setTodo(text)
}
const saveTodo = () =>{
setTodoList([...todoList , {title: todo, key:`${Math.random()}`}])
setTodo("")
}
const onChangeTodo = () =>{
//UPDATE HERE
}
const saveonChangeTodo = () =>{
//SAVE HERE
}
return(
<View style={{flex:1, backgroundColor: "beige", justifyContent: "center", alignItems: "center", paddingTop: 300,}}>
<TextInput
placeholder="Write todo here"
style={{backgroundColor:"white", padding: 20, width: 300,}}
value={todo}
onChangeText={text=>onChangeHandler(text)}
onBlur={saveTodo}
/>
<FlatList
data={todoList}
renderItem={({item}) => {
return(<TextInput style={{borderColor: "black", borderWidth: 2, width: 200, padding: 20, margin: 10}}
value={item.title}
onChangeText={text=>onChangeTodo(text, item)}
onBlur={saveonChangeTodo}
/>
)
}}/>
Change your onChangeTodo as below. No need to have saveonChangeTodo as it is already supported by onChangeTodo.
const onChangeTodo = (text, item) => {
setTodoList((todos) =>
todos.map((todo) =>
todo.key === item.key ? { ...todo, title: text } : todo
)
);
};
Code Sandbox