Why useRecoilState return outdated data - reactjs

I have React component like this:
import { useRecoilState} from 'recoil';
import { inputText } from './globalState';
export const Input = () => {
const [text, setText] = useRecoilState(inputText);
const handleOnChange = async (event) => {
setText(event.target.value)
console.log(text);
}
return (
<input value={text} onChange={handleOnChange} placeholder='blabla' />
)
}
Also I have global state for recoil purpose:
import { atom } from 'recoil';
export const inputText = atom({
key: 'input',
default: ''
})
My question is, why console.log returns old value of input?

Related

How to implement pagination and search in React

Good evening! I am creating simple React app using TS + React Query + Recoil. App is about 'online library'. I would like to create pagination and search input (to find specific author or title).
My idea was, when app starts I am fetching data from page 1. Then when I'll click 2nd button on my pagination bar I'll fetch data from page 2 etc. Code looks like this:
Main component
import { useGetBooks } from '../../hooks/useGetBooks';
import { BookType } from '../../types/Book';
import { SingleBook } from './SingleBook';
import styled from 'styled-components';
import { Navbar } from './Navbar';
import { Loader } from '../utilities/Loader';
import { Error } from '../utilities/Error';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
import { Books } from '../../recoil/globalState';
type bookType = BookType;
export const BookList = () => {
const [pageNumber, setPageNumber] = useState(1);
const [books, setBooks] = useRecoilState(Books);
const { isLoading, isError } = useGetBooks(pageNumber, setBooks);
if (isLoading) {
return <Loader isLoading={isLoading} />
}
if (isError) {
return <Error />
}
const displayBooks = books.data.map((book: bookType) => {
return (
<SingleBook key={book.id} book={book} />
)
})
return (
<BookContainer>
<div className='test'>
<button onClick={() => setPageNumber((page) => page - 1)} disabled={pageNumber == 1}>Prev page</button>
<p>{books.metadata.page}</p>
<button onClick={() => setPageNumber((page) => page + 1)} disabled={books.metadata.records_per_page * books.metadata.page > books.metadata.total_records}>Next page</button>
</div>
<Navbar />
<BookContent>
{displayBooks}
</BookContent>
</BookContainer>
)
}
React query:
import { useQuery } from "react-query";
import axios from 'axios';
const fetchBooks = async (pageNumber: number) => {
const res = await axios.get(`http://localhost:3001/api/book?page=${pageNumber}`);
return res.data
}
export const useGetBooks = (pageNumber: number, setBooks: any) => {
return useQuery(['books', pageNumber], () => fetchBooks(pageNumber),
{
onSuccess: (data) => setBooks(data),
keepPreviousData: true
})
}
Recoil:
import { atom } from 'recoil';
export const Books = atom({
key: 'book',
default: [] as any
})
And books response:
books: {
data: [
{
author: 'Some crazy',
title: 'Some crazy'
},
{
author: 'Some crazy1',
title: 'Some crazy1'
},
],
metadata: {
page: 1,
records_per_page: 10,
total_records: 17
}
}
Search Input Implementation:
import React, { useState } from "react"
import { useGetFilteredBooks } from "../../hooks/useGetFilteredBooks"
import { useRecoilState } from "recoil"
import { Books } from "../../recoil/globalState"
export const SearchBook = () => {
const [text, setText] = useState('')
const [books, setBooks] = useRecoilState(Books);
const { data, refetch } = useGetFilteredBooks(text, setBooks);
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setText(event.target.value)
}
const handleOnSubmit = (e: any) => {
e.preventDefault();
refetch();
}
return (
<>
<form onSubmit={handleOnSubmit}>
<Input value={text} onChange={handleOnChange} placeholder="Enter the name of the book or author" />
<button type="submit">Show</button>
</form>
<button onClick={() => console.log(books)}>plasda</button>
</>
)
}
React query:
import { useQuery } from "react-query";
import axios from 'axios';
const fetchFilteredsBooks = async (searchText: string) => {
const res = await axios.get(`http://localhost:3001/api/book?search=${searchText}`);
return res.data
}
export const useGetFilteredBooks = (searchText: string, setBooks: any) => {
return useQuery(['filteredBooks', searchText], () => fetchFilteredsBooks(searchText),
{
onSuccess: (data) => setBooks(data),
enabled: false
})
}
We can only display 10 items per 1 page.
PROBLEM:
When we search something and we get data back, we can have scenario, when data will need to be display not on 1 page. So when we have filtered data, and we click 2nd button on pagination, the filtered data will disapeared and we see not filtered data from page 2

React jest login form test

