React Query: InvalidateQuery not working to update users list - reactjs

I have a simple app that has a form and list. Currently, I am using query client.InvalidateQueries to update the users' list after submitting the form. As the documentation says, using InvalidateQuery will trigger refetching, but somehow I had not seen an update to the list after adding users. Am I missing something?
Add User
import React, { useState } from 'react';
import { useFormik } from 'formik';
import Input from '../../elements/Input/Input';
import * as Yup from 'yup';
import { QueryClient, useMutation } from 'react-query';
import axios from 'axios';
const queryClient = new QueryClient();
const CreateItemView = () => {
function gen4() {
return Math.random().toString(16).slice(-4);
}
function generateID(prefix) {
return (prefix || '').concat([gen4(), gen4(), gen4(), gen4(), gen4(), gen4(), gen4(), gen4()].join(''));
}
const mutation = useMutation(
(formData) => {
axios.post('http://localhost:8000/users', formData).then((response) => console.log(response));
},
{
onSuccess: () => {
queryClient.invalidateQueries('users');
},
},
);
const [data, setData] = useState([]);
const initialValues = {
id: '',
name: '',
email: '',
channel: '',
};
const onSubmit = (values, { resetForm }) => {
setData([...data, values]);
const ID = generateID().toString();
values.id = ID;
mutation.mutate(values);
resetForm();
};
const validationSchema = Yup.object({
name: Yup.string().required('Required!'),
email: Yup.string().email('Invalid format').required('Required!'),
channel: Yup.string().required('Required!'),
});
const formik = useFormik({
initialValues,
onSubmit,
validationSchema,
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<Input type={'text'} name={'name'} id={'name'} label={'Name'} formik={formik} />
<Input type={'email'} name={'email'} id={'email'} label={'Email'} formik={formik} />
<Input type={'text'} name={'channel'} id={'channel'} label={'channel'} formik={formik} />
<button type="submit">Submit</button>
</form>
</div>
);
};
export default CreateItemView;
User's list
import React from 'react';
import ListView from './ListView';
import { useQuery } from 'react-query';
import axios from 'axios';
const getUsers = async () => {
const response = await axios.get('http://localhost:8000/users');
return response.data;
};
const ListContainer = () => {
const { data, isLoading, isFetching } = useQuery('users', getUsers);
console.log('list', data);
return <div>{isFetching ? 'loading...' : <ListView dataSource={data} />}</div>;
};
export default ListContainer;

You have to return the fetch function in the mutation. The onSuccess handler will fire when the promise is resolved.
const mutation = useMutation(
formData => {
return axios.post('http://localhost:8000/users', formData)
.then((response) => console.log(response));
},
{
onSuccess: () => {
queryClient.invalidateQueries('users');
},
},
);

i think the solution for your problem is to replace this :
onSuccess: () => {
queryClient.invalidateQueries('users');
},
By this :
onSettled:() => {
queryClient.invalidateQueries('users');
},

you should use the same instance of queryClient from the root of your app which is accessible via the useQueryClient() hook.
Hence you should be doing const queryClient = useQueryClient() instead of generating new instance with const queryClient = new QueryClient().

Related

Function setDoc() called with invalid data. Unsupported field value: a custom UserImpl object (found in field owner in document CreatedClasses)

This is the first time I'm asking a question here and also a newbie to coding. I'm trying to clone google classroom.
I am trying to use firestore to make a db collection when creating the class. But when I click create it doesn't create the class and create the db in firestore. It shows that the setDoc() function is invalid. Im using firestore version 9 (modular)
Here is my Form.js file. (The firestore related code is also included here)
import { DialogActions, TextField , Button} from "#material-ui/core"
import React, {useState} from 'react'
import { useLocalContext, useAuth } from '../../../context/AuthContext'
import { v4 as uuidV4 } from 'uuid'
import { db} from '../../../firebase'
import { collection, doc, setDoc } from "firebase/firestore"
const Form = () => {
const [className, setClassName] = useState('')
const [Level, setLevel] = useState('')
const [Batch, setBatch] = useState('')
const [Institute, setInstitute] = useState('')
const {setCreateClassDialog} = useLocalContext();
const {currentUser} = useAuth();
const addClass = async (e) => {
e.preventDefault()
const id = uuidV4()
// Add a new document with a generated id
const createClasses = doc(collection(db, 'CreatedClasses'));
await setDoc(createClasses, {
owner:currentUser,
className: className,
level: Level,
batch: Batch,
institute: Institute,
id: id
}).then (() => {
setCreateClassDialog(false);
})
}
return (
<div className='form'>
<p className="class__title">Create Class</p>
<div className='form__inputs'>
<TextField
id="filled-basic"
label="Class Name (Required)"
className="form__input"
variant="filled"
value={className}
onChange={(e) => setClassName(e.target.value)}
/>
<TextField
id="filled-basic"
label="Level/Semester (Required)"
className="form__input"
variant="filled"
value={Level}
onChange={(e) => setLevel(e.target.value)}
/>
<TextField
id="filled-basic"
label="Batch (Required)"
className="form__input"
variant="filled"
value={Batch}
onChange={(e) => setBatch(e.target.value)}
/>
<TextField
id="filled-basic"
label="Institute Name"
className="form__input"
variant="filled"
value={Institute}
onChange={(e) => setInstitute(e.target.value)}
/>
</div>
<DialogActions>
<Button onClick={addClass} color='primary'>
Create
</Button>
</DialogActions>
</div>
)
}
export default Form
And also (I don't know whether this is helpful but my context file is below)
import React, { createContext, useContext, useEffect, useState } from "react";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
signOut,
GoogleAuthProvider,
signInWithPopup
} from "firebase/auth";
import { auth } from "../firebase";
const AuthContext = createContext();
const AddContext = createContext()
export function useAuth() {
return useContext(AuthContext);
}
export function useLocalContext(){
return useContext(AddContext)
}
export function ContextProvider({children}){
const [createClassDialog,setCreateClassDialog] = useState(false);
const [joinClassDialog, setJoinClassDialog] = useState(false);
const value = { createClassDialog, setCreateClassDialog, joinClassDialog, setJoinClassDialog };
return <AddContext.Provider value={value}> {children} </AddContext.Provider>;
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true)
function signup(email, password) {
return createUserWithEmailAndPassword(auth,email, password);
}
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
function logout() {
return signOut(auth);
}
function resetPassword(email) {
return auth.sendPasswordResetEmail(email)
}
function googleSignIn() {
const googleAuthProvider = new GoogleAuthProvider();
return signInWithPopup(auth, googleAuthProvider);
}
function updateEmail(email) {
return currentUser.updateEmail(email)
}
function updatePassword(password) {
return currentUser.updatePassword(password)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged( auth, (user) => {
setCurrentUser(user);
setLoading(false)
});
return () => {
unsubscribe();
};
}, []);
return (
<AuthContext.Provider
value={{ currentUser, login, signup, logout, googleSignIn, resetPassword,updateEmail, updatePassword }}
>
{!loading && children}
</AuthContext.Provider>
);
}
The console error message:
Try something like this, excluding the collection function from setting the document.
// Add a new document with a generated id
await setDoc(doc(db, 'CreatedClasses'), {
owner:currentUser,
className: className,
level: Level,
batch: Batch,
institute: Institute,
id: classId
}).then (() => {
setCreateClassDialog(false);
})

