How to get useState values inside getServerSideProps in NextJS - javascript

I have two functions one function takes input from user and other performs search for that supplied input data. I want to use searchQuery from useState inside getServerSideProps function.
Is there any way to do this?
function SearchDemo() {
const [searchQuery, setSearchQuery] = useState('')
const ChangeHandler = event => {
setSearchQuery(event.target.value)
}
const ButtonHandler = event => {
event.preventDefault()
console.log(`Data: ${searchQuery}`)
}
return (
<div>
<form onSubmit={ButtonHandler}>
<div>
<label>Search:</label>
<input type="text" name="search" value={searchQuery} onChange={ChangeHandler}/>
<button>Search</button>
</div>
</form>
</div>
);
}
export async function getServerSideProps({query: {page}, searchQuery}){
console.log(searchQuery)
return {
props: {
data: data
}
}
}

You need to push the url and reload the page, because it's serverside-rendered.
const router = useRouter()
const ButtonHandler = event => {
event.preventDefault()
router.push(`/thecurrentUrl?search=${searchQuery}`
// if doesn't work then use window.location.href
console.log(`Data: ${searchQuery}`)
}
export async function getServerSideProps({query: {page}, searchQuery}){
console.log(query.search)
return {
props: {
data: data
}
}
}

Related

How to write value to localStorage and display it in input on reload?

I have an input on the page, initially it is empty. I need to implement the following functionality: on page load, the component App fetches from localStorage a value of key appData and puts it in the input. That is, so that in the localStorage I write the value to the input and when reloading it is displayed in the input. How can i do this?
I need to use useEffect
import { useEffect, useState } from "react";
export default function App() {
const [userData, setUserData] = useState("");
useEffect(() => {
localStorage.setItem("Userdata", JSON.stringify(userData));
}, [userData]);
return (
<div>
<input value={userData} onChange={(e) => setUserData(e.target.value)}></input>
</div>
);
}
Use the change event to write to the localStorage, then use an init function in the useState hook.
import { useState } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const [ userData, setUserData ] = useState(loadUserData);
const handleUserDataUpdate = e => {
const userData = e.target.value;
setUserData(userData);
saveUserData(userData);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input id="testInput" value={ userData } onChange={ handleUserDataUpdate } />
</div>;
}
If you need an example using uncontrolled inputs, here is one using useEffect :
import { useEffect } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const inputRef = useRef();
useEffect(() => {
inputRef.current.value = loadUserData();
}, []); // initial load
const handleUpdateUserData = () => {
saveUserData(inputRef.current.value);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input ref={ inputRef } id="testInput" onChange={ handleUpdateUserData } />
</div>;
}
You can set a default value for the input inside state.
const [userData, setUserData] =
useState(JSON.parse(localStorage.getItem('Userdata')) || '');
So when the component mounts (after reload), the initial userData value is taken directly from the localStorage. If it's empty, the fallback value will be set ('').
Note: Make sure to add also the onChange handler to the input.

Read data from request which just finished

If user type id in input, I'd like to fetch post by specific id typed by user. If not, I'd like to fetch whole array of posts, and then get some id from fetched data. I know it doesn't make sense, but it just only for tests.
It doesn't work, cause useState works asynchronously. I tried few solutions, but all of them looked very ugly.
LIVE DEMO
I received an error:
Cannot read properties of undefined (reading 'id')
Cause setPosts hasn't set yet.
What is the best, clear way to handle this case?
import { useState } from "react";
export default function App() {
const [id, setId] = useState("");
const [post, setPost] = useState(null);
const [posts, setPosts] = useState([]);
const fetchPost = async (id) => {
const res = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
setPost(res.data);
};
const fetchPosts = async () => {
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
setPosts(res.data);
};
const onSubmit = async (e) => {
e.preventDefault();
if (id) {
await fetchPost(id);
} else {
await fetchPosts();
await fetchPost(posts[0].id);
}
};
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" onChange={(e) => setId(e.target.value)} />
<button type="submit">submit</button>
</form>
</div>
);
}
You can treat fetchPosts as a side-effect and wrap fetchPost(posts[0].id) in a useEffect dependant on posts.
Or just use the result directly in onSubmit() (presuming you don't need posts for something else).
const fetchPosts = async () => {
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
// setPosts(res.data); // this state is transitory and not used directly by the render
return res.data;
};
const onSubmit = async (e) => {
e.preventDefault();
if (id) {
await fetchPost(id);
} else {
const posts = await fetchPosts(); // Only used as part of submit event?
await fetchPost(posts[0].id);
}
};
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" onChange={(e) => setId(e.target.value)} />
<button type="submit">submit</button>
</form>
<div>{(post && post.title) || "No post yet"}</div>
</div>
);
Just like you said useState works asynchronously , if you want to do something after mutating it you will have to use useEffect and set posts as its arguments , now whenever the posts get mutated your funcion will be run and the first index of array will be sent to the fetchPost(id),
import axios from "axios";
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [id, setId] = useState("");
const [post, setPost] = useState(null);
const [posts, setPosts] = useState([]);
useEffect(() => {
if (posts.length) {
fetchPost(posts[0].id);
}
}, [posts]);
const fetchPost = async (id) => {
console.log(`fetching ${id}`);
const res = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
console.log(res.data);
setPost(res.data);
};
const fetchPosts = async () => {
console.log(`fetching all posts`);
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
setPosts(res.data);
};
const onSubmit = async (e) => {
e.preventDefault();
if (id) {
await fetchPost(id);
} else {
await fetchPosts();
// res = await fetchPost(posts[0].id); we dont need it here it defined in useffect function
}
};
const setDefaultId = (e) => {
setId(e.target.value);
};
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" onChange={(e) => setDefaultId(e)} />
<button type="submit">submit</button>
</form>
</div>
);
}
Also consider never to update state directly in your return function it will cause performance issues
The problem is in the method "fetchPost". Inside this method, you have two variables with the same name. "id" from the state hook, and "id" from the function parameter.
You can solve the problem changing one of those variables names.
One more thing, if "id" doesn't have value, your way to get the first post won't work because the await method won't wait to the change of the state.
I have edit a bit the code to solve both problems.
import { useState } from 'react';
import axios from 'axios';
export default function App() {
const [id, setId] = useState('');
const [post, setPost] = useState(null);
const fetchPost = async (idProp) => {
const res = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${idProp}`,
);
setPost(res.data);
};
const fetchPosts = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
await fetchPost(res.data[0].id);
};
const onSubmit = async (e) => {
e.preventDefault();
if (id) {
await fetchPost(id);
} else {
await fetchPosts();
}
};
return (
<div className="App">
<form onSubmit={onSubmit}>
<input type="text" onChange={(e) => {
setId(e.target.value);
}} />
<button type="submit">submit</button>
</form>
</div>
);
}
I hop I've helped you.

Why can't I have updated states in function component (using hook)?

I have login function component with two inputs. It's controlled component so email and password are bound to state(user). Input is a component I use instead of input tag(refactoring input). I can change state user(email and password) with input values using handleInputChange event handler and I also can submit form using handleSubmit handler.
Everything was good until I tried to validate form using yup and catching errors. I declared errors state to save errors I got. I want to catch errors and show in "div className="alert"" and I want to post user to server when no error exists. I see the errors related to yup in validate() function, but when I change errors state(setErrors([...er.errors])) I find errors state empty (console.log(errors.length)).
Here is login component:
import axios from "axios";
import queryString from "query-string"
import { useEffect, useRef,useState } from "react";
import React from "react"
import {useLocation, useRouteMatch,useParams} from "react-router-dom"
import Input from "./input";
import * as yup from 'yup';
const Login = () => {
useEffect(async()=>{
console.log(errors)
},[errors])
var [user,setUser]=useState({email:'',password:''});
var [errors,setErrors]=useState([])
let schema=yup.object().shape({
email:yup.string().email("ایمیل نامعتبر است").required("فیلد ایمیل الزامیست"),
password:yup.string().min(8,"رمز عبور باید حداقل 8 رقم باشد")
})
const validate=async()=>{
try {
const resultValidate=await schema.validate(user, { abortEarly: false })
}
catch(er){
console.log(er.errors)
setErrors([...er.errors])
}
}
const handleSubmit= async(e)=>{
e.preventDefault();
await validate();
console.log(errors.length)
if(errors.length===0){
alert("X")
const response= await axios.post("https://reqres.in/api/login",user)
console.log(response)
}
}
const handleInputChange=async(e)=>{
setUser({...user,[e.currentTarget.name]:e.currentTarget.value})
}
return (
<>
<div id="login-box" className="col-md-12">
{errors.length!==0 && (<div className="alert">
<ul>
{errors.map((element,item)=>{
return(
<>
<li key={item}>
{element}
</li>
</>
)
})}
</ul>
</div>) }
<form onSubmit={handleSubmit} id="login-form" className="form" action="" method="post">
<h3 className="text-center text-info">Login</h3>
<Input onChange={handleInputChange} name="email" id="email" label="نام کاربری" value={user.email}/>
<Input name="password" onChange={handleInputChange} id="password" value={user.password} label="رمز عبور"/>
{/* <div id="register-link" className="text-right">
Register here
</div> */}
<input type="submit" className="btn btn-primary" value="ثبت"/>
</form>
</div>
</>
);
}
export default Login;
and here is Input component:
import {Component} from "react"
class Input extends Component {
render() {
return <>
<div className="form-group">
<label htmlFor="username" className="text-info">{this.props.label}</label><br/>
<input type="text" onChange={this.props.onChange} name={this.props.name} id={this.props.id} className="form-control" value={this.props.value} />
</div>
</>;
}
}
export default Input;
I understood that setStates(in my component setErrors) are asynchronous and it's delayed. I tried using simple array variable (named errors) instead of state and hook, but guess what, it didn't rerender page when I changed the errors variable! Of course I can't see errors in page using this way.
I tried to resolve this using useEffect() and I decided to check validation errors and post in useEffect instead of handleSubmit handler:
useEffect(async()=>{
if(errors.length===0){
const response= await axios.post("https://reqres.in/api/login",user)
console.log(response)
}
console.log(errors)
}, [errors])
Now I see errors when inputs are invalid. When I type valid values, there are still same errors!
It looks like I can't have updated errors state and I just get previous errors even after I enter valid values! I try to not use class based component as I can. What shall I do?
You can return true if the input values are validated and false if not, from the validate function like this:
const validate = async () => {
try {
const resultValidate = await schema.validate(user, { abortEarly: false });
return true;
} catch (er) {
console.log(er.errors);
setErrors([...er.errors]);
return false;
}
};
And now in the handleSubmit function you have to modify a bit:
const handleSubmit = async (e) => {
e.preventDefault();
const isValid = await validate();
console.log(errors.length);
if (isValid) {
alert("X");
const response= await axios.post("https://reqres.in/api/login",user)
console.log(response)
setErrors([]); //so that the previous errors are removed
}
};
Issue
The issue you face is that React state updates are asynchronously processed. This doesn't mean that the state update is async and can be waited for. The errors state you enqueue won't be available until the next render cycle.
const validate = async () => {
try {
const resultValidate = await schema.validate(user, { abortEarly: false });
} catch(er) {
console.log(er.errors);
setErrors([...er.errors]); // (2) <-- state update enqueued
}
}
const handleSubmit = async (e) => {
e.preventDefault();
await validate(); // (1) <-- validate called and awaited
console.log(errors.length); // <-- (3) errors state from current render cycle
if (errors.length === 0) {
alert("X");
const response = await axios.post("https://reqres.in/api/login", user);
console.log(response);
}
}
Solution
I suggest returning an "errors" object from validate instead, you can enqueue any state updates later if you like.
const validate = async () => {
const errors = [];
try {
await schema.validate(user, { abortEarly: false });
} catch(er) {
console.log(er.errors);
errors.push(...er.errors);
}
return errors;
}
const handleSubmit = async (e) => {
e.preventDefault();
const errors = await validate();
console.log(errors.length);
if (!errors.length) {
alert("X");
const response = await axios.post("https://reqres.in/api/login", user);
console.log(response);
} else {
setErrors(prevErrors => [...prevErrors, ...errors]);
}
}

React hook form returning blank object on submit

Here's my simplified problem:
https://codesandbox.io/s/busy-fire-mm91r?file=/src/FormWrapper.tsx
And the code:
export const FormItemWrapper = ({ children }) => {
return <FormItem>{children}</FormItem>;
};
export const FormWrapper = ({ children }) => {
const { handleSubmit } = useForm();
const onSubmit = (data) => {
console.log(data); // logs { }
};
return <Form onFinish={handleSubmit(onSubmit)}>{children}</Form>;
};
export default function App() {
const { control, watch } = useForm();
console.log(watch()); // logs { }
return (
<FormWrapper>
<FormItemWrapper>
<Controller
control={control}
name="tmp"
render={({ field }) => <Input {...field} />}
/>
</FormItemWrapper>
<FormItemWrapper>
<Button htmlType="submit">Save</Button>
</FormItemWrapper>
</FormWrapper>
);
}
The problem:
React-hook-form doesn't seem to see the data I type in. I can get it using antd, but can't with react-hook-form. Why? What am I missing?
watch() logs only once, and it logs { }. onSubmit logs { }
You have created two different form instances with useForm call. If you want to get current form context inside Controller you should use useFormContext and wrap your form in FormProvider.
Working example:
https://codesandbox.io/s/admiring-lehmann-mgd0i?file=/src/App.tsx

Reactjs filter not returning correct users, when i delete the characters in the search filter

I am fetching the users from dummyapi and i am listing them. There is a search input, i want to filter the users on the page by name. When i type the characters, it filters correctly. When i start to delete the character, users are not listed correctly. It remains filtered. How can i fix this ? This is my code:
import { useEffect, useState } from "react";
import Header from "../components/Header";
import User from "./User";
import axios from "axios";
function App() {
const BASE_URL = "https://dummyapi.io/data/api";
const APP_ID = "your app id";
const [users, setUsers] = useState(null);
const handleChange = (e) => {
const keyword = e.target.value.toLowerCase();
const filteredUsers =
users &&
users.filter((user) => user.firstName.toLowerCase().includes(keyword));
setUsers(filteredUsers);
};
useEffect(() => {
async function fetchData() {
try {
const response = await axios.get(`${BASE_URL}/user?limit=1`, {
headers: { "app-id": APP_ID },
});
setUsers(response.data.data);
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
return (
<>
<Header />
<div className="container">
<div className="filter">
<h3 className="filter__title">USER LIST</h3>
<div>
<input
id="filter"
type="text"
placeholder="Search by name"
onChange={handleChange}
/>
</div>
</div>
<div className="user__grid">
{users &&
users.map((user, index) => {
const { id } = user;
return <User key={index} id={id} />;
})}
</div>
</div>
</>
);
}
export default App;
This is because you are manipulating the original array of users. So after each filter the original array has less values than previous hence after deleting it will search from the reduced number of elements.
To avoid this, keep original way as it is, apply filter on that and store the result in a separate array.
Something like this:
const [allUsers, setAllUsers] = useState(null); //will store original records
const [users, setUsers] = useState(null); // will store filtered results
then in useEffect hook:
useEffect(() => {
async function fetchData() {
try {
const response = await axios.get(`${BASE_URL}/user?limit=1`, {
headers: { "app-id": APP_ID },
});
setUsers(response.data.data);
setAllUsers(response.data.data); //add this line
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
and finally in handleChange event:
const handleChange = (e) => {
const keyword = e.target.value.toLowerCase();
// use allUsers array (with original unchanged data)
const filteredUsers =
allUsers &&
allUsers.filter((user) => user.firstName.toLowerCase().includes(keyword));
setUsers(filteredUsers);
};
Obviously, you can use some better approach, but this is just to give the idea of original issue.

Categories

Resources