Hi i am new developer testing platform. I have a problem but I did not find a solution or work it with correct way. I am trying to login component test with to parameter by Inputs. Firstly I filled these are userEvent.type. After I am clicking my button. And when I was waiting my method that call by onSubmitForTest in one time , I am facing an error like fallowing image.
What is the reason of this ? How can I solve my problem ? Thanks for your helps.
My Login.tsx component:
import React, { FC, useState } from "react";
import { useTranslation } from "react-i18next";
import Input from "../../components/Input";
import InputPassword from "../../components/Input/InputPassword";
import ButtonLoading from "../../components/Button/ButtonLoading";
import { GetLoginInfo, ILoginRequest } from "../../store/actions/loginActions";
interface ILoginState {
emailorUsername: string;
password: string;
}
const initialState = {
emailorUsername: "",
password: "",
};
interface IProps {
onSubmitForTest: (items: any) => void
}
const Login: FC<IProps> = ({ onSubmitForTest }) => {
const { t } = useTranslation();
const [state, setstate] = useState<ILoginState>(initialState);
const onChange = (key: string, value: string | number) => {
setstate({ ...state, [key]: value });
};
const handleLogin = async () => {
const loginRequest: ILoginRequest = {
emailOrUsername: state.emailorUsername,
password: state.password,
returnUrl: "",
};
const response = await GetLoginInfo(loginRequest);
if (response.isSucceed) { } else { }
};
const renderLoginPart = () => {
return (
<div className="flex">
<Input
name="emailorUsername"
label={t("emailorUsername")}
value={state.emailorUsername}
onChange={(val: any) => onChange("emailorUsername", val)}
/>
<InputPassword
name="password"
label={t("password")}
value={state.password}
onChange={(val: any) => onChange("password", val)}
/>
<ButtonLoading
text={t("login")}
variant="contained"
onClick={() => {
if (onSubmitForTest) {
const loginRequestItemForTest = {
emailOrUsername: "testUsername",
password: "testPassword",
};
onSubmitForTest(loginRequestItemForTest)
}
handleLogin()
}}
dataTestid={"login-button-element"}
/>
</div>
);
};
return <div className="">{renderLoginPart()}</div>;
};
export default Login;
My index.test.js :
import React from 'react'
import { render, screen, waitFor } from "#testing-library/react"
import LoginPage from "../index"
import userEvent from "#testing-library/user-event"
const onSubmit = jest.fn()
beforeEach(()=>{
const { } = render(<LoginPage />)
onSubmit.mockClear()
})
test('Login form parametre olarak doğru data gönderme testi', async () => {
const eMail = screen.getByTestId('text-input-element')
const password = screen.getByTestId('password-input-element')
userEvent.type(eMail, "fillWithTestUsername")
userEvent.type(password, "fillWithTestPassword")
userEvent.click(screen.getByTestId('login-button-element'))
await waitFor(()=>{
expect(onSubmit).toHaveBeenCalledTimes(1)
})
})
beforeEach(()=>{
render(<LoginPage onSubmitForTest={onSubmit} />)
})
Please try doing this in beforeEach. If this still doesn't work you can try replacing toHaveBeenCalledTimes with toBeCalledTimes like below
await waitFor(()=>{
expect(onSubmit).toBeCalledTimes(1)
})

The state in React Context does not change

I have been practicing with React Context, but I have a problem with storing a user in my general state, the problem is that I have to click the save button twice to save the state in my context, with the first click I only see that the state is as an empty object and with the second click the state is saved (the name and password), this is the code I have. This would be the context of my application:
import { createContext } from "react";
const UserContext = createContext()
export default UserContext
this would be my types file:
export const LOGIN= 'LOGIN'
this is my UserState file:
import { useReducer } from "react"
import UserReducer from './UserReducer'
import UserContext from "./UserContex"
const UserState = ({ children }) => {
const initialState = {
usernew: {}
}
const [state, dispatch] = useReducer(UserReducer, initialState)
const login = (userLogin) => {
dispatch({
type: 'LOGIN',
payload: userLogin
})
window.localStorage.setItem('user', JSON.stringify(state.usernew))
const data = window.localStorage.getItem('user')
console.log(data)
}
return (
<UserContext.Provider
value={{
usernew: state.usernew,
login,
}}
>
{children}
</UserContext.Provider>
)
}
export default UserState
UserReducer file:
import {LOGIN} from "../types";
export default (state, action) => {
const { payload, type } = action;
switch (type) {
case LOGIN:
return {
...state,
usernew: payload
}
default:
return state
}
}
and this would be my component where I am changing the context:
import { Box, Button } from '#chakra-ui/react'
import { Input } from '#chakra-ui/react'
import { useState } from 'react'
import { useContext } from 'react'
import UserContext from '../context/User/UserContex'
const Login = () => {
const { login, usernew} = useContext(UserContext)
const [fields, setFields] = useState({
name: '',
password: ''
})
const handleChange = (e) => {
const name = e.target.name;
const value = e.target.value;
setFields({
...fields,
[name]: value
})
}
return (
<Box w='80%' p={4}>
<Input
placeholder='Name'
onChange={handleChange}
name="name"
value={fields.name}
/>
<Input
placeholder='Password'
onChange={handleChange}
name="password"
value={fields.password}
/>
<Button bg="teal" color="#fff" onClick={() => login(fields)}>Submit</Button>
{usernew ?
<div>
<h2>Usuario con {usernew.name} - {usernew.password}</h2>
</div>
:
<div>
<h1>Sin usuario</h1>
</div>
}
</Box>
)
}
export default Login
I checked all the code but I can't find the error, that's why I'm showing all the code I have. Thank you for your attention