How to save values sent via post request in redux-toolkit async thunk

I'm making a react component which has two input fields.One have the key : type,another the key: range.The problem is that when i submit the data i dont know how to save it as an array or something,to stack more pairs of information,because i need to display a progress bar based on the information from the input field. Could you help me please?
Here is my Slice:
export const skillSlice = createSlice({
name: "skill",
initialState: {
name:'',
range:null
},
reducers: {
setSkill: (state, action) => {
console.log("action", action.payload);
state.name = action.payload?.name;
state.range = action.payload?.range;
}
}
});
export const addNewSkill = createAsyncThunk(
'skills/addNewSkill',
async (_,{rejectWithValue,dispatch}) =>{
try{
const response = await fetch('/api/skills',{
method:'POST',
headers:{
'Content-name' : 'application/json',
},
});
if(!response.ok){
throw new Error('Can\'t add skill. Server error')
}
const data = await response.json();
dispatch(setSkill(data))
}catch(error){
return rejectWithValue(error.message);
}
}
)
export const fetchSkills = createAsyncThunk(
'skills/fetchSkills',
async (_, {rejectWithValue}) => {
try{
const response = await fetch('/api/skills',{
method:'GET',
})
// console.log(response)
if(!response.ok){
throw new Error ('Server Error!');
}
const data = await response.json();
// console.log(data)
return data;
} catch(error){
return rejectWithValue(error.message);
}
}
);
const { setSkill } = skillSlice.actions;
export const selectSkill = (state) => state?.skill;
export default skillSlice.reducer;
And here is the component:
import React, { useState,useEffect } from 'react'
import { Formik, Form, useFormik } from 'formik'
import * as Yup from 'yup'
import FormikControl from '../Form/FormikControl'
import DisplayFormikState from '../Form/DisplayFormikState.js'
import { useDispatch, useSelector } from 'react-redux'
import { addNewSkill,fetchSkills,selectSkill } from '../../features/skills/skillSlice'
const Skills = () =>{
const dispatch = useDispatch();
const [skill, setSkills] = useState({
name: '',
range: null
});
useEffect(()=>{
dispatch(fetchSkills());
},[dispatch])
const userInfo = useSelector(selectSkill);
const skillList = useSelector(state => state.skillState)
const { status, error } = useSelector(state => state.skillState)
const handleChange = (e) => {
const { name, value } = e.target;
setSkills({ ...skill, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addNewSkill(skill));
};
const formik = useFormik({
// initialValues:{
// name: skill.name,
// range: skill.range
// },
validationSchema:Yup.object({
}),
})
return(
<>
<section id="skills">
<h1 className='SkillSection'>Skills</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="type">Type</label>
<input
id='type'
name='name'
type='text'
placeholder='Enter skill name'
onChange={handleChange}
// value={formik.values.name}
/>
</div>
<div>
<label htmlFor="level">Level</label>
<input
id='level'
type='text'
name='range'
placeholder='Enter range'
onChange={handleChange}
// value={formik.values.range}
/>
</div>
<button type='submit'>Submit</button>
</form>
</section>
</>
)
}
export default Skills
In the above code the initial state isn't an array because when i tried to push values to it i got undefined,so,i left the working state not to get confused. Thanks in advance!

React state update memory leak due to an unmounted component

My app uses firebase to authenticate. During the sign-in process, I get the "Can't perform a React state update on an unmounted component" and it recommends using a cleanup function in a useEffect. I thought I was cleaning up the function in async function with the
finally {
setLoading(false);
}
Any help would be appreciated. Code below:
import React, { useState, useContext } from "react";
import styled from "styled-components/native";
import { Image, Text, StyleSheet } from "react-native";
import { FirebaseContext } from "../context/FirebaseContext";
import { UserContext } from "../context/UserContext";
export default function SignInScreen() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const firebase = useContext(FirebaseContext);
const [_, setUser] = useContext(UserContext);
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
setLoading(false);
}
};
return (
<Container>
<Main>
<Text style={styles.welcomeText}>Welcome</Text>
</Main>
<Auth>
<AuthContainer>
<AuthTitle>Email Address</AuthTitle>
<AuthField
autoCapitalize="none"
autoCompleteType="email"
autoCorrect={false}
autoFocus={true}
keyboardType="email-address"
onChangeText={(email) => setEmail(email.trim())}
value={email}
/>
</AuthContainer>
<AuthContainer>
<AuthTitle>Password</AuthTitle>
<AuthField
autoCapitalize="none"
autoCompleteType="password"
autoCorrect={false}
autoFocus={true}
secureTextEntry={true}
onChangeText={(password) => setPassword(password.trim())}
value={password}
/>
</AuthContainer>
</Auth>
<SignInContainer onPress={signIn} disabled={loading}>
{loading ? <Loading /> : <Text style={styles.text}>Sign In</Text>}
</SignInContainer>
<HeaderGraphic>
<Image
source={require("../images/heritage-films-logo.png")}
style={{ height: 150, width: 300, resizeMode: "contain" }}
/>
</HeaderGraphic>
</Container>
);
}
You should check if the component is still mounted before calling setState in some way. It's a typical React leakage issue. You can implement isMounted variable with useRef hook for that, despite the fact that the authors of React call it an anti-pattern, since you should cancel your async routines when the component unmounts.
function Component() {
const isMounted = React.useRef(true);
React.useEffect(() => () => (isMounted.current = false), []);
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
isMounted.current && setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
isMounted.current && setLoading(false);
}
};
}
Or another a bit magic way:
import { useAsyncCallback, E_REASON_UNMOUNTED } from "use-async-effect2";
import { CanceledError } from "c-promise2";
export default function SignInScreen() {
//...
const signIn = useAsyncCallback(function*() {
setLoading(true);
try {
yield firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = yield firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
setLoading(false);
} catch (error) {
CanceledError.rethrow(error, E_REASON_UNMOUNTED);
setLoading(false);
alert(error.message);
}
}, []);
return (<YourJSX onPress={signIn}>);
}

