How to get data when language changed in react V18.2? - reactjs

I have problem with fetching data when language changed. I tried a lot of things that I found from Stack overflow, but unfortunately it just changing the direction and it didn't fetch the data based on language changed.
I fetching data with a custom hook and call it inside my functional component. let me share the code that I write.
Note: I'm using I18nextLng for translation.
App.js
import { RouterProvider } from "react-router-dom";
import Loading from './components/loading';
import routes from './routes/routes';
import { useEffect } from "react";
import i18n from "./utils/i18n";
function App() {
useEffect(() => {
let cleanup = true;
if (cleanup) {
i18n.on('languageChanged', (local) => {
let direction = i18n.dir(local);
document.body.dir = direction;
})
}
return () => {
cleanup = false;
};
}, []);
return (
<RouterProvider router={routes} fallbackElement={<Loading />} />
)
}
export default App;
LanguageSwitcher.js
import { useTranslation } from "react-i18next";
const LanguageSwitcher = () => {
const { i18n } = useTranslation();
return (
<select
className="form-select-sm rounded-pill text-center"
aria-label="Default select example"
value={i18n.language}
onChange={(e) =>
i18n.changeLanguage( e.target.value)
}
>
<option value="en">English</option>
<option value="fa">دری</option>
</select>
);
}
export default LanguageSwitcher;
Internships.js
import Image from "react-bootstrap/Image"
import { useFetchWebsiteData } from "../../hooks/website/useFetchWebsiteData";
import Loading from '../../components/loading'
import { useEffect, useState } from "react";
const Internships = () => {
let lang = localStorage.getItem("i18nextLng")
const { data, isLoading } = useFetchWebsiteData("getInternship", lang);
console.log("language changed", language);
return !isLoading ? (
<div className="container-fluid news-wrapper">
<div className="container">
<div className="row py-5">
<div className="col-md-12">
<div className="col-md-8">
<h4 className="title mb-4">{data?.title}</h4>
<p className="sub-title">{data?.content}</p>
</div>
<div className="col-md-2 text-center">
<Image
src={require("../../images/internships.png")}
fluid={true}
/>
</div>
</div>
</div>
</div>
</div>
) : (
<Loading />
);
}
export default Internships;
useFetchWebsiteData.js (Custom hook for fetching data)
import { useState, useEffect } from "react";
import { axiosPublic } from "../../utils/axios";
export const useFetchWebsiteData = (url,lang) => {
const [data, setData] = useState({});
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
// const lang = localStorage.getItem("i18nextLng");
console.log('lang inside hook', lang)
useEffect(() => {
// const controller = new AbortController()
const fetchData = async () => {
setIsLoading(true);
await axiosPublic
.get(url, {
headers: { lang: lang === "fa" ? "dr" : "en" },
// signal: controller.signal,
})
.then((response) => {
if (response.status === 200) {
if (lang === "en") {
setIsLoading(false);
response.data.data.en.map((d) => {
let title = d.title;
let content = d.content;
return setData({ title: title, content: content });
});
}
if (lang === "fa") {
setIsLoading(false);
console.log("fa intern", response.data.data.dr)
response.data.data.dr.map((d) => {
let title = d.title;
let content = d.content;
return setData({ title: title, content: content });
});
setIsLoading(false);
}
} else {
setIsError(true);
}
})
.catch((error) => {
setIsLoading(false);
setIsError(true);
console.error(error.message);
});
};
fetchData();
// return () => {
// controller.abort()
// };
}, [url, lang]);
return { data, isLoading, isError };
};
I really appreciate for your helping.

Related

Redux updating state with null before actual data