How to test a controlled input (via Redux state) that dispatches an action on its onChange handler?

This is my component:
import React, { ChangeEvent } from "react";
import { Input } from "#chakra-ui/react"
import { useDispatch, useSelector } from "react-redux";
import { ACTION } from "../../redux/appSlice";
import { RootState } from "../../redux/store";
interface IAddTodoForm {}
const AddTodoForm: React.FC<IAddTodoForm> = (props) => {
const dispatch = useDispatch();
const { addTodoForm } = useSelector((state: RootState) => state.app);
const { title } = addTodoForm;
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
const title = e.target.value;
dispatch(ACTION.UPDATE_TODO_INPUT({title}));
};
return(
<Input
value={title}
onChange={onChange}
/>
);
};
export default React.memo(AddTodoForm);
It's a basic input that dispatches to Redux from the onChange handler.
This is what I'd like to test:
import { render, screen, fireEvent } from "../../../test/test-utils";
import AddTodoForm from "../AddTodoForm";
beforeEach(() => {
render(<AddTodoForm/>); // NOTE: THIS IS A CUSTOM render FUNCTION THAT ALREADY WRAPPED WITH THE <Provider store={store}> FROM react-redux
});
test("AddTodoForm updated input vale", () => {
const { container } = render(<AddTodoForm/>);
const input = container.getElementsByTagName("input")[0];
expect(input).toBeInTheDocument();
fireEvent.change(input, { target: { value: "NEW TODO" }});
// HERE I WOULD LIKE TO CHECK IF THE INPUT VALUE HAS BEEN UPDATE
// HOW CAN I DO THAT
});
As you can see, I would like to fire a change event, that should dispatch to the Redux store, and then I would like to confirm that the input has been updated with the NEW TODO value. Is this the correct approach?
You would simply use an expect like so:
test("AddTodoForm updated input vale", () => {
const input = screen.getByLabelText("add-todo-input");
expect(input).toBeInTheDocument();
fireEvent.change(input, { target: { value: "NEW TODO" }});
expect(input.value).toBe('NEW TODO')
});
For an async operation, you could use this method instead:
await screen.findByText("NEW TODO");
expect(getByText("NEW TODO")).toBeTruthy();
By using await findByText you wait for the text to appear.

React How to use state in different component

Somebody know why my state doesn't update
This is my Context:
import React, { createContext, useState } from 'react';
export const WeatherDataContext = createContext();
const WeatherDataContextProvider = (props) => {
const [weather, setWeather] = useState(
{
city: null,
temp: null
}
)
const addWeather = (city, temp) => {
setWeather({
city,
temp
})
}
return (
<WeatherDataContext.Provider value={{weather, addWeather}}>
{props.children}
</WeatherDataContext.Provider>
)
}
export default WeatherDataContextProvider
And My Form where I pass my data from axios to addWeather function:
import React, {useContext, useState} from 'react';
import { WeatherDataContext } from '../context/WeatherDataContext';
import axios from 'axios'
import {Link} from 'react-router-dom'
const WeatherForm = () => {
const {addWeather} = useContext(WeatherDataContext);
const [value, setValue] = useState('')
const handleChange = (e) => {
e.preventDefault();
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${value}&appid=${KEY}&units=metric`)
.then(res => {
addWeather({
city: res.data.name,
temp: res.data.main.temp
});
})
}
return (
<div class='weather-form'>
<form onSubmit={handleChange}>
<input placeholder='Wpisz miasto' onChange={(e) => setValue(e.target.value)} value={value} required/>
<Link to='/weather'><button>Wyszukaj</button></Link>
</form>
</div>
)
}
export default WeatherForm
After i update my state in the context i want to use that data in different component like this:
import React, {useContext, useState} from 'react';
import { WeatherDataContext } from '../context/WeatherDataContext';
const WeatherFront = () => {
const {weather} = useContext(WeatherDataContext)
console.log(weather)
return (
<div class='weather-front'>
<h1>City: {weather.city}, Temperatura: {weather.temp}</h1>
</div>
)
}
export default WeatherFront
Sum up i dont know why but my state doesnt update and my weather component return only null from
initial state
import React from 'react';
import WeatherDataContextProvider from '../context/WeatherDataContext'
import WeatherFront from '../components/WeatherFront';
const Weather = () => {
return (
<div class='weather'>
<WeatherDataContextProvider>
<WeatherFront />
</WeatherDataContextProvider>
</div>
)
}
export default Weather
WeatherDataContext has the values as weather and addWeather and not changeCity and changeWeather
You need to consume it appropriately
const {addWeather} = useContext(WeatherDataContext);
const handleChange = (e) => {
e.preventDefault();
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${value}&appid=${KEY}&units=metric`)
.then(res => {
addWeather({
city: res.data.name,
temp: res.data.main.temp
});
})
}

Resources