handling multiple input in a array of objects

I am trying to handle multiple inputs in a array of objects and each object I am mapping to the input field in a form .Now I am getting empty array I want to know where I am doing wrong .I am also sending post axios request and the value I am getting in input text fields through urlParmas using hooks.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { useLocation } from "react-router-dom";
import axios from "axios";
import { Button, TextField } from "#material-ui/core";
const MyComponent = () => {
const [sale, setSale] = useState("");
const [district, setDistrict] = useState("");
const obj = [
{
key: 1,
label: "Sales office",
handleChange: (e) => {
setSale(e.target.value);
},
val: sale,
},
{
key: 2,
label: "Sales district",
handleChange: (e) => {
setDistrict(e.target.value);
},
val: sale,
},
];
const [object, setObject] = useState(obj);
const handleSubmit = () => {
axios
.post("localhots:8000", {
sale,
district,
})
.then(() => {});
setSale("");
setDistrict("");
};
const handleComment = (e, item) => {
e.preventDefault();
let result = object;
result = result.map((el) => {
if (el.name === item.name) el.name = e.target.value;
return el;
});
console.log(result);
setObject(result);
};
const { search } = useLocation();
const urlParams = Object.fromEntries([...new URLSearchParams(search)]);
useEffect(() => {
const { salesoff } = urlParams;
setSale(salesoff);
}, []);
object.map((item, index) => {
return (
<div>
<form
onSubmit={(e) => {
e.target.reset();
}}
>
<TextField
name={item.name}
value={item.sale}
label={item.label}
onChange={handleChange}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
onClick={() => handleSubmit()}
>
Submit
</Button>
</form>
</div>
);
});
};
export default MyComponent;

Test login component with react hooks,

I am trying to test the Login.js component of my app.
I am actually trying to test 3 things:
When the values in the inputs field are not filled the disabled prop is true.
When the values in the inputs field are filled the disabled prop is false.
When the values in the inputs field are filled the login-button should call handleSubmit.
My test is failing in the second test and regarding the third test, I am not sure how to approach this test.
If someone has a good idea what I am doing wrong will be appreciated.
//Login.js
import React, { useState } from 'react';
import axios from 'axios';
import useStyles from '../styles/LoginStyle';
import { useStatefulFields } from '../../hooks/useStatefulFields';
function Login({ success }) {
const [values, handleChange] = useStatefulFields();
const [error, setError] = useState();
const handleSubmit = async () => {
await axios.post('www.example.com', {values}, {key})
.then((res) => {
if (res.data.success) {
success();
} else {
setError(res.data.error);
}
})
};
return (
<div>
<p className={classes.p}>Personalnummer</p>
<input
type="number"
className={classes.input}
onChange={handleChange}
name="personal_number"
title="personal_number"
/>
<p className={classes.p}>Arbeitsplatz</p>
<input
type="number"
onChange={(e) => handleChange(e)}
name="workplace"
title="workplace"
className={classes.input}
/>
<ColorButton
id="login-button"
className={
(values.password && values.personal_number && values.workplace)
? classes.button
: classes.buttonGray
}
disabled={!values.password && !values.personal_number && !values.workplace}
size="large"
onClick={
values.password && values.personal_number && values.workplace
? handleSubmit
: () => {}
}
>
</ColorButton>
</div>
)
}
//useStatefulFields.js
import { useState } from 'react';
export function useStatefulFields() {
const [values, setValues] = useState({});
const handleChange = (e) => {
setValues({
...values,
[e.target.name]: e.target.value,
});
};
return [values, handleChange];
}
//Login.test.js
import React from 'react';
import { shallow } from 'enzyme';
import Login from '../components/Workers/Login';
let wrapper;
beforeEach(() => {
wrapper = shallow(<Login success={() => {}} />);
});
test('check if login button is disabled', () => {
const loginButton = wrapper.find('#login-button');
expect(loginButton.prop('disabled')).toEqual(true);
});
test('check if login button is not disabled', () => {
const loginButton = wrapper.find('#login-button');
wrapper.find('input[name="workplace"]').simulate('change', { target: { value: 'test' } });
wrapper.find('input[name="password"]').simulate('change', { target: { value: 'test' } });
wrapper.find('input[name="personal_number"]').simulate('change', { target: { value: 'test' } });
expect(loginButton.prop('disabled')).toEqual(false);
});
Pass name and value once simulate the change.
test('check if login button is not disabled', () => {
let loginButton = wrapper.find('#login-button');
wrapper.find('input[name="workplace"]').simulate('change', { target: { name:'workplace', value: 'test' } });
wrapper.find('input[name="password"]').simulate('change', { target: { name:'password', value: 'test' } });
wrapper.find('input[name="personal_number"]').simulate('change', { target: {name: 'personal_number', value: 'test' } });
loginButton = wrapper.find('#login-button');
loginButton.props().onClick(); // for handleSubmit
expect(loginButton.prop('disabled')).toEqual(false);
});
Make sure that you have the input field for the password also that I am not seeing in your question. Otherwise, the test will fail again.

Resources