I am dispatching an action that is supposed to take an input from the user and store it in a database. However, when I inspect my posts state in redux after the action is dispatched, there is a null value appended to the state array before the actual post. This is preventing me from working with the actual data in the posts array. Basically I'm wondering how to prevent null from being appended each time I dispatch a new post. Here are the relevant code snippets and images.
Post Reducer:
import { enableAllPlugins, produce } from 'immer';
enableAllPlugins();
const initialState = {
posts: [],
loading: false,
error: false,
uploading: false,
};
const postReducer = produce((draftstate, action = {}) => {
switch (action.type) {
case 'UPLOAD_START':
draftstate.loading = true;
draftstate.error = false;
case 'UPLOAD_SUCCESS':
draftstate.posts.push(action.data);
draftstate.uploading = false;
draftstate.error = false;
case 'UPLOAD_FAIL':
draftstate.uploading = false;
draftstate.error = true;
default:
return draftstate;
}
}, initialState);
export default postReducer;
Upload Post action:
export const uploadPost = (data) => async (dispatch) => {
dispatch({ type: 'UPLOAD_START' });
try {
const newPost = await UploadApi.uploadPost(data);
console.log('new post before', newPost);
dispatch({ type: 'UPLOAD_SUCCESS', data: newPost.data });
} catch (error) {
console.log(error);
dispatch({ type: 'UPLOAD_FAIL' });
}
};
Share Post code:
import React, { useState, useRef } from "react";
import ProfileImage from "../../img/profileImg.jpg";
import "./PostShare.css";
import { UilScenery } from "#iconscout/react-unicons";
import { UilPlayCircle } from "#iconscout/react-unicons";
import { UilLocationPoint } from "#iconscout/react-unicons";
import { UilSchedule } from "#iconscout/react-unicons";
import { UilTimes } from "#iconscout/react-unicons";
import { useSelector, useDispatch } from "react-redux";
import { uploadImage, uploadPost } from "../../actions/uploadAction";
const PostShare = () => {
const loading = useSelector((state) => state.postReducer.uploading);
const [image, setImage] = useState(null);
const imageRef = useRef();
const desc = useRef();
const dispatch = useDispatch();
const { user } = useSelector((state) => state.authReducer.authData);
// handle Image Change
const onImageChange = (event) => {
if (event.target.files && event.target.files[0]) {
let img = event.target.files[0];
setImage(img);
}
};
const reset = () => {
setImage(null);
desc.current.value = "";
};
const handleSubmit = async (e) => {
e.preventDefault();
const newPost = {
userId: user._id,
desc: desc.current.value,
};
if (image) {
const data = new FormData();
const filename = Date.now() + image.name;
data.append("name", filename);
data.append("file", image);
newPost.image = filename;
console.log(newPost);
try {
dispatch(uploadImage(data));
} catch (error) {
console.log(error);
}
}
dispatch(uploadPost(newPost));
reset();
};
return (
<div>
<div className="PostShare">
<img src={ProfileImage} alt="" />
<div>
<input
ref={desc}
required
type="text"
placeholder="What's happening"
/>
<div className="postOptions">
<div
className="option"
style={{ color: "var(--photo)" }}
onClick={() => imageRef.current.click()}
>
<UilScenery />
Photo
</div>
<div className="option" style={{ color: "var(--video" }}>
<UilPlayCircle />
Video
</div>
<div className="option" style={{ color: "var(--location)" }}>
<UilLocationPoint />
Location
</div>
<div className="option" style={{ color: "var(--shedule)" }}>
<UilSchedule />
Schedule
</div>
<button
className="button ps-button"
onClick={handleSubmit}
disabled={loading}
>
{loading ? "Uploading..." : "Share"}
</button>
<div style={{ display: "none" }}>
<input
type="file"
name="myImage"
ref={imageRef}
onChange={onImageChange}
/>
</div>
</div>
{image && (
<div className="previewImage">
<UilTimes onClick={() => setImage(null)} />
<img src={URL.createObjectURL(image)} alt="" />
</div>
)}
</div>
</div>
</div>
);
};
export default PostShare;
I would be glad to provide any other details if that helps.
Update with other portions of code:
Dispatcher of RETRIEVING_SUCCESS:
import * as PostApi from '../api/PostRequest';
export const getTimelinePosts = (id) => async (dispatch) => {
dispatch({ type: 'RETRIEVING_START' });
try {
const { data } = await PostApi.getTimelinePosts(id);
dispatch({ type: 'RETRIEVING_SUCCESS', data: data });
} catch (error) {
dispatch({ type: 'RETRIEVING_FAIL' });
console.log(error);
}
};
getTimelinePosts usage:
import React, { useEffect } from 'react';
import './Posts.css';
import { PostsData } from '../../Data/PostsData';
import { useDispatch, useSelector } from 'react-redux';
import { getTimelinePosts } from '../../actions/postAction';
import Post from '../Post/Post';
const Posts = () => {
const dispatch = useDispatch();
const { user } = useSelector((state) => state.authReducer.authData);
let { posts, loading } = useSelector((state) => state.postReducer);
console.log('posts content', posts);
useEffect(() => {
dispatch(getTimelinePosts(user._id));
}, []);
return (
<div className="Posts">
{/* {posts.map((post, id) => {
return <Post data={post} id={id}></Post>;
})} */}
</div>
);
};
export default Posts;
in postReducer, let's remove the default on the switch statement, we don't need it on reducer because other actions will come here and the code make all states return the initial state.

Uncaught (in promise) IntegrationError: Invalid value for stripe.confirmCardPayment intent secret: value should be a client_secret string

I'm working on a project where I am trying to integrate stripe. I'm trying to get payments to go through with one click of the placeOrderHandler button. Currently, when I click this button I get an error stating: Uncaught (in promise) IntegrationError: Invalid value for stripe.confirmCardPayment intent secret: value should be a client_secret string, and then if I click this button again it goes through and console.log(client_secret) provides the string associated with it. I'm unsure why this is happening, and I would really appreciate any help or advice on how to fix this. Thank you!
PlaceOrderScreen.js
import React, { useState, useEffect} from 'react';
import { useDispatch, useSelector, createSelector } from 'react-redux';
import {createOrder} from '../actions/orderActions';
import {listProducts} from '../actions/productActions';
import { ORDER_CREATE_RESET } from '../constants/orderConstants';
import { PAYMENT_SUCCESS } from '../constants/paymentConstants';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import { loadStripe } from "#stripe/stripe-js";
import { Elements } from "#stripe/react-stripe-js";
import {CardElement, PaymentElement, CardNumberElement, CardExpiryElement, CardCvcElement, useStripe, useElements} from '#stripe/react-stripe-js';
import {payment, paymentInfo} from '../actions/paymentActions';
import LoadingSpinner from "../components/LoadingSpinner";
export default function PlaceOrderScreen(props) {
const cart = useSelector((state) => state.cart);
const userSignin = useSelector((state) => state.userSignin);
const { userInfo } = userSignin;
const paymentCreate = useSelector((state) => state.paymentCreate);
const { client_secret } = paymentCreate;
const paymentInformation = useSelector((state) => state.paymentInformation);
const {loadingPayment} = paymentInformation;
const userId = userInfo._id;
const orderCreate = useSelector((state) => state.orderCreate);
const { loading, success, error, order } = orderCreate;
const [loadedPay, setLoadedPay] = useState('');
const dispatch = useDispatch();
const productList = useSelector((state) => state.productList);
const { products } = productList;
useEffect(() =>{
dispatch(listProducts({}));
}, [dispatch]);
const elements = useElements();
const stripe = useStripe();
const placeOrderHandler = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
console.log('Stripe.js has not yet loaded.')
return;
}
const paymentMethodType = 'card';
const currency = 'usd';
const {error: backendError} = dispatch(payment(paymentMethodType, currency, cart, userId, ));
if (backendError) {
//addMessage(backendError.message);
console.log(backendError)
return;
}
console.log('Client secret returned')
console.log(client_secret)
const {error: stripeError, paymentIntent} = await stripe.confirmCardPayment(client_secret,
{
payment_method: {
card: elements.getElement(CardNumberElement),
billing_details: {
name: cart.billingAddress.fullName,
},
},
},
)
if (stripeError) {
console.log('stripeError')
return;
}
cart.paymentId = paymentIntent.id
dispatch(createOrder({ ...cart, orderItems: cart.cartItems }))
const orderId = order._id;
setLoadedPay(false)
.then(dispatch(paymentInfo(orderId)))
setLoadedPay(true)
};
const [isLoading, setIsLoading] = useState(false);
var loaded = false;
useEffect(() => {
if (success ) {
props.history.push(`/order/${order._id}`)
dispatch({ type: ORDER_CREATE_RESET })
}
}, [dispatch, order, success]);
return (
<div>
{loadedPay === false ? (<LoadingBox></LoadingBox>) : (
<div>
{loadingPayment & loadingShipping ? (
<LoadingBox></LoadingBox>
) : (
<div>
{isLoading ? <LoadingSpinner /> :
<div>
<div className="row top">
<div className="col-2">
<ul>
<li>
<div className="card card-body">
<h2>Payment</h2>
<div>
<h1>Card</h1>
<form id="payment-form" >
<label htmlFor="card">Card</label>
{/*<CardElement id="card" />*/}
<CardNumberElement id="card"/>
<CardExpiryElement id="card"/>
<CardCvcElement id="card"/>
</form>
</li>
<li>
<button
type="button"
onClick={placeOrderHandler}
disabled={isLoading && cart.cartItems.length === 0}
className="primary block"
>
Place Order
</button>
</li>
{loading && <LoadingBox></LoadingBox>}
{error && <MessageBox variant="danger">{error}</MessageBox>}
</ul>
</div>
</div>
</div>
</div>
}
</div>
)}
</div>
)}
</div>
);
}
paymentReducer.js
export const paymentCreateReducer = (state = {client_secret:[]}, action) => {
switch (action.type) {
case PAYMENT_REQUEST:
return { ...state, loading: true };
case PAYMENT_SUCCESS:
return { ...state, loading: false, client_secret: action.payload};
case PAYMENT_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
Backend
stripe.js
....
stripeRouter.get(
'/config',
expressAsyncHandler((req, res) => {
res.send({
publishableKey: '',
});
}));
stripeRouter.post(
'/pay',
expressAsyncHandler(async (req, res) => {
const userId = req.body.userId
const user = await User.findById(userId);
stripeClient.paymentIntents.create({
payment_method_types: [req.body.paymentMethodType],
amount: amount,
currency: req.body.currency,
})
.catch(function(err) {
console.log("There was an error"})
.then(async function(paymentIntents) {
const clientSecret = paymentIntents.client_secret
const client_secret = clientSecret.toString()
res.status(201).send(client_secret)
})
}));

ReactJS Error when using map function, cannot read properties of undefined

I'm trying to make a sport/tinder like app for a school project from a friend of mine. It came together well on my localhost, but for him it was a requirement to host it online. Not really a professional in hosting, but I was a bit familiar with Heroku. I used a client and a server side for my application, so I build the client side and put it into the server side folder. This server side is hosted on the Heroku page. But whenever I try to login, it won't work and I get this error message in my console.
TypeError: Cannot read properties of undefined (reading 'map')
The error says it is caused by this line of code.
const matchedUserIds = matches.map(({user_id}) => user_id)
This is the whole MatchDisplay file that is used in my Dashboard. I'm using a MongoDB for the storage of my users.
import axios from "axios";
import { useEffect, useState } from "react";
import { useCookies } from "react-cookie";
const MatchesDisplay = ({ matches, setClickedUser }) => {
const [matchedProfiles, setMatchedProfiles] = useState(null);
const [cookies, setCookie, removeCookie] = useCookies(null);
const [matched, setMatched] = useState(null);
const matchedUserIds = matches.map(({ user_id }) => user_id);
const userId = cookies.UserId;
const getMatches = async () => {
try {
const response = await axios.get(
"https://[app].herokuapp.com/users",
{
params: { userIds: JSON.stringify(matched()) },
}
);
setMatchedProfiles(response.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getMatches();
}, [matches]);
const filteredMatchedProfiles = matchedProfiles?.filter(
(matchedProfile) =>
matchedProfile.matches.filter(
(profile) => profile.user_id === userId
).length > 0
);
return (
<div className="matches-display">
{filteredMatchedProfiles?.map((match) => (
<div
key={match.user_id}
className="match-card"
onClick={() => setClickedUser(match)}
>
<div className="img-container">
<img
src={match?.url}
alt={match?.first_name + "profile"}
/>
</div>
<h3>{match?.first_name}</h3>
</div>
))}
</div>
);
};
export default MatchesDisplay;
Any help is welcome. If you need more code examples, please reply ;)
EDIT
The ChatContainer that passes the user to the MatchesDisplay.
import ChatHeader from "./ChatHeader";
import MatchesDisplay from "./MatchesDisplay";
import ChatDisplay from "./ChatDisplay";
import { useState } from 'react';
const ChatContainer = ({user}) => {
const [ clickedUser, setClickedUser] = useState(null)
return (
<div className="chat-container">
<ChatHeader user={user}/>
<div>
<button className="option" onClick={() => setClickedUser(null)}>Matches</button>
<button className="option" disabled={!clickedUser}>Chat</button>
<button className="option" >Prices</button>
</div>
{!clickedUser && <MatchesDisplay matches={user.matches} setClickedUser={setClickedUser}/>}
{clickedUser && <ChatDisplay user={user} clickedUser={clickedUser}/>}
</div>
)
}
export default ChatContainer
The Dashboard that passes the user to the Chatcontainer.
import TinderCard from 'react-tinder-card';
import {useEffect, useState} from 'react';
import {useCookies} from 'react-cookie';
import ChatContainer from '../components/ChatContainer'
import axios from "axios";
const Dashboard = () => {
const [user, setUser] = useState(null)
const [genderedUsers, setGenderedUsers] = useState(null)
const [lastDirection, setLastDirection] = useState(null)
const [cookies, setCookie, removeCookie] = useCookies(['user'])
const [matchedUserIds, setMatchedUserIds] = useState(null)
const [filteredGenderedUsers, setFilteredGenderedUsers] = useState(null)
const userId = cookies.UserId
const getUser = async () => {
try {
const response = await axios.get('https://funfit-webpage.herokuapp.com/user', {
params: {userId}
})
return setUser(response.data)
} catch (error) {
console.log(error)
}
}
const getGenderedUsers = async () => {
try {
const response = await axios.get('https://funfit-webpage.herokuapp.com/gendered-users', {
params: {gender: user?.gender_interest}
})
return setGenderedUsers(response.data)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getUser()
}, [])
useEffect(() => {
setMatchedUserIds(user?.matches.map(({user_id}) => user_id).concat(userId))
if (user) return getGenderedUsers()
}, [user])
useEffect(() => {
if (genderedUsers) {
return setFilteredGenderedUsers(genderedUsers?.filter(
genderedUser => !matchedUserIds.includes(genderedUser.user_id)
))
}
}, [genderedUsers])
const updateMatches = async (matchedUserId) => {
try {
await axios.put('https://funfit-webpage.herokuapp.com/addmatch', {
userId,
matchedUserId
})
return getUser()
} catch (error) {
console.log(error)
}
}
const swiped = (direction, swipedUserId) => {
console.log(direction, swipedUserId)
if (direction === 'right') {
updateMatches(swipedUserId)
}
return setLastDirection(direction)
}
const outOfFrame = (name) => {
console.log(name + ' left the screen!')
}
return (<>
{user && <div className="dashboard">
<ChatContainer user={user}/>
<div className="swipe-container">
<div className="card-container">
{filteredGenderedUsers?.map((genderedUser) =>
<TinderCard
className='swipe'
key={genderedUser.user_id}
onSwipe={(dir) => swiped(dir, genderedUser.user_id)}
onCardLeftScreen={() => outOfFrame(genderedUser.first_name)}>
<div style={{backgroundImage: 'url(' + genderedUser.url + ')'}} className='card'>
<h3>{'Name: ' + genderedUser.first_name} <br/> {'Sport: ' + genderedUser.about}</h3>
</div>
</TinderCard>)}
<div className="swipe-info">
{lastDirection ? <p>You swiped {lastDirection}</p> : <p/>}
</div>
</div>
</div>
</div>}
</>)
}
export default Dashboard

Correct way to cleanup useEffect

I have useEffect callback which gives me an error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
This is my code:
import React, {useEffect, useState} from 'react'
import { useTranslation } from 'react-i18next'
import firebase from 'firebase/app'
import '../firebase'
import Input from '../Components/Input'
import Button from '../Components/Button'
import { useHistory, useParams } from 'react-router-dom'
import { connect } from 'react-redux'
import { isAuth } from '../redux/actions/session/isAuth'
import Header from '../Modules/Header'
import Loader from '../Components/Loader'
import logo from '../media/logo/seanor_logo.svg'
let db = firebase.firestore()
const Login = ({setIsAuth, agencyData, userData, userUID, isAuth}) => {
const { t } = useTranslation()
let history = useHistory()
let { agency } = useParams()
// Change document title
document.title = `${t('login')}`
const [loading, setLoading] = useState(false)
// Input states
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(false)
const [agencyErr, setAgencyErr] = useState(false)
useEffect( () => {
const checkUserType = () => {
// Check is user logged in and redirects to dashboard
if (userData !== null && isAuth) {
if (userData.type === 'employee') {
history.push(`/${agency}/dashboard/${userUID}`)
} else if (userData.type === 'crewagency') {
history.push(`/${agency}/crew-dashboard/`)
} else if ( userData.type === 'keyagency') {
history.push(`/${agency}/key-dashboard/`)
}
}
}
return () => checkUserType()
}, [agency, history, userData, userUID, isAuth])
const submit = () => {
setAgencyErr(false)
setLoading(true)
firebase.auth()
.signInWithEmailAndPassword(email, password)
.then(res => {
let resData = JSON.stringify(res.user)
resData = JSON.parse(resData)
let uid = resData.uid
// Check is usere belongs to agency
db.collection('users').doc(uid).get()
.then( res => {
let resData = JSON.stringify(res.data())
resData = JSON.parse(resData)
if (resData.data.agencyId === agency) {
// Check user type and redirect to dashboar by type
if (resData.data.type === 'employee') {
history.push(`/${agency}/dashboard/${uid}`)
} else if (resData.data.type === 'crewagency') {
history.push(`/${agency}/crew-dashboard`)
} else if ( resData.data.type === 'keyagency') {
history.push(`/${agency}/key-dashboard`)
}
} else {
firebase.auth().signOut()
setAgencyErr(true)
}
setLoading(false)
})
.catch(err => console.log('User info error', err))
setIsAuth(true)
})
.catch(err => {
console.log('Login error: ', err.message)
setError(true)
setLoading(false)
})
}
return (
<div>
{loading && <Loader />}
<Header />
<div className='login'>
<div className='login__box'>
<div className='login__logo'>
<img src={logo} alt='logo' />
</div>
<div className='login__box-title'>
<h2>{t('loginToCrewManager')}</h2>
</div>
<div className='login__input'>
<div className='login__input-label'>
{t('email')}
</div>
<Input
type='email'
handleInput={(value) => setEmail(value)}
/>
</div>
<div className='login__input'>
<div className='login__input-label'>
{t('password')}
</div>
<Input
type='password'
handleInput={(value) => setPassword(value)}
/>
</div>
{error &&
<div className='error'>
{t('loginError')}
</div>}
{agencyErr &&
<div className='error'>
{t('agencyErrorLogin')}
</div>}
<div className='login__submit'>
<Button
text='login'
handleClick={() => submit()}
/>
</div>
<div className='login__registration'>
<a href={`/${agency}/reset-password`}>{t('resetPasswordLogin')}</a>
</div>
<div className='login__registration'>
<a href={`/${agency}/registration`}>{t('newUserRegistration')}</a>
</div>
</div>
</div>
</div>
)
}
const mapStateToProps = state => {
return {
agencyData: state.agencyDataRed.obj,
isAuth: state.isAuthRed.bool
}
}
const mapDispatchToProps = dispatch => {
return {
setIsAuth: bool => dispatch(isAuth(bool))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)
Maybe I'm not understand cleanup function? where is my mistake?
I think the problem is in your submit function and you're setting one the states after changing the url. I moved setLoading(false) a bit, try this:
const submit = () => {
setAgencyErr(false);
setLoading(true);
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((res) => {
let resData = JSON.stringify(res.user);
resData = JSON.parse(resData);
let uid = resData.uid;
// Check is usere belongs to agency
db.collection("users")
.doc(uid)
.get()
.then((res) => {
let resData = JSON.stringify(res.data());
resData = JSON.parse(resData);
if (resData.data.agencyId === agency) {
// Check user type and redirect to dashboar by type
if (resData.data.type === "employee") {
history.push(`/${agency}/dashboard/${uid}`);
} else if (resData.data.type === "crewagency") {
history.push(`/${agency}/crew-dashboard`);
} else if (resData.data.type === "keyagency") {
history.push(`/${agency}/key-dashboard`);
}
} else {
firebase.auth().signOut();
setAgencyErr(true);
setLoading(false); //<=== HERE
}
})
.catch((err) => console.log("User info error", err));
setIsAuth(true);
})
.catch((err) => {
console.log("Login error: ", err.message);
setError(true);
setLoading(false);
});
};
setIsAuth(true) is one the candidate that needs to be move to the right place

useMutation update,the cache changed,but UI not change

I useMutation to send message ,but the message list in chat window not change. I found that the cache has changed . Please help , I can't understand.
The useQuery not work . UI have no change :(
But~! When I put them in one js file. it works.... why???
The version I used is #apollo/react-hooks 3.1.1
Parent window.js
import React from 'react';
import { useQuery } from "#apollo/react-hooks";
import { GET_CHAT } from "#/apollo/graphql";
import ChatInput from "#/pages/chat/components/input";
const ChatWindow = (props) => {
const { chatId, closeChat } = props;
const { data, loading, error } = useQuery(GET_CHAT, { variables: { chatId: chatId } });
if (loading) return <p>Loading...</p>;
if (error) return <p>{error.message}</p>;
const { chat } = data;
return (
<div className="chatWindow" key={'chatWindow' + chatId}>
<div className="header">
<span>{chat.users[1].username}</span>
<button className="close" onClick={() => closeChat(chatId)}>X</button>
</div>
<div className="messages">
{chat.messages.map((message, j) =>
<div key={'message' + message.id} className={'message ' + (message.user.id > 1 ? 'left' : 'right')}>
{message.text}
</div>
)}
</div>
<div className="input">
<ChatInput chatId={chatId}/>
</div>
</div>
);
};
export default ChatWindow;
Child input.js
import React, { useState } from 'react';
import { useApolloClient, useMutation } from "#apollo/react-hooks";
import { ADD_MESSAGE, GET_CHAT } from "#/apollo/graphql";
const ChatInput = (props) => {
const [textInput, setTextInput] = useState('');
const client = useApolloClient();
const { chatId } = props;
const [addMessage] = useMutation(ADD_MESSAGE, {
update(cache, { data: { addMessage } }) {
const { chat } = client.readQuery({
query: GET_CHAT,
variables: {
chatId: chatId
}
});
chat.messages.push(addMessage);
client.writeQuery({
query: GET_CHAT,
variables: {
chatId: chatId
},
data: {
chat
}
});
}
});
const onChangeInput = (event) => {
event.preventDefault();
setTextInput(event.target.value);
};
const handleKeyPress = (event, chatId, addMessage) => {
if (event.key === 'Enter' && textInput.length) {
addMessage({
variables: {
message: {
text: textInput,
chatId: chatId
}
}
});
setTextInput('');
}
};
return (
<input type="text"
value={textInput}
onChange={(event) => onChangeInput(event)}
onKeyPress={(event) => handleKeyPress(event, chatId, addMessage)}
/>
);
};
export default ChatInput;
You probably solved the issue by now, but for the record:
Your code mutates the chat state:
chat.messages.push(addMessage);
State should not be mutated (see the React setState Docs for more details).
Contruct a new array instead:
const newChat = [...chat, addMessage]

